mirror of
				https://github.com/SoPat712/YTLitePlus.git
				synced 2025-10-30 20:34:03 -04:00 
			
		
		
		
	added files via upload
This commit is contained in:
		
							
								
								
									
										2
									
								
								Tweaks/YouPiP/.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								Tweaks/YouPiP/.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| # Auto detect text files and perform LF normalization | ||||
| * text=auto | ||||
							
								
								
									
										3
									
								
								Tweaks/YouPiP/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								Tweaks/YouPiP/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| .theos | ||||
| .DS_Store | ||||
| packages | ||||
							
								
								
									
										18
									
								
								Tweaks/YouPiP/AVKit-Header.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								Tweaks/YouPiP/AVKit-Header.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| #import <AVFoundation/AVFoundation.h> | ||||
| #import <AVKit/AVKit.h> | ||||
| #import <UIKit/UIKit.h> | ||||
|  | ||||
| @interface AVPlayerController : UIResponder | ||||
| @end | ||||
|  | ||||
| @interface AVPictureInPictureControllerContentSource (Private) | ||||
| @property(assign) bool hasInitialRenderSize; | ||||
| @end | ||||
|  | ||||
| @interface AVPictureInPictureController (Private) | ||||
| @property(nonatomic, retain) AVPictureInPictureControllerContentSource *contentSource API_AVAILABLE(ios(15.0)); // retain -> strong on iOS 15 | ||||
| - (instancetype)initWithContentSource:(AVPictureInPictureControllerContentSource *)contentSource API_AVAILABLE(ios(15.0)); | ||||
| - (void)sampleBufferDisplayLayerRenderSizeDidChangeToSize:(CGSize)renderSize; | ||||
| - (void)sampleBufferDisplayLayerDidAppear; | ||||
| - (void)sampleBufferDisplayLayerDidDisappear; | ||||
| @end | ||||
							
								
								
									
										16
									
								
								Tweaks/YouPiP/Header.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								Tweaks/YouPiP/Header.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| #ifndef YOUPIP_H_ | ||||
| #define YOUPIP_H_ | ||||
|  | ||||
| #import "AVKit-Header.h" | ||||
|  | ||||
| #define TweakName @"YouPiP" | ||||
|  | ||||
| #define EnabledKey @"YouPiPEnabled" | ||||
| #define CompatibilityModeKey @"CompatibilityModeKey" | ||||
| #define PiPActivationMethodKey @"PiPActivationMethodKey" | ||||
| #define PiPActivationMethod2Key @"PiPActivationMethod2Key" | ||||
| #define NoMiniPlayerPiPKey @"NoMiniPlayerPiPKey" | ||||
| #define NonBackgroundableKey @"NonBackgroundableKey" | ||||
| #define FakeVersionKey @"FakeVersionKey" | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										22
									
								
								Tweaks/YouPiP/LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								Tweaks/YouPiP/LICENSE
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| MIT License | ||||
|  | ||||
| Copyright (c) 2018 - 2020 SpicaT | ||||
| Copyright (c) 2020 - 2023 PoomSmart | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| in the Software without restriction, including without limitation the rights | ||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all | ||||
| copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
| SOFTWARE. | ||||
							
								
								
									
										357
									
								
								Tweaks/YouPiP/LegacyPiPCompat.x
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										357
									
								
								Tweaks/YouPiP/LegacyPiPCompat.x
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,357 @@ | ||||
| #import "Header.h" | ||||
| #import <version.h> | ||||
| #import "../YouTubeHeader/MLAVPlayer.h" | ||||
| #import "../YouTubeHeader/MLHAMQueuePlayer.h" | ||||
| #import "../YouTubeHeader/MLPIPController.h" | ||||
| #import "../YouTubeHeader/MLPlayerPool.h" | ||||
| #import "../YouTubeHeader/MLPlayerPoolImpl.h" | ||||
| #import "../YouTubeHeader/MLVideoDecoderFactory.h" | ||||
| #import "../YouTubeHeader/MLDefaultPlayerViewFactory.h" | ||||
| #import "../YouTubeHeader/YTHotConfig.h" | ||||
| #import "../YouTubeHeader/YTPlayerPIPController.h" | ||||
| #import "../YouTubeHeader/YTBackgroundabilityPolicy.h" | ||||
| #import "../YouTubeHeader/YTPlayerViewControllerConfig.h" | ||||
| #import "../YouTubeHeader/YTSystemNotifications.h" | ||||
| #import "../YouTubeHeader/YTAutonavEndscreenController.h" | ||||
|  | ||||
| extern BOOL TweakEnabled(); | ||||
| extern BOOL isPictureInPictureActive(MLPIPController *); | ||||
|  | ||||
| BOOL hasSampleBufferPiP; | ||||
| BOOL isLegacyVersion; | ||||
|  | ||||
| BOOL LegacyPiP() { | ||||
|     return isLegacyVersion ? YES : [[NSUserDefaults standardUserDefaults] boolForKey:CompatibilityModeKey]; | ||||
| } | ||||
|  | ||||
| static void forceRenderViewTypeBase(YTIHamplayerConfig *hamplayerConfig) { | ||||
|     if (!LegacyPiP()) return; | ||||
|     hamplayerConfig.renderViewType = 2; | ||||
| } | ||||
|  | ||||
| static void forceRenderViewTypeHot(YTIHamplayerHotConfig *hamplayerHotConfig) { | ||||
|     if (!LegacyPiP()) return; | ||||
|     hamplayerHotConfig.renderViewType = 2; | ||||
| } | ||||
|  | ||||
| static void forceRenderViewType(YTHotConfig *hotConfig) { | ||||
|     YTIHamplayerHotConfig *hamplayerHotConfig = [hotConfig hamplayerHotConfig]; | ||||
|     forceRenderViewTypeHot(hamplayerHotConfig); | ||||
| } | ||||
|  | ||||
| static MLPIPController *(*InjectMLPIPController)(void); | ||||
| static YTSystemNotifications *(*InjectYTSystemNotifications)(void); | ||||
| static YTBackgroundabilityPolicy *(*InjectYTBackgroundabilityPolicy)(void); | ||||
| static YTPlayerViewControllerConfig *(*InjectYTPlayerViewControllerConfig)(void); | ||||
| YTHotConfig *(*InjectYTHotConfig)(void); | ||||
|  | ||||
| %group WithInjection | ||||
|  | ||||
| YTPlayerPIPController *initPlayerPiPControllerIfNeeded(YTPlayerPIPController *controller, id delegate, id parentResponder) { | ||||
|     if (controller) return controller; | ||||
|     controller = [[%c(YTPlayerPIPController) alloc] init]; | ||||
|     MLPIPController *pip = InjectMLPIPController(); | ||||
|     YTSystemNotifications *systemNotifications = InjectYTSystemNotifications(); | ||||
|     YTBackgroundabilityPolicy *bgPolicy = InjectYTBackgroundabilityPolicy(); | ||||
|     YTPlayerViewControllerConfig *playerConfig = InjectYTPlayerViewControllerConfig(); | ||||
|     [controller setValue:pip forKey:@"_pipController"]; | ||||
|     [controller setValue:bgPolicy forKey:@"_backgroundabilityPolicy"]; | ||||
|     [controller setValue:playerConfig forKey:@"_config"]; | ||||
|     @try { | ||||
|         YTHotConfig *config = InjectYTHotConfig(); | ||||
|         [controller setValue:config forKey:@"_hotConfig"]; | ||||
|     } @catch (id ex) {} | ||||
|     if (parentResponder) { | ||||
|         @try { | ||||
|             [controller setValue:parentResponder forKey:@"_parentResponder"]; | ||||
|         } @catch (id ex) {} | ||||
|     } | ||||
|     [controller setValue:delegate forKey:@"_delegate"]; | ||||
|     [bgPolicy addBackgroundabilityPolicyObserver:controller]; | ||||
|     [pip addPIPControllerObserver:controller]; | ||||
|     [systemNotifications addSystemNotificationsObserver:controller]; | ||||
|     return controller; | ||||
| } | ||||
|  | ||||
| %hook YTPlayerPIPController | ||||
|  | ||||
| - (instancetype)initWithDelegate:(id)delegate { | ||||
|     return initPlayerPiPControllerIfNeeded(%orig, delegate, nil); | ||||
| } | ||||
|  | ||||
| - (instancetype)initWithDelegate:(id)delegate parentResponder:(id)parentResponder { | ||||
|     return initPlayerPiPControllerIfNeeded(%orig, delegate, parentResponder); | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| %hook YTAutonavEndscreenController | ||||
|  | ||||
| - (instancetype)initWithParentResponder:(id)arg1 config:(id)arg2 imageService:(id)arg3 lastActionController:(id)arg4 reachabilityController:(id)arg5 endscreenDelegate:(id)arg6 { | ||||
|     self = %orig; | ||||
|     if ([self valueForKey:@"_pipController"] == nil) | ||||
|         [self setValue:InjectMLPIPController() forKey:@"_pipController"]; | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| %hook MLHAMQueuePlayer | ||||
|  | ||||
| - (instancetype)initWithStickySettings:(MLPlayerStickySettings *)stickySettings playerViewProvider:(MLPlayerPoolImpl *)playerViewProvider playerConfiguration:(void *)playerConfiguration { | ||||
|     self = %orig; | ||||
|     if ([self valueForKey:@"_pipController"] == nil) | ||||
|         [self setValue:InjectMLPIPController() forKey:@"_pipController"]; | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| %hook MLAVPlayer | ||||
|  | ||||
| - (bool)isPictureInPictureActive { | ||||
|     return isPictureInPictureActive(InjectMLPIPController()); | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| %hook MLPlayerPoolImpl | ||||
|  | ||||
| - (instancetype)init { | ||||
|     self = %orig; | ||||
|     if (self) | ||||
|         [self setValue:InjectMLPIPController() forKey:@"_pipController"]; | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| %hook MLAVPIPPlayerLayerView | ||||
|  | ||||
| - (id)initWithPlaceholderPlayerItem:(AVPlayerItem *)playerItem { | ||||
|     self = %orig; | ||||
|     if ([self valueForKey:@"_pipController"] == nil) | ||||
|         [self setValue:InjectMLPIPController() forKey:@"_pipController"]; | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| %end | ||||
|  | ||||
| %group Legacy | ||||
|  | ||||
| static MLAVPlayer *makeAVPlayer(id self, MLVideo *video, MLInnerTubePlayerConfig *playerConfig, MLPlayerStickySettings *stickySettings, BOOL gimmeAlloc) { | ||||
|     BOOL externalPlaybackActive = [(MLAVPlayer *)[self valueForKey:@"_activePlayer"] externalPlaybackActive]; | ||||
|     MLAVPlayer *player = gimmeAlloc ? [((MLPlayerPool *)self).gimme allocOf:%c(MLAVPlayer)] : [%c(MLAVPlayer) alloc]; | ||||
|     player = [player initWithVideo:video playerConfig:playerConfig stickySettings:stickySettings externalPlaybackActive:externalPlaybackActive]; | ||||
|     if (stickySettings) | ||||
|         player.rate = stickySettings.rate; | ||||
|     return player; | ||||
| } | ||||
|  | ||||
| %hook MLPIPController | ||||
|  | ||||
| - (void)activatePiPController { | ||||
|     if (!isPictureInPictureActive(self)) { | ||||
|         AVPictureInPictureController *pip = [self valueForKey:@"_pictureInPictureController"]; | ||||
|         if (!pip) { | ||||
|             MLAVPIPPlayerLayerView *avpip = [self valueForKey:@"_AVPlayerView"]; | ||||
|             if (avpip) { | ||||
|                 AVPlayerLayer *playerLayer = [avpip playerLayer]; | ||||
|                 pip = [[AVPictureInPictureController alloc] initWithPlayerLayer:playerLayer]; | ||||
|                 [self setValue:pip forKey:@"_pictureInPictureController"]; | ||||
|                 pip.delegate = self; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (void)deactivatePiPController { | ||||
|     AVPictureInPictureController *pip = [self valueForKey:@"_pictureInPictureController"]; | ||||
|     [pip stopPictureInPicture]; | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| %hook MLPlayerPoolImpl | ||||
|  | ||||
| - (id)acquirePlayerForVideo:(MLVideo *)video playerConfig:(MLInnerTubePlayerConfig *)playerConfig stickySettings:(MLPlayerStickySettings *)stickySettings { | ||||
|     return makeAVPlayer(self, video, playerConfig, stickySettings, NO); | ||||
| } | ||||
|  | ||||
| - (id)acquirePlayerForVideo:(MLVideo *)video playerConfig:(MLInnerTubePlayerConfig *)playerConfig stickySettings:(MLPlayerStickySettings *)stickySettings latencyLogger:(id)latencyLogger { | ||||
|     return makeAVPlayer(self, video, playerConfig, stickySettings, NO); | ||||
| } | ||||
|  | ||||
| - (id)acquirePlayerForVideo:(MLVideo *)video playerConfig:(MLInnerTubePlayerConfig *)playerConfig stickySettings:(MLPlayerStickySettings *)stickySettings latencyLogger:(id)latencyLogger reloadContext:(id)reloadContext { | ||||
|     return makeAVPlayer(self, video, playerConfig, stickySettings, NO); | ||||
| } | ||||
|  | ||||
| - (MLAVPlayerLayerView *)playerViewForVideo:(MLVideo *)video playerConfig:(MLInnerTubePlayerConfig *)playerConfig { | ||||
|     MLDefaultPlayerViewFactory *factory = [self valueForKey:@"_playerViewFactory"]; | ||||
|     return [factory AVPlayerViewForVideo:video playerConfig:playerConfig]; | ||||
| } | ||||
|  | ||||
| - (BOOL)canQueuePlayerPlayVideo:(MLVideo *)video playerConfig:(MLInnerTubePlayerConfig *)playerConfig { | ||||
|     return NO; | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| %hook MLPlayerPool | ||||
|  | ||||
| - (id)acquirePlayerForVideo:(MLVideo *)video playerConfig:(MLInnerTubePlayerConfig *)playerConfig stickySettings:(MLPlayerStickySettings *)stickySettings { | ||||
|     return makeAVPlayer(self, video, playerConfig, stickySettings, YES); | ||||
| } | ||||
|  | ||||
| - (id)acquirePlayerForVideo:(MLVideo *)video playerConfig:(MLInnerTubePlayerConfig *)playerConfig stickySettings:(MLPlayerStickySettings *)stickySettings latencyLogger:(id)latencyLogger { | ||||
|     return makeAVPlayer(self, video, playerConfig, stickySettings, YES); | ||||
| } | ||||
|  | ||||
| - (MLAVPlayerLayerView *)playerViewForVideo:(MLVideo *)video playerConfig:(MLInnerTubePlayerConfig *)playerConfig { | ||||
|     MLDefaultPlayerViewFactory *factory = [self valueForKey:@"_playerViewFactory"]; | ||||
|     return [factory AVPlayerViewForVideo:video playerConfig:playerConfig]; | ||||
| } | ||||
|  | ||||
| - (BOOL)canUsePlayerView:(id)playerView forVideo:(MLVideo *)video playerConfig:(MLInnerTubePlayerConfig *)playerConfig { | ||||
|     forceRenderViewTypeBase([playerConfig hamplayerConfig]); | ||||
|     return %orig; | ||||
| } | ||||
|  | ||||
| - (BOOL)canQueuePlayerPlayVideo:(MLVideo *)video playerConfig:(MLInnerTubePlayerConfig *)playerConfig { | ||||
|     return NO; | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| %hook MLDefaultPlayerViewFactory | ||||
|  | ||||
| - (id)hamPlayerViewForVideo:(MLVideo *)video playerConfig:(MLInnerTubePlayerConfig *)playerConfig { | ||||
|     forceRenderViewType([self valueForKey:@"_hotConfig"]); | ||||
|     forceRenderViewTypeBase([playerConfig hamplayerConfig]); | ||||
|     return %orig; | ||||
| } | ||||
|  | ||||
| - (BOOL)canUsePlayerView:(id)playerView forVideo:(MLVideo *)video playerConfig:(MLInnerTubePlayerConfig *)playerConfig { | ||||
|     forceRenderViewTypeBase([playerConfig hamplayerConfig]); | ||||
|     return %orig; | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| %hook MLVideoDecoderFactory | ||||
|  | ||||
| - (void)prepareDecoderForFormatDescription:(id)formatDescription delegateQueue:(id)delegateQueue { | ||||
|     forceRenderViewTypeHot([self valueForKey:@"_hotConfig"]); | ||||
|     %orig; | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| %end | ||||
|  | ||||
| %group Compat | ||||
|  | ||||
| %hook AVPictureInPictureController | ||||
|  | ||||
| %new(v@:) | ||||
| - (void)invalidatePlaybackState {} | ||||
|  | ||||
| %new(v@:) | ||||
| - (void)sampleBufferDisplayLayerDidDisappear {} | ||||
|  | ||||
| %new(v@:) | ||||
| - (void)sampleBufferDisplayLayerDidAppear {} | ||||
|  | ||||
| %new | ||||
| - (void)sampleBufferDisplayLayerRenderSizeDidChangeToSize:(CGSize)size {} | ||||
|  | ||||
| %new | ||||
| - (void)setRequiresLinearPlayback:(BOOL)linear {} | ||||
|  | ||||
| %new(v@:) | ||||
| - (void)reloadPrerollAttributes {} | ||||
|  | ||||
| %end | ||||
|  | ||||
| %end | ||||
|  | ||||
| #pragma clang diagnostic push | ||||
| #pragma clang diagnostic ignored "-Wunguarded-availability-new" | ||||
|  | ||||
| %group AVKit_iOS14_2_Up | ||||
|  | ||||
| %hook AVPictureInPictureControllerContentSource | ||||
|  | ||||
| %property (assign) bool hasInitialRenderSize; | ||||
|  | ||||
| - (id)initWithSampleBufferDisplayLayer:(AVSampleBufferDisplayLayer *)sampleBufferDisplayLayer initialRenderSize:(CGSize)initialRenderSize playbackDelegate:(id)playbackDelegate { | ||||
|     self = %orig; | ||||
|     if (self) | ||||
|         self.hasInitialRenderSize = true; | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| %end | ||||
|  | ||||
| %group AVKit_preiOS14_2 | ||||
|  | ||||
| %hook AVPictureInPictureControllerContentSource | ||||
|  | ||||
| %property (assign) bool hasInitialRenderSize; | ||||
|  | ||||
| %new | ||||
| - (instancetype)initWithSampleBufferDisplayLayer:(AVSampleBufferDisplayLayer *)sampleBufferDisplayLayer initialRenderSize:(CGSize)initialRenderSize playbackDelegate:(id <AVPictureInPictureSampleBufferPlaybackDelegate>)playbackDelegate { | ||||
|     return [self initWithSampleBufferDisplayLayer:sampleBufferDisplayLayer playbackDelegate:playbackDelegate]; | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| %hook AVPictureInPictureController | ||||
|  | ||||
| %new | ||||
| - (void)setCanStartPictureInPictureAutomaticallyFromInline:(BOOL)canStartFromInline {} | ||||
|  | ||||
| %end | ||||
|  | ||||
| %end | ||||
|  | ||||
| #pragma clang diagnostic pop | ||||
|  | ||||
| %ctor { | ||||
|     if (!TweakEnabled()) return; | ||||
|     NSString *frameworkPath = [NSString stringWithFormat:@"%@/Frameworks/Module_Framework.framework/Module_Framework", NSBundle.mainBundle.bundlePath]; | ||||
|     NSBundle *bundle = [NSBundle bundleWithPath:frameworkPath]; | ||||
|     if (!bundle.loaded) [bundle load]; | ||||
|     MSImageRef ref = MSGetImageByName([frameworkPath UTF8String]); | ||||
|     InjectMLPIPController = MSFindSymbol(ref, "_InjectMLPIPController"); | ||||
|     if (InjectMLPIPController) { | ||||
|         InjectYTSystemNotifications = MSFindSymbol(ref, "_InjectYTSystemNotifications"); | ||||
|         InjectYTBackgroundabilityPolicy = MSFindSymbol(ref, "_InjectYTBackgroundabilityPolicy"); | ||||
|         InjectYTPlayerViewControllerConfig = MSFindSymbol(ref, "_InjectYTPlayerViewControllerConfig"); | ||||
|         InjectYTHotConfig = MSFindSymbol(ref, "_InjectYTHotConfig"); | ||||
|         %init(WithInjection); | ||||
|     } else { | ||||
|         NSString *currentVersion = [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey]; | ||||
|         hasSampleBufferPiP = isLegacyVersion = [currentVersion compare:@"15.33.4" options:NSNumericSearch] == NSOrderedDescending; | ||||
|         hasSampleBufferPiP &= IS_IOS_OR_NEWER(iOS_13_0); | ||||
|     } | ||||
|     if (!IS_IOS_OR_NEWER(iOS_14_0)) { | ||||
|         %init(Compat); | ||||
|         if (!IS_IOS_OR_NEWER(iOS_13_0)) | ||||
|             isLegacyVersion = YES; | ||||
|     } | ||||
|     if (LegacyPiP()) { | ||||
|         %init(Legacy); | ||||
|     } | ||||
|     if (!IS_IOS_OR_NEWER(iOS_14_0) || IS_IOS_OR_NEWER(iOS_15_0)) | ||||
|         return; | ||||
|     if (IS_IOS_OR_NEWER(iOS_14_2)) { | ||||
|         %init(AVKit_iOS14_2_Up); | ||||
|     } else { | ||||
|         %init(AVKit_preiOS14_2); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										15
									
								
								Tweaks/YouPiP/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								Tweaks/YouPiP/Makefile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| TARGET = iphone:clang:latest:11.0 | ||||
| ARCHS = arm64 | ||||
| PACKAGE_VERSION = 1.7.19-2 | ||||
| MIN_YOUTUBE_VERSION = 15.10.4 | ||||
|  | ||||
| EXTRA_CFLAGS = -DMIN_YOUTUBE_VERSION=$(MIN_YOUTUBE_VERSION) | ||||
|  | ||||
| include $(THEOS)/makefiles/common.mk | ||||
|  | ||||
| TWEAK_NAME = YouPiP | ||||
| $(TWEAK_NAME)_FILES = Tweak.x Settings.x LegacyPiPCompat.x | ||||
| $(TWEAK_NAME)_CFLAGS = -fobjc-arc $(EXTRA_CFLAGS) | ||||
| $(TWEAK_NAME)_FRAMEWORKS = AVFoundation AVKit UIKit | ||||
|  | ||||
| include $(THEOS_MAKE_PATH)/tweak.mk | ||||
							
								
								
									
										9
									
								
								Tweaks/YouPiP/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								Tweaks/YouPiP/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| # YouPiP | ||||
|  | ||||
| Enable native Picture-in-Picture feature for videos in YouTube app. | ||||
|  | ||||
| Activate PiP by playing the video and dismissing the app, or tapping PiP button in video tab bar or control overlay. | ||||
|  | ||||
| ## Build it yourself | ||||
|  | ||||
| Use iOS 15+ SDK and latest THEOS. | ||||
							
								
								
									
										186
									
								
								Tweaks/YouPiP/Settings.x
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								Tweaks/YouPiP/Settings.x
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,186 @@ | ||||
| #import <version.h> | ||||
| #import "Header.h" | ||||
| #import "../YouTubeHeader/YTAlertView.h" | ||||
| #import "../YouTubeHeader/YTHotConfig.h" | ||||
| #import "../YouTubeHeader/YTSettingsViewController.h" | ||||
| #import "../YouTubeHeader/YTSettingsSectionItem.h" | ||||
| #import "../YouTubeHeader/YTSettingsSectionItemManager.h" | ||||
| #import "../YouTubeHeader/YTAppSettingsSectionItemActionController.h" | ||||
|  | ||||
| #define LOC(x) [tweakBundle localizedStringForKey:x value:nil table:nil] | ||||
|  | ||||
| #define FEATURE_CUTOFF_VERSION @"16.46.5" | ||||
|  | ||||
| static const NSInteger YouPiPSection = 200; | ||||
|  | ||||
| @interface YTSettingsSectionItemManager (YouPiP) | ||||
| - (void)updateYouPiPSectionWithEntry:(id)entry; | ||||
| @end | ||||
|  | ||||
| extern BOOL TweakEnabled(); | ||||
| extern BOOL UsePiPButton(); | ||||
| extern BOOL UseTabBarPiPButton(); | ||||
| extern BOOL NoMiniPlayerPiP(); | ||||
| extern BOOL LegacyPiP(); | ||||
| extern BOOL NonBackgroundable(); | ||||
| extern BOOL FakeVersion(); | ||||
|  | ||||
| extern NSBundle *YouPiPBundle(); | ||||
|  | ||||
| NSString *currentVersion; | ||||
|  | ||||
| static NSString *YouPiPWarnVersionKey = @"YouPiPWarnVersionKey"; | ||||
|  | ||||
| %hook YTAppSettingsPresentationData | ||||
|  | ||||
| + (NSArray *)settingsCategoryOrder { | ||||
|     NSArray *order = %orig; | ||||
|     NSMutableArray *mutableOrder = [order mutableCopy]; | ||||
|     NSUInteger insertIndex = [order indexOfObject:@(1)]; | ||||
|     if (insertIndex != NSNotFound) | ||||
|         [mutableOrder insertObject:@(YouPiPSection) atIndex:insertIndex + 1]; // Add YouPiP under General (ID: 1) section | ||||
|     return mutableOrder; | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| %hook YTSettingsSectionItemManager | ||||
|  | ||||
| %new(v@:@) | ||||
| - (void)updateYouPiPSectionWithEntry:(id)entry { | ||||
|     YTSettingsViewController *delegate = [self valueForKey:@"_dataDelegate"]; | ||||
|     NSMutableArray *sectionItems = [NSMutableArray array]; | ||||
|     NSBundle *tweakBundle = YouPiPBundle(); | ||||
|     YTSettingsSectionItem *enabled = [%c(YTSettingsSectionItem) switchItemWithTitle:LOC(@"ENABLED") | ||||
|         titleDescription:LOC(@"ENABLED_DESC") | ||||
|         accessibilityIdentifier:nil | ||||
|         switchOn:TweakEnabled() | ||||
|         switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) { | ||||
|             [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:EnabledKey]; | ||||
|             return YES; | ||||
|         } | ||||
|         settingItemId:0]; | ||||
|     [sectionItems addObject:enabled]; | ||||
|     YTSettingsSectionItem *activationMethod = [%c(YTSettingsSectionItem) switchItemWithTitle:LOC(@"USE_PIP_BUTTON") | ||||
|         titleDescription:LOC(@"USE_PIP_BUTTON_DESC") | ||||
|         accessibilityIdentifier:nil | ||||
|         switchOn:UsePiPButton() | ||||
|         switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) { | ||||
|             [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:PiPActivationMethodKey]; | ||||
|             return YES; | ||||
|         } | ||||
|         settingItemId:0]; | ||||
|     [sectionItems addObject:activationMethod]; | ||||
|     YTSettingsSectionItem *activationMethod2 = [%c(YTSettingsSectionItem) switchItemWithTitle:LOC(@"USE_TAB_BAR_PIP_BUTTON") | ||||
|         titleDescription:LOC(@"USE_TAB_BAR_PIP_BUTTON_DESC") | ||||
|         accessibilityIdentifier:nil | ||||
|         switchOn:UseTabBarPiPButton() | ||||
|         switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) { | ||||
|             [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:PiPActivationMethod2Key]; | ||||
|             return YES; | ||||
|         } | ||||
|         settingItemId:0]; | ||||
|     [sectionItems addObject:activationMethod2]; | ||||
|     YTSettingsSectionItem *miniPlayer = [%c(YTSettingsSectionItem) switchItemWithTitle:LOC(@"DISABLE_PIP_MINI_PLAYER") | ||||
|         titleDescription:LOC(@"DISABLE_PIP_MINI_PLAYER_DESC") | ||||
|         accessibilityIdentifier:nil | ||||
|         switchOn:NoMiniPlayerPiP() | ||||
|         switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) { | ||||
|             [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:NoMiniPlayerPiPKey]; | ||||
|             return YES; | ||||
|         } | ||||
|         settingItemId:0]; | ||||
|     [sectionItems addObject:miniPlayer]; | ||||
|     if (IS_IOS_OR_NEWER(iOS_13_0) && [currentVersion compare:@"15.33.4" options:NSNumericSearch] == NSOrderedDescending) { | ||||
|         YTSettingsSectionItem *legacyPiP = [%c(YTSettingsSectionItem) switchItemWithTitle:LOC(@"LEGACY_PIP") | ||||
|             titleDescription:LOC(@"LEGACY_PIP_DESC") | ||||
|             accessibilityIdentifier:nil | ||||
|             switchOn:LegacyPiP() | ||||
|             switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) { | ||||
|                 [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:CompatibilityModeKey]; | ||||
|                 return YES; | ||||
|             } | ||||
|             settingItemId:0]; | ||||
|         [sectionItems addObject:legacyPiP]; | ||||
|     } | ||||
|     YTAppSettingsSectionItemActionController *sectionItemActionController = [delegate valueForKey:@"_sectionItemActionController"]; | ||||
|     YTSettingsSectionItemManager *sectionItemManager = [sectionItemActionController valueForKey:@"_sectionItemManager"]; | ||||
|     YTHotConfig *hotConfig; | ||||
|     @try { | ||||
|         hotConfig = [sectionItemManager valueForKey:@"_hotConfig"]; | ||||
|     } @catch (id ex) { | ||||
|         hotConfig = [sectionItemManager.gimme instanceForType:%c(YTHotConfig)]; | ||||
|     } | ||||
|     YTIIosMediaHotConfig *iosMediaHotConfig = hotConfig.hotConfigGroup.mediaHotConfig.iosMediaHotConfig; | ||||
|     if ([iosMediaHotConfig respondsToSelector:@selector(setEnablePipForNonBackgroundableContent:)]) { | ||||
|         YTSettingsSectionItem *nonBackgroundable = [%c(YTSettingsSectionItem) switchItemWithTitle:LOC(@"NON_BACKGROUNDABLE_PIP") | ||||
|             titleDescription:LOC(@"NON_BACKGROUNDABLE_PIP_DESC") | ||||
|             accessibilityIdentifier:nil | ||||
|             switchOn:NonBackgroundable() | ||||
|             switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) { | ||||
|                 [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:NonBackgroundableKey]; | ||||
|                 return YES; | ||||
|             } | ||||
|             settingItemId:0]; | ||||
|         [sectionItems addObject:nonBackgroundable]; | ||||
|     } | ||||
|     if ([currentVersion compare:FEATURE_CUTOFF_VERSION options:NSNumericSearch] == NSOrderedDescending) { | ||||
|         YTSettingsSectionItem *fakeVersion = [%c(YTSettingsSectionItem) switchItemWithTitle:LOC(@"FAKE_YT_VERSION") | ||||
|             titleDescription:[NSString stringWithFormat:LOC(@"FAKE_YT_VERSION_DESC"), FEATURE_CUTOFF_VERSION] | ||||
|             accessibilityIdentifier:nil | ||||
|             switchOn:FakeVersion() | ||||
|             switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) { | ||||
|                 [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:FakeVersionKey]; | ||||
|                 return YES; | ||||
|             } | ||||
|             settingItemId:0]; | ||||
|         [sectionItems addObject:fakeVersion]; | ||||
|     } | ||||
|     [delegate setSectionItems:sectionItems forCategory:YouPiPSection title:TweakName titleDescription:nil headerHidden:NO]; | ||||
| } | ||||
|  | ||||
| - (void)updateSectionForCategory:(NSUInteger)category withEntry:(id)entry { | ||||
|     if (category == YouPiPSection) { | ||||
|         [self updateYouPiPSectionWithEntry:entry]; | ||||
|         return; | ||||
|     } | ||||
|     %orig; | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| BOOL loadWatchNextRequest = NO; | ||||
|  | ||||
| %hook YTVersionUtils | ||||
|  | ||||
| + (NSString *)appVersion { | ||||
|     return FakeVersion() && loadWatchNextRequest ? FEATURE_CUTOFF_VERSION : %orig; | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| %hook YTWatchNextViewController | ||||
|  | ||||
| - (void)loadWatchNextRequest:(id)arg1 withInitialWatchNextResponse:(id)arg2 disableUnloadModel:(BOOL)arg3 { | ||||
|     loadWatchNextRequest = YES; | ||||
|     %orig; | ||||
|     loadWatchNextRequest = NO; | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| %ctor { | ||||
|     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; | ||||
|     currentVersion = [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey]; | ||||
|     if (![defaults boolForKey:YouPiPWarnVersionKey] && [currentVersion compare:@(OS_STRINGIFY(MIN_YOUTUBE_VERSION)) options:NSNumericSearch] != NSOrderedDescending) { | ||||
|         [defaults setBool:YES forKey:YouPiPWarnVersionKey]; | ||||
|         dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ | ||||
|             NSBundle *tweakBundle = YouPiPBundle(); | ||||
|             YTAlertView *alertView = [%c(YTAlertView) infoDialog]; | ||||
|             alertView.title = TweakName; | ||||
|             alertView.subtitle = [NSString stringWithFormat:LOC(@"UNSUPPORTED_YT_VERSION"), currentVersion, @(OS_STRINGIFY(MIN_YOUTUBE_VERSION))]; | ||||
|             [alertView show]; | ||||
|         }); | ||||
|     } | ||||
|     %init; | ||||
| } | ||||
							
								
								
									
										600
									
								
								Tweaks/YouPiP/Tweak.x
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										600
									
								
								Tweaks/YouPiP/Tweak.x
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,600 @@ | ||||
| #import <version.h> | ||||
| #import <rootless.h> | ||||
| #import "Header.h" | ||||
| #import "../YouTubeHeader/GIMBindingBuilder.h" | ||||
| #import "../YouTubeHeader/GPBExtensionRegistry.h" | ||||
| #import "../YouTubeHeader/MLPIPController.h" | ||||
| #import "../YouTubeHeader/MLDefaultPlayerViewFactory.h" | ||||
| #import "../YouTubeHeader/YTBackgroundabilityPolicy.h" | ||||
| #import "../YouTubeHeader/YTMainAppControlsOverlayView.h" | ||||
| #import "../YouTubeHeader/YTMainAppVideoPlayerOverlayViewController.h" | ||||
| #import "../YouTubeHeader/YTHotConfig.h" | ||||
| #import "../YouTubeHeader/YTLocalPlaybackController.h" | ||||
| #import "../YouTubeHeader/YTPlayerPIPController.h" | ||||
| #import "../YouTubeHeader/YTSettingsSectionItem.h" | ||||
| #import "../YouTubeHeader/YTSettingsSectionItemManager.h" | ||||
| #import "../YouTubeHeader/YTIPictureInPictureRendererRoot.h" | ||||
| #import "../YouTubeHeader/YTColor.h" | ||||
| #import "../YouTubeHeader/QTMIcon.h" | ||||
| #import "../YouTubeHeader/YTSlimVideoScrollableActionBarCellController.h" | ||||
| #import "../YouTubeHeader/YTSlimVideoScrollableDetailsActionsView.h" | ||||
| #import "../YouTubeHeader/YTSlimVideoDetailsActionView.h" | ||||
| #import "../YouTubeHeader/YTISlimMetadataButtonSupportedRenderers.h" | ||||
| #import "../YouTubeHeader/YTPageStyleController.h" | ||||
| #import "../YouTubeHeader/YTPlayerStatus.h" | ||||
| #import "../YouTubeHeader/YTWatchViewController.h" | ||||
|  | ||||
| #define PiPButtonType 801 | ||||
|  | ||||
| @interface YTMainAppControlsOverlayView (YP) | ||||
| @property (retain, nonatomic) YTQTMButton *pipButton; | ||||
| - (void)didPressPiP:(id)arg; | ||||
| - (UIImage *)pipImage; | ||||
| @end | ||||
|  | ||||
| BOOL FromUser = NO; | ||||
| BOOL PiPDisabled = NO; | ||||
|  | ||||
| extern BOOL LegacyPiP(); | ||||
| extern YTHotConfig *(*InjectYTHotConfig)(void); | ||||
|  | ||||
| BOOL TweakEnabled() { | ||||
|     return [[NSUserDefaults standardUserDefaults] boolForKey:EnabledKey]; | ||||
| } | ||||
|  | ||||
| BOOL UsePiPButton() { | ||||
|     return [[NSUserDefaults standardUserDefaults] boolForKey:PiPActivationMethodKey]; | ||||
| } | ||||
|  | ||||
| BOOL NoMiniPlayerPiP() { | ||||
|     return [[NSUserDefaults standardUserDefaults] boolForKey:NoMiniPlayerPiPKey]; | ||||
| } | ||||
|  | ||||
| BOOL UseTabBarPiPButton() { | ||||
|     return [[NSUserDefaults standardUserDefaults] boolForKey:PiPActivationMethod2Key]; | ||||
| } | ||||
|  | ||||
| BOOL NonBackgroundable() { | ||||
|     return [[NSUserDefaults standardUserDefaults] boolForKey:NonBackgroundableKey]; | ||||
| } | ||||
|  | ||||
| BOOL FakeVersion() { | ||||
|     return [[NSUserDefaults standardUserDefaults] boolForKey:FakeVersionKey]; | ||||
| } | ||||
|  | ||||
| BOOL isPictureInPictureActive(MLPIPController *pip) { | ||||
|     return [pip respondsToSelector:@selector(pictureInPictureActive)] ? [pip pictureInPictureActive] : [pip isPictureInPictureActive]; | ||||
| } | ||||
|  | ||||
| static NSString *PiPIconPath; | ||||
| static NSString *TabBarPiPIconPath; | ||||
| static NSString *PiPVideoPath; | ||||
|  | ||||
| static void forcePictureInPicture(YTHotConfig *hotConfig, BOOL value) { | ||||
|     [hotConfig mediaHotConfig].enablePictureInPicture = value; | ||||
|     YTIIosMediaHotConfig *iosMediaHotConfig = hotConfig.hotConfigGroup.mediaHotConfig.iosMediaHotConfig; | ||||
|     iosMediaHotConfig.enablePictureInPicture = value; | ||||
|     if ([iosMediaHotConfig respondsToSelector:@selector(setEnablePipForNonBackgroundableContent:)]) | ||||
|         iosMediaHotConfig.enablePipForNonBackgroundableContent = value && NonBackgroundable(); | ||||
|     if ([iosMediaHotConfig respondsToSelector:@selector(setEnablePipForNonPremiumUsers:)]) | ||||
|         iosMediaHotConfig.enablePipForNonPremiumUsers = value; | ||||
| } | ||||
|  | ||||
| static void activatePiPBase(YTPlayerPIPController *controller, BOOL playPiP) { | ||||
|     MLPIPController *pip = [controller valueForKey:@"_pipController"]; | ||||
|     if ([controller respondsToSelector:@selector(maybeEnablePictureInPicture)]) | ||||
|         [controller maybeEnablePictureInPicture]; | ||||
|     else if ([controller respondsToSelector:@selector(maybeInvokePictureInPicture)]) | ||||
|         [controller maybeInvokePictureInPicture]; | ||||
|     else { | ||||
|         BOOL canPiP = [controller respondsToSelector:@selector(canEnablePictureInPicture)] && [controller canEnablePictureInPicture]; | ||||
|         if (!canPiP) | ||||
|             canPiP = [controller respondsToSelector:@selector(canInvokePictureInPicture)] && [controller canInvokePictureInPicture]; | ||||
|         if (canPiP) { | ||||
|             if ([pip respondsToSelector:@selector(activatePiPController)]) | ||||
|                 [pip activatePiPController]; | ||||
|             else | ||||
|                 [pip startPictureInPicture]; | ||||
|         } | ||||
|     } | ||||
|     AVPictureInPictureController *avpip = [pip valueForKey:@"_pictureInPictureController"]; | ||||
|     if (playPiP) { | ||||
|         if ([avpip isPictureInPicturePossible]) | ||||
|             [avpip startPictureInPicture]; | ||||
|     } else { | ||||
|         if ([pip respondsToSelector:@selector(deactivatePiPController)]) | ||||
|             [pip deactivatePiPController]; | ||||
|         else | ||||
|             [avpip stopPictureInPicture]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void activatePiP(YTLocalPlaybackController *local, BOOL playPiP) { | ||||
|     if (![local isKindOfClass:%c(YTLocalPlaybackController)]) | ||||
|         return; | ||||
|     YTPlayerPIPController *controller = [local valueForKey:@"_playerPIPController"]; | ||||
|     activatePiPBase(controller, playPiP); | ||||
| } | ||||
|  | ||||
| static void bootstrapPiP(YTPlayerViewController *self, BOOL playPiP) { | ||||
|     YTHotConfig *hotConfig; | ||||
|     @try { | ||||
|         if (InjectYTHotConfig) | ||||
|             hotConfig = InjectYTHotConfig(); | ||||
|         else | ||||
|             hotConfig = [self valueForKey:@"_hotConfig"]; | ||||
|     } @catch (id ex) { | ||||
|         hotConfig = [[self gimme] instanceForType:%c(YTHotConfig)]; | ||||
|     } | ||||
|     forcePictureInPicture(hotConfig, YES); | ||||
|     YTLocalPlaybackController *local = [self valueForKey:@"_playbackController"]; | ||||
|     activatePiP(local, playPiP); | ||||
| } | ||||
|  | ||||
| #pragma mark - Video tab bar PiP Button | ||||
|  | ||||
| static YTISlimMetadataButtonSupportedRenderers *makeUnderPlayerButton(NSString *title, int iconType, NSString *browseId) { | ||||
|     YTISlimMetadataButtonSupportedRenderers *supportedRenderer = [[%c(YTISlimMetadataButtonSupportedRenderers) alloc] init]; | ||||
|     YTISlimMetadataButtonRenderer *metadataButtonRenderer = [[%c(YTISlimMetadataButtonRenderer) alloc] init]; | ||||
|     YTIButtonSupportedRenderers *buttonSupportedRenderer = [[%c(YTIButtonSupportedRenderers) alloc] init]; | ||||
|     YTIBrowseEndpoint *endPoint = [[%c(YTIBrowseEndpoint) alloc] init]; | ||||
|     YTICommand *command = [[%c(YTICommand) alloc] init]; | ||||
|     YTIButtonRenderer *button = [[%c(YTIButtonRenderer) alloc] init]; | ||||
|     YTIIcon *icon = [[%c(YTIIcon) alloc] init]; | ||||
|     endPoint.browseId = browseId; | ||||
|     command.browseEndpoint = endPoint; | ||||
|     icon.iconType = iconType; | ||||
|     button.style = 8; // Opacity style | ||||
|     button.tooltip = title; | ||||
|     button.size = 1; // Default size | ||||
|     button.isDisabled = NO; | ||||
|     button.text = [%c(YTIFormattedString) formattedStringWithString:title]; | ||||
|     button.icon = icon; | ||||
|     button.navigationEndpoint = command; | ||||
|     buttonSupportedRenderer.buttonRenderer = button; | ||||
|     metadataButtonRenderer.button = buttonSupportedRenderer; | ||||
|     supportedRenderer.slimMetadataButtonRenderer = metadataButtonRenderer; | ||||
|     return supportedRenderer; | ||||
| } | ||||
|  | ||||
| %hook YTIIcon | ||||
|  | ||||
| - (UIImage *)iconImageWithColor:(UIColor *)color { | ||||
|     if (self.iconType == PiPButtonType) { | ||||
|         UIImage *image = [%c(QTMIcon) tintImage:[UIImage imageWithContentsOfFile:TabBarPiPIconPath] color:[[%c(YTPageStyleController) currentColorPalette] textPrimary]]; | ||||
|         if ([image respondsToSelector:@selector(imageFlippedForRightToLeftLayoutDirection)]) | ||||
|             image = [image imageFlippedForRightToLeftLayoutDirection]; | ||||
|         return image; | ||||
|     } | ||||
|     return %orig; | ||||
| } | ||||
| %end | ||||
|  | ||||
| %hook YTSlimVideoScrollableDetailsActionsView | ||||
|  | ||||
| - (void)createActionViewsFromSupportedRenderers:(NSMutableArray *)renderers { // for old YouTube version | ||||
|     if (UseTabBarPiPButton()) { | ||||
|         YTISlimMetadataButtonSupportedRenderers *PiPButton = makeUnderPlayerButton(@"PiP", PiPButtonType, @"YouPiP.pip.command"); | ||||
|         if (![renderers containsObject:PiPButton]) | ||||
|             [renderers addObject:PiPButton]; | ||||
|     } | ||||
|     %orig; | ||||
| } | ||||
|  | ||||
| - (void)createActionViewsFromSupportedRenderers:(NSMutableArray *)renderers withElementsContextBlock:(id)arg2 { | ||||
|     if (UseTabBarPiPButton()) { | ||||
|         YTISlimMetadataButtonSupportedRenderers *PiPButton = makeUnderPlayerButton(@"PiP", PiPButtonType, @"YouPiP.pip.command"); | ||||
|         if (![renderers containsObject:PiPButton]) | ||||
|             [renderers addObject:PiPButton]; | ||||
|     } | ||||
|     %orig; | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| %hook YTSlimVideoDetailsActionView | ||||
|  | ||||
| - (void)didTapButton:(id)arg1 { | ||||
|     if ([self.label.attributedText.string isEqualToString:@"PiP"]) { | ||||
|         YTSlimVideoScrollableActionBarCellController *_delegate = self.delegate; | ||||
|         YTPlayerViewController *playerViewController = nil; | ||||
|         @try { | ||||
|             if ([[_delegate valueForKey:@"_metadataPanelStateProvider"] isKindOfClass:%c(YTWatchController)]) { | ||||
|                 id provider = [_delegate valueForKey:@"_metadataPanelStateProvider"]; | ||||
|                 @try { | ||||
|                     YTWatchViewController *watchViewController = [provider valueForKey:@"_watchViewController"]; | ||||
|                     playerViewController = [watchViewController valueForKey:@"_playerViewController"]; | ||||
|                 } @catch (id ex) { | ||||
|                     playerViewController = [provider valueForKey:@"_playerViewController"]; | ||||
|                 } | ||||
|             } | ||||
|         } @catch (id ex) { // for old YouTube version | ||||
|             if ([[_delegate valueForKey:@"_ngwMetadataPanelStateProvider"] isKindOfClass:%c(YTNGWatchController)]) { | ||||
|                 id provider = [_delegate valueForKey:@"_ngwMetadataPanelStateProvider"]; | ||||
|                 playerViewController = [provider valueForKey:@"_playerViewController"]; | ||||
|             } | ||||
|         } | ||||
|         if (playerViewController && [playerViewController isKindOfClass:%c(YTPlayerViewController)]) { | ||||
|             FromUser = YES; | ||||
|             bootstrapPiP(playerViewController, YES); | ||||
|         } | ||||
|         return; | ||||
|     } | ||||
|     %orig; | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| #pragma mark - Overlay PiP Button | ||||
|  | ||||
| %hook YTMainAppVideoPlayerOverlayViewController | ||||
|  | ||||
| - (void)updateTopRightButtonAvailability { | ||||
|     %orig; | ||||
|     YTMainAppVideoPlayerOverlayView *v = [self videoPlayerOverlayView]; | ||||
|     YTMainAppControlsOverlayView *c = [v valueForKey:@"_controlsOverlayView"]; | ||||
|     c.pipButton.hidden = !UsePiPButton(); | ||||
|     [c setNeedsLayout]; | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| static void createPiPButton(YTMainAppControlsOverlayView *self) { | ||||
|     if (self) { | ||||
|         CGFloat padding = [[self class] topButtonAdditionalPadding]; | ||||
|         UIImage *image = [self pipImage]; | ||||
|         self.pipButton = [self buttonWithImage:image accessibilityLabel:@"pip" verticalContentPadding:padding]; | ||||
|         self.pipButton.hidden = YES; | ||||
|         self.pipButton.alpha = 0; | ||||
|         [self.pipButton addTarget:self action:@selector(didPressPiP:) forControlEvents:UIControlEventTouchUpInside]; | ||||
|         @try { | ||||
|             [[self valueForKey:@"_topControlsAccessibilityContainerView"] addSubview:self.pipButton]; | ||||
|         } @catch (id ex) { | ||||
|             [self addSubview:self.pipButton]; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| static NSMutableArray *topControls(YTMainAppControlsOverlayView *self, NSMutableArray *controls) { | ||||
|     if (UsePiPButton()) | ||||
|         [controls insertObject:self.pipButton atIndex:0]; | ||||
|     return controls; | ||||
| } | ||||
|  | ||||
| %hook YTMainAppControlsOverlayView | ||||
|  | ||||
| %property (retain, nonatomic) YTQTMButton *pipButton; | ||||
|  | ||||
| - (id)initWithDelegate:(id)delegate { | ||||
|     self = %orig; | ||||
|     createPiPButton(self); | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| - (id)initWithDelegate:(id)delegate autoplaySwitchEnabled:(BOOL)autoplaySwitchEnabled { | ||||
|     self = %orig; | ||||
|     createPiPButton(self); | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| - (NSMutableArray *)topButtonControls { | ||||
|     return topControls(self, %orig); | ||||
| } | ||||
|  | ||||
| - (NSMutableArray *)topControls { | ||||
|     return topControls(self, %orig); | ||||
| } | ||||
|  | ||||
| - (void)setTopOverlayVisible:(BOOL)visible isAutonavCanceledState:(BOOL)canceledState { | ||||
|     if (UsePiPButton()) | ||||
|         self.pipButton.alpha = canceledState || !visible ? 0.0 : 1.0; | ||||
|     %orig; | ||||
| } | ||||
|  | ||||
| %new(@:) | ||||
| - (UIImage *)pipImage { | ||||
|     static UIImage *image = nil; | ||||
|     static dispatch_once_t onceToken; | ||||
|     dispatch_once(&onceToken, ^{ | ||||
|         UIColor *color = [%c(YTColor) white1]; | ||||
|         image = [%c(QTMIcon) tintImage:[UIImage imageWithContentsOfFile:PiPIconPath] color:color]; | ||||
|         if ([image respondsToSelector:@selector(imageFlippedForRightToLeftLayoutDirection)]) | ||||
|             image = [image imageFlippedForRightToLeftLayoutDirection]; | ||||
|     }); | ||||
|     return image; | ||||
| } | ||||
|  | ||||
| %new(v@:@) | ||||
| - (void)didPressPiP:(id)arg { | ||||
|     YTMainAppVideoPlayerOverlayViewController *c = [self valueForKey:@"_eventsDelegate"]; | ||||
|     FromUser = YES; | ||||
|     bootstrapPiP([c delegate], YES); | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| #pragma mark - PiP Support | ||||
|  | ||||
| %hook AVPictureInPictureController | ||||
|  | ||||
| + (BOOL)isPictureInPictureSupported { | ||||
|     return YES; | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| %hook AVPlayerController | ||||
|  | ||||
| - (BOOL)isPictureInPictureSupported { | ||||
|     return YES; | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| %hook AVSampleBufferDisplayLayerPlayerController | ||||
|  | ||||
| - (void)setPictureInPictureAvailable:(BOOL)available { | ||||
|     %orig(YES); | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| %hook MLPIPController | ||||
|  | ||||
| - (void)activatePiPController { | ||||
|     %orig; | ||||
|     if (!IS_IOS_OR_NEWER(iOS_15_0) && !LegacyPiP()) { | ||||
|         MLHAMSBDLSampleBufferRenderingView *view = [self valueForKey:@"_HAMPlayerView"]; | ||||
|         CGSize size = [self renderSizeForView:view]; | ||||
|         AVPictureInPictureController *avpip = [self valueForKey:@"_pictureInPictureController"]; | ||||
|         [avpip sampleBufferDisplayLayerRenderSizeDidChangeToSize:size]; | ||||
|         [avpip sampleBufferDisplayLayerDidAppear]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (BOOL)isPictureInPictureSupported { | ||||
|     return YES; | ||||
| } | ||||
|  | ||||
| %new(v@:@) | ||||
| - (BOOL)pictureInPictureControllerPlaybackPaused:(AVPictureInPictureController *)pictureInPictureController { | ||||
|     return [self pictureInPictureControllerIsPlaybackPaused:pictureInPictureController]; | ||||
| } | ||||
|  | ||||
| %new(v@:@) | ||||
| - (void)pictureInPictureControllerStartPlayback:(id)arg1 { | ||||
|     [self pictureInPictureControllerStartPlayback]; | ||||
| } | ||||
|  | ||||
| %new(v@:@) | ||||
| - (void)pictureInPictureControllerStopPlayback:(id)arg1 { | ||||
|     [self pictureInPictureControllerStopPlayback]; | ||||
| } | ||||
|  | ||||
| %new | ||||
| - (void)renderingViewSampleBufferFrameSizeDidChange:(CGSize)size { | ||||
|     if (!IS_IOS_OR_NEWER(iOS_15_0) && size.width && size.height) { | ||||
|         AVPictureInPictureController *avpip = [self valueForKey:@"_pictureInPictureController"]; | ||||
|         [avpip sampleBufferDisplayLayerRenderSizeDidChangeToSize:size]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| %new(v@:@) | ||||
| - (void)appWillEnterForeground:(id)arg1 { | ||||
|     if (!IS_IOS_OR_NEWER(iOS_15_0) && !LegacyPiP()) { | ||||
|         AVPictureInPictureController *avpip = [self valueForKey:@"_pictureInPictureController"]; | ||||
|         [avpip sampleBufferDisplayLayerDidAppear]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| %new(v@:@) | ||||
| - (void)appWillEnterBackground:(id)arg1 { | ||||
|     if (!IS_IOS_OR_NEWER(iOS_15_0) && !LegacyPiP()) { | ||||
|         AVPictureInPictureController *avpip = [self valueForKey:@"_pictureInPictureController"]; | ||||
|         [avpip sampleBufferDisplayLayerDidDisappear]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| %hook MLDefaultPlayerViewFactory | ||||
|  | ||||
| - (MLAVPlayerLayerView *)AVPlayerViewForVideo:(MLVideo *)video playerConfig:(MLInnerTubePlayerConfig *)playerConfig { | ||||
|     forcePictureInPicture([self valueForKey:@"_hotConfig"], YES); | ||||
|     return %orig; | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| #pragma mark - PiP Support, Backgroundable | ||||
|  | ||||
| %hook YTIHamplayerConfig | ||||
|  | ||||
| - (BOOL)enableBackgroundable { | ||||
|     return YES; | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| %hook YTIBackgroundOfflineSettingCategoryEntryRenderer | ||||
|  | ||||
| - (BOOL)isBackgroundEnabled { | ||||
|     return YES; | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| %hook YTBackgroundabilityPolicy | ||||
|  | ||||
| - (void)updateIsBackgroundableByUserSettings { | ||||
|     %orig; | ||||
|     [self setValue:@(YES) forKey:@"_backgroundableByUserSettings"]; | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| %hook YTSettingsSectionItemManager | ||||
|  | ||||
| - (YTSettingsSectionItem *)pictureInPictureSectionItem { | ||||
|     forcePictureInPicture([self valueForKey:@"_hotConfig"], YES); | ||||
|     return %orig; | ||||
| } | ||||
|  | ||||
| - (YTSettingsSectionItem *)pictureInPictureSectionItem:(id)arg1 { | ||||
|     forcePictureInPicture([self valueForKey:@"_hotConfig"], YES); | ||||
|     return %orig; | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| #pragma mark - Hacks | ||||
|  | ||||
| BOOL YTSingleVideo_isLivePlayback_override = NO; | ||||
|  | ||||
| %hook YTSingleVideo | ||||
|  | ||||
| - (BOOL)isLivePlayback { | ||||
|     return YTSingleVideo_isLivePlayback_override ? NO : %orig; | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| static YTHotConfig *getHotConfig(YTPlayerPIPController *self) { | ||||
|     @try { | ||||
|         return [self valueForKey:@"_hotConfig"]; | ||||
|     } @catch (id ex) { | ||||
|         return [[self valueForKey:@"_config"] valueForKey:@"_hotConfig"]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| %hook YTPlayerPIPController | ||||
|  | ||||
| - (BOOL)canInvokePictureInPicture { | ||||
|     forcePictureInPicture(getHotConfig(self), YES); | ||||
|     YTSingleVideo_isLivePlayback_override = YES; | ||||
|     BOOL value = %orig; | ||||
|     YTSingleVideo_isLivePlayback_override = NO; | ||||
|     return value; | ||||
| } | ||||
|  | ||||
| - (BOOL)canEnablePictureInPicture { | ||||
|     forcePictureInPicture(getHotConfig(self), YES); | ||||
|     YTSingleVideo_isLivePlayback_override = YES; | ||||
|     BOOL value = %orig; | ||||
|     YTSingleVideo_isLivePlayback_override = NO; | ||||
|     return value; | ||||
| } | ||||
|  | ||||
| - (void)didStopPictureInPicture { | ||||
|     FromUser = NO; | ||||
|     %orig; | ||||
| } | ||||
|  | ||||
| - (void)appWillResignActive:(id)arg1 { | ||||
|     // If PiP button on, PiP doesn't activate on app resign unless it's from user | ||||
|     BOOL hasPiPButton = UsePiPButton() || UseTabBarPiPButton(); | ||||
|     BOOL disablePiP = hasPiPButton && !FromUser; | ||||
|     if (disablePiP) { | ||||
|         MLPIPController *pip = [self valueForKey:@"_pipController"]; | ||||
|         [pip setValue:nil forKey:@"_pictureInPictureController"]; | ||||
|     } else { | ||||
|         if (LegacyPiP()) | ||||
|             activatePiPBase(self, YES); | ||||
|         %orig; | ||||
|     } | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| %hook YTSingleVideoController | ||||
|  | ||||
| - (void)playerStatusDidChange:(YTPlayerStatus *)playerStatus { | ||||
|     %orig; | ||||
|     PiPDisabled = NoMiniPlayerPiP() && playerStatus.visibility == 1; | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| %hook AVPictureInPicturePlatformAdapter | ||||
|  | ||||
| - (BOOL)isSystemPictureInPicturePossible { | ||||
|     return PiPDisabled ? NO : %orig; | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| %hook YTIPlayabilityStatus | ||||
|  | ||||
| - (BOOL)isPlayableInBackground { | ||||
|     return YES; | ||||
| } | ||||
|  | ||||
| - (BOOL)isPlayableInPictureInPicture { | ||||
|     return YES; | ||||
| } | ||||
|  | ||||
| - (BOOL)hasPictureInPicture { | ||||
|     return YES; | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| #pragma mark - PiP Support, Binding | ||||
|  | ||||
| %hook YTAppModule | ||||
|  | ||||
| - (void)configureWithBinder:(GIMBindingBuilder *)binder { | ||||
|     %orig; | ||||
|     [[binder bindType:%c(MLPIPController)] initializedWith:^(id a) { | ||||
|         MLPIPController *pip = [%c(MLPIPController) alloc]; | ||||
|         if ([pip respondsToSelector:@selector(initWithPlaceholderPlayerItemResourcePath:)]) | ||||
|             pip = [pip initWithPlaceholderPlayerItemResourcePath:PiPVideoPath]; | ||||
|         else if ([pip respondsToSelector:@selector(initWithPlaceholderPlayerItem:)]) | ||||
|             pip = [pip initWithPlaceholderPlayerItem:[AVPlayerItem playerItemWithURL:[NSURL URLWithString:PiPVideoPath]]]; | ||||
|         return pip; | ||||
|     }]; | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| %hook YTIInnertubeResourcesIosRoot | ||||
|  | ||||
| - (GPBExtensionRegistry *)extensionRegistry { | ||||
|     GPBExtensionRegistry *registry = %orig; | ||||
|     [registry addExtension:[%c(YTIPictureInPictureRendererRoot) pictureInPictureRenderer]]; | ||||
|     return registry; | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| %hook GoogleGlobalExtensionRegistry | ||||
|  | ||||
| - (GPBExtensionRegistry *)extensionRegistry { | ||||
|     GPBExtensionRegistry *registry = %orig; | ||||
|     [registry addExtension:[%c(YTIPictureInPictureRendererRoot) pictureInPictureRenderer]]; | ||||
|     return registry; | ||||
| } | ||||
|  | ||||
| %end | ||||
|  | ||||
| NSBundle *YouPiPBundle() { | ||||
|     static NSBundle *bundle = nil; | ||||
|     static dispatch_once_t onceToken; | ||||
| 	dispatch_once(&onceToken, ^{ | ||||
|         NSString *tweakBundlePath = [[NSBundle mainBundle] pathForResource:@"YouPiP" ofType:@"bundle"]; | ||||
|         if (tweakBundlePath) | ||||
|             bundle = [NSBundle bundleWithPath:tweakBundlePath]; | ||||
|         else | ||||
|             bundle = [NSBundle bundleWithPath:ROOT_PATH_NS(@"/Library/Application Support/YouPiP.bundle")]; | ||||
|     }); | ||||
|     return bundle; | ||||
| } | ||||
|  | ||||
| %ctor { | ||||
|     if (!TweakEnabled()) return; | ||||
|     NSBundle *tweakBundle = YouPiPBundle(); | ||||
|     PiPVideoPath = [tweakBundle pathForResource:@"PiPPlaceholderAsset" ofType:@"mp4"]; | ||||
|     PiPIconPath = [tweakBundle pathForResource:@"yt-pip-overlay" ofType:@"png"]; | ||||
|     TabBarPiPIconPath = [tweakBundle pathForResource:@"yt-pip-tabbar" ofType:@"png"]; | ||||
|     %init; | ||||
| } | ||||
							
								
								
									
										1
									
								
								Tweaks/YouPiP/YouPiP.plist
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								Tweaks/YouPiP/YouPiP.plist
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| { Filter = { Bundles = ( "com.google.ios.youtube" ); }; } | ||||
							
								
								
									
										14
									
								
								Tweaks/YouPiP/control
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								Tweaks/YouPiP/control
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| Package: com.ps.youpip | ||||
| Name: YouPiP | ||||
| Depends: mobilesubstrate, firmware (>= 11.0), firmware (>= 14.0) | gsc.ipad | com.ps.forceinpicture | com.cabralcole.forceinpicture | ||||
| Version: 1.0.0 | ||||
| Architecture: iphoneos-arm | ||||
| Description: Enable native PiP in YouTube app. | ||||
| Maintainer: PoomSmart | ||||
| Author: PoomSmart | ||||
| Section: Tweaks | ||||
| Replaces: com.spicat.youpip | ||||
| Provides: com.spicat.youpip | ||||
| Conflicts: com.spicat.youpip | ||||
| Depiction: https://poomsmart.github.io/repo/depictions/youpip.html | ||||
| SileoDepiction: https://poomsmart.github.io/repo/sileodepictions/youpip.json | ||||
							
								
								
									
										3
									
								
								Tweaks/YouPiP/layout/DEBIAN/postinst
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										3
									
								
								Tweaks/YouPiP/layout/DEBIAN/postinst
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| #!/bin/bash | ||||
|  | ||||
| killall -9 YouTube || true | ||||
							
								
								
									
										3
									
								
								Tweaks/YouPiP/layout/DEBIAN/postrm
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										3
									
								
								Tweaks/YouPiP/layout/DEBIAN/postrm
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| #!/bin/bash | ||||
|  | ||||
| killall -9 YouTube || true | ||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -0,0 +1,25 @@ | ||||
| // Settings | ||||
|  | ||||
| "ENABLED" = "مفعَّل"; | ||||
| "ENABLED_DESC" = "يجب إعادة تشغيل التطبيق."; | ||||
|  | ||||
| "USE_PIP_BUTTON" = "استخدم زر PiP"; | ||||
| "USE_PIP_BUTTON_DESC" = "إضافة زر PiP مع الأزرار على الفيديو لتفعيل PiP عند الخروج من التطبيق."; | ||||
|  | ||||
| "USE_TAB_BAR_PIP_BUTTON" = "إضافة زر PiP أسفل الفيديو"; | ||||
| "USE_TAB_BAR_PIP_BUTTON_DESC" = "إضافة زر PiP مع شريط الأزرار أسفل الفيديو لتفعيل PiP عند الخروج من التطبيق. يجب إعادة تشغيل التطبيق لتفعيل الميزة."; | ||||
|  | ||||
| "DISABLE_PIP_MINI_PLAYER" = "منع PiP في المشغّل المصغر"; | ||||
| "DISABLE_PIP_MINI_PLAYER_DESC" = "منع PiP من العمل أثناء تشغيل فيديو عبرالمشغّل المصغّر."; | ||||
|  | ||||
| "LEGACY_PIP" = "نمط PiP القديم"; | ||||
| "LEGACY_PIP_DESC" = "استخدام AVPlayerLayer لـ PiP. النمط هذا يصلح مشكلة التسريع ولكن قد يمنع خيارات UHD (2K/4K) من جميع الفيديوهات. إعادة تشغيل التطبيق مطلوبة لتفعيل هذا النمط."; | ||||
|  | ||||
| "NON_BACKGROUNDABLE_PIP" = "PiP للفيديوهات التي لا تعمل في الخلفية"; | ||||
| "NON_BACKGROUNDABLE_PIP_DESC" = "تفعيل PiP للفيديوهات التي لا تعمل في الخلفية."; | ||||
|  | ||||
| "FAKE_YT_VERSION" = "إصدار يوتيوب مزيف"; | ||||
| "FAKE_YT_VERSION_DESC" = "تغيير إصدار اليوتيوب إلى %@ لكي يظهر زر PiP أسفل الفيديو."; | ||||
|  | ||||
| // Compatibility warning | ||||
| "UNSUPPORTED_YT_VERSION" = "إصدار يوتيوب رقم %@ لم يتم اختباره، وقد لا يكون مدعوماً من YouPiP. رجاءً قم بتحديث يوتيوب إلى إصدار %@ على الأقل."; | ||||
| @@ -0,0 +1,25 @@ | ||||
| // Settings | ||||
|  | ||||
| "ENABLED" = "Aktiviert"; | ||||
| "ENABLED_DESC" = "App-Neustart erforderlich!"; | ||||
|  | ||||
| "USE_PIP_BUTTON" = "PiP-Taste im Video anzeigen"; | ||||
| "USE_PIP_BUTTON_DESC" = "Fügt im Videoplayer eine PiP-Taste hinzu, um PiP auch ohne verlassen der App zu verwenden."; | ||||
|  | ||||
| "USE_TAB_BAR_PIP_BUTTON" = "PiP-Taste unter dem Video anzeigen"; | ||||
| "USE_TAB_BAR_PIP_BUTTON_DESC" = "Fügt unter dem Video eine PiP-Taste hinzu, um PiP auch ohne verlassen der App zu verwenden. App-Neustart erforderlich!"; | ||||
|  | ||||
| "DISABLE_PIP_MINI_PLAYER" = "PiP für Miniplayer deaktivieren"; | ||||
| "DISABLE_PIP_MINI_PLAYER_DESC" = "Deaktiviert PiP während videos im Miniplayer laufen."; | ||||
|  | ||||
| "LEGACY_PIP" = "Legacy PiP"; | ||||
| "LEGACY_PIP_DESC" = "Verwendet AVPlayerLayer für PiP. Dies behebt das Geschwindigkeitsproblem, entfernt aber auch die UHD-Optionen (2K/4K) aus allen Videos. App-Neustart erforderlich!"; | ||||
|  | ||||
| "NON_BACKGROUNDABLE_PIP" = "Nicht-hintergrundfähiges PiP"; | ||||
| "NON_BACKGROUNDABLE_PIP_DESC" = "Aktiviert PiP für nicht im Hintergrund laufende Videos."; | ||||
|  | ||||
| "FAKE_YT_VERSION" = "Fake YouTube-Version"; | ||||
| "FAKE_YT_VERSION_DESC" = "Setzt die YouTube-Version auf %@, sodass die PiP-Taste unter dem Videoplayer eventuell angezeigt wird."; | ||||
|  | ||||
| // Compatibility warning | ||||
| "UNSUPPORTED_YT_VERSION" = "YouTube-Version %@ wurde nicht getestet und wird eventuell nicht von YouPiP unterstützt, bitte upgrade YouTube mindestens zur Version %@"; | ||||
| @@ -0,0 +1,25 @@ | ||||
| // Settings | ||||
|  | ||||
| "ENABLED" = "Enabled"; | ||||
| "ENABLED_DESC" = "App restart is required."; | ||||
|  | ||||
| "USE_PIP_BUTTON" = "Use PiP Button"; | ||||
| "USE_PIP_BUTTON_DESC" = "Adds a PiP button over the video control overlay to activate PiP instead of dismissing the app."; | ||||
|  | ||||
| "USE_TAB_BAR_PIP_BUTTON" = "Use Video Tab Bar PiP Button"; | ||||
| "USE_TAB_BAR_PIP_BUTTON_DESC" = "Adds a PiP button in video tab bar to activate PiP instead of dismissing the app. App restart is required."; | ||||
|  | ||||
| "DISABLE_PIP_MINI_PLAYER" = "Disable PiP for Mini Player"; | ||||
| "DISABLE_PIP_MINI_PLAYER_DESC" = "Disables PiP while playing a video in the mini player."; | ||||
|  | ||||
| "LEGACY_PIP" = "Legacy PiP"; | ||||
| "LEGACY_PIP_DESC" = "Uses AVPlayerLayer for PiP. This fixes speedup bug but may remove UHD options (2K/4K) from any videos. App restart is required."; | ||||
|  | ||||
| "NON_BACKGROUNDABLE_PIP" = "Non-backgroundable PiP"; | ||||
| "NON_BACKGROUNDABLE_PIP_DESC" = "Enables PiP for non-backgroundable video."; | ||||
|  | ||||
| "FAKE_YT_VERSION" = "Fake YouTube version"; | ||||
| "FAKE_YT_VERSION_DESC" = "Set YouTube version to %@ so that PiP button under video player may show."; | ||||
|  | ||||
| // Compatibility warning | ||||
| "UNSUPPORTED_YT_VERSION" = "YouTube version %@ is not tested and may not be supported by YouPiP, please upgrade YouTube to at least version %@"; | ||||
| @@ -0,0 +1,25 @@ | ||||
| // Settings | ||||
|  | ||||
| "ENABLED" = "Activado"; | ||||
| "ENABLED_DESC" = "Es necesario reiniciar la aplicación"; | ||||
|  | ||||
| "USE_PIP_BUTTON" = "Utilizar botón PiP"; | ||||
| "USE_PIP_BUTTON_DESC" = "Añade un botón PiP sobre la superposición de control de vídeo para activar PiP en lugar de cerrar la aplicación"; | ||||
|  | ||||
| "USE_TAB_BAR_PIP_BUTTON" = "Utiliza el botón PiP de la barra de pestañas de vídeo"; | ||||
| "USE_TAB_BAR_PIP_BUTTON_DESC" = "Añade un botón PiP en la barra de pestañas de vídeo para activar PiP en lugar de cerrar la aplicación. Es necesario reiniciar la aplicación"; | ||||
|  | ||||
| "DISABLE_PIP_MINI_PLAYER" = "Desactiva PiP para Mini Player"; | ||||
| "DISABLE_PIP_MINI_PLAYER_DESC" = "Desactiva PiP mientras se reproduce un vídeo en el minirreproductor"; | ||||
|  | ||||
| "LEGACY_PIP" = "PiP heredado"; | ||||
| "LEGACY_PIP_DESC" = "Utiliza AVPlayerLayer para PiP. Esto corrige el error de velocidad, pero puede eliminar las opciones UHD (2K/4K) de cualquier vídeo. Es necesario reiniciar la aplicación"; | ||||
|  | ||||
| "NON_BACKGROUNDABLE_PIP" = "PiP sin segundo plano"; | ||||
| "NON_BACKGROUNDABLE_PIP_DESC" = "Habilita PiP para videos sin segundo plano."; | ||||
|  | ||||
| "FAKE_YT_VERSION" = "Versión falsa de YouTube"; | ||||
| "FAKE_YT_VERSION_DESC" = "Establece la versión de YouTube en %@ para que se muestre el botón PiP bajo el reproductor de vídeo"; | ||||
|  | ||||
| // Compatibility warning | ||||
| "UNSUPPORTED_YT_VERSION" = "La versión %@ de YouTube no ha sido probada y puede no ser compatible con YouPiP, por favor actualice YouTube al menos a la versión %@"; | ||||
| @@ -0,0 +1,25 @@ | ||||
| // Settings | ||||
|  | ||||
| "ENABLED" = "Enabled"; | ||||
| "ENABLED_DESC" = "Le redémarrage de l'application est nécessaire."; | ||||
|  | ||||
| "USE_PIP_BUTTON" = "Utiliser le bouton PiP"; | ||||
| "USE_PIP_BUTTON_DESC" = "Ajoute un bouton PiP sur la superposition de contrôle vidéo pour activer le PiP au lieu de quitter l'application"; | ||||
|  | ||||
| "USE_TAB_BAR_PIP_BUTTON" = "Utilise le bouton PiP de la barre d'onglets vidéo"; | ||||
| "USE_TAB_BAR_PIP_BUTTON_DESC" = "Ajoute un bouton PiP dans la barre d'onglets vidéo pour activer le PiP au lieu de quitter l'application. Le redémarrage de l'application est nécessaire."; | ||||
|  | ||||
| "DISABLE_PIP_MINI_PLAYER" = "Désactive le PiP pour le Mini Player"; | ||||
| "DISABLE_PIP_MINI_PLAYER_DESC" = "Désactive le PiP pendant la lecture d'une vidéo dans le mini-lecteur"; | ||||
|  | ||||
| "LEGACY_PIP" = "PiP hérité"; | ||||
| "LEGACY_PIP_DESC" = "Utilise AVPlayerLayer pour le PiP. Cela corrige le bug d'accélération mais supprime également les options UHD (2K/4K) de toutes les vidéos. Un redémarrage de l'application est nécessaire."; | ||||
|  | ||||
| "NON_BACKGROUNDABLE_PIP" = "PiP non rétroactif"; | ||||
| "NON_BACKGROUNDABLE_PIP_DESC" = "Active le PiP pour les vidéos qui ne sont pas en arrière-plan"; | ||||
|  | ||||
| "FAKE_YT_VERSION" = "Fausse version YouTube"; | ||||
| "FAKE_YT_VERSION_DESC" = "Définir la version de YouTube à %@ pour que le bouton PiP sous le lecteur vidéo puisse s'afficher"; | ||||
|  | ||||
| // Compatibility warning | ||||
| "UNSUPPORTED_YT_VERSION" = "La version %@ de YouTube n'a pas été testée et peut ne pas être prise en charge par YouPiP, veuillez mettre à jour YouTube vers au moins la version %@"; | ||||
| @@ -0,0 +1,25 @@ | ||||
| // Settings | ||||
|  | ||||
| "ENABLED" = "Engedélyezve"; | ||||
| "ENABLED_DESC" = "Az alkalmazás újraindítása szükséges."; | ||||
|  | ||||
| "USE_PIP_BUTTON" = "Használja a PiP gombot"; | ||||
| "USE_PIP_BUTTON_DESC" = "Adjon hozzá egy PiP gombot a videovezérlő fedvényéhez, hogy aktiválja a kép a képben funkciót az alkalmazás elvetése helyett."; | ||||
|  | ||||
| "USE_TAB_BAR_PIP_BUTTON" = "Használja a Video Tab Bar PiP gombot"; | ||||
| "USE_TAB_BAR_PIP_BUTTON_DESC" = "Hozzáad egy PiP gombot a videó lap sávjához a kép a képben funkció aktiválásához az alkalmazás elvetése helyett. Az alkalmazás újraindítása szükséges."; | ||||
|  | ||||
| "DISABLE_PIP_MINI_PLAYER" = "A PiP letiltása a Mini Player számára"; | ||||
| "DISABLE_PIP_MINI_PLAYER_DESC" = "Letiltja a PiP-t videó lejátszása közben a minilejátszóban."; | ||||
|  | ||||
| "LEGACY_PIP" = "Legacy PiP"; | ||||
| "LEGACY_PIP_DESC" = "AVPlayerLayert használ a képben. Ez javítja a gyorsítási hibát, de eltávolíthatja az UHD opciókat (2K/4K) minden videóból. Az alkalmazás újraindítása szükséges."; | ||||
|  | ||||
| "NON_BACKGROUNDABLE_PIP" = "Háttérben nem használható PiP"; | ||||
| "NON_BACKGROUNDABLE_PIP_DESC" = "Engedélyezi a PiP-t a nem háttérrel ellátott videókhoz."; | ||||
|  | ||||
| "FAKE_YT_VERSION" = "Hamis YouTube-verzió"; | ||||
| "FAKE_YT_VERSION_DESC" = "Állítsa a YouTube verziót %@ értékre, hogy megjelenjen a videólejátszó alatti PiP gomb."; | ||||
|  | ||||
| // Compatibility warning | ||||
| "UNSUPPORTED_YT_VERSION" = "A YouTube %@ verziója nincs tesztelve, és előfordulhat, hogy a YouPiP nem támogatja. Kérjük, frissítse a YouTube-ot legalább %@ verzióra"; | ||||
| @@ -0,0 +1,25 @@ | ||||
| // Settings | ||||
|  | ||||
| "ENABLED" = "Enabled"; | ||||
| "ENABLED_DESC" = "È richiesto un riavvio dell'app."; | ||||
|  | ||||
| "USE_PIP_BUTTON" = "Usa Bottone PiP"; | ||||
| "USE_PIP_BUTTON_DESC" = "Aggiunge un bottone PiP tra i controlli in sovrimpressione al video per attivare il PiP invece di chiudere l'app."; | ||||
|  | ||||
| "USE_TAB_BAR_PIP_BUTTON" = "Usa Bottone PiP nella Barra dei Tab"; | ||||
| "USE_TAB_BAR_PIP_BUTTON_DESC" = "Aggiunge un bottone PiP nella Barra dei Tab per attivare il PiP invece di chiudere l'app. È richiesto un riavvio dell'app."; | ||||
|  | ||||
| "DISABLE_PIP_MINI_PLAYER" = "Disattiva PiP per Mini Player"; | ||||
| "DISABLE_PIP_MINI_PLAYER_DESC" = "Disattiva PiP durante la riproduzione nel mini player."; | ||||
|  | ||||
| "LEGACY_PIP" = "Legacy PiP"; | ||||
| "LEGACY_PIP_DESC" = "Usa AVPlayerLayer per PiP. Questo ripara il bug relativo all'accelerazione ma rimuove anche le opzioni UHD (2K/4K) da qualsiasi video. È richiesto un riavvio dell'app."; | ||||
|  | ||||
| "NON_BACKGROUNDABLE_PIP" = "Non sullo sfondo PiP"; | ||||
| "NON_BACKGROUNDABLE_PIP_DESC" = "Attiva PiP per i video che non possono essere messi sullo sfondo."; | ||||
|  | ||||
| "FAKE_YT_VERSION" = "Versione YouTube falsa"; | ||||
| "FAKE_YT_VERSION_DESC" = "Imposta la versione di Youtube a %@ in modo tale da poter mostrare il bottone PiP sotto al video."; | ||||
|  | ||||
| // Compatibility warning | ||||
| "UNSUPPORTED_YT_VERSION" = "YouTube versione %@ non è stato testato e potrebbe non essere supportato da YouPiP, ti preghiamo di aggiorare YouTube almeno alla versione %@"; | ||||
| @@ -0,0 +1,25 @@ | ||||
| // Settings | ||||
|  | ||||
| "ENABLED" = "有効"; | ||||
| "ENABLED_DESC" = "アプリの再起動が必要です。"; | ||||
|  | ||||
| "USE_PIP_BUTTON" = "PiPボタンを使用"; | ||||
| "USE_PIP_BUTTON_DESC" = "ビデオコントロールオーバーレイの上にPiPボタンを追加しアプリを終了する代わりにPiPを起動できるようにします。"; | ||||
|  | ||||
| "USE_TAB_BAR_PIP_BUTTON" = "動画タブバーPiPボタンを使用"; | ||||
| "USE_TAB_BAR_PIP_BUTTON_DESC" = "ビデオタブバーにPiPボタンを追加しアプリを終了する代わりにPiPを起動できるようにします。アプリの再起動が必要です。"; | ||||
|  | ||||
| "DISABLE_PIP_MINI_PLAYER" = "ミニプレーヤーでのPiPを無効化"; | ||||
| "DISABLE_PIP_MINI_PLAYER_DESC" = "ミニプレーヤーで再生している際にはPiPを無効化します。"; | ||||
|  | ||||
| "LEGACY_PIP" = "レガシーPiP"; | ||||
| "LEGACY_PIP_DESC" = "AVPlayerLayerをPiPに使用します。スピードアップのバグを修正しますが全ての動画からUHDオプション(2K/4K)を削除されます。アプリの再起動が必要です。"; | ||||
|  | ||||
| "NON_BACKGROUNDABLE_PIP" = "非バックグラウンドPiP"; | ||||
| "NON_BACKGROUNDABLE_PIP_DESC" = "非バックグラウンドのビデオのPiPを有効化します。"; | ||||
|  | ||||
| "FAKE_YT_VERSION" = "YouTubeのバージョンを偽装"; | ||||
| "FAKE_YT_VERSION_DESC" = "YouTubeをバージョン %@ に設定し、ビデオプレーヤー下のPiPボタンに表示させます。"; | ||||
|  | ||||
| // Compatibility warning | ||||
| "UNSUPPORTED_YT_VERSION" = "YouTubeバージョン %@ はテストされておらずYouPiPがサポートされていません、YouTubeを最低でもバージョン %@ 以上にアップグレードしてください。"; | ||||
| @@ -0,0 +1,25 @@ | ||||
| // Settings | ||||
|  | ||||
| "ENABLED" = "활성화"; | ||||
| "ENABLED_DESC" = "앱 재시작이 필요합니다."; | ||||
|  | ||||
| "USE_PIP_BUTTON" = "PiP 버튼 사용"; | ||||
| "USE_PIP_BUTTON_DESC" = "영상 제어 오버레이 위에 PiP 버튼을 직접 추가하여 기존 PiP 방식과는 다른 방법으로 PiP를 활성화합니다."; | ||||
|  | ||||
| "USE_TAB_BAR_PIP_BUTTON" = "비디오 툴바 PiP 버튼 사용"; | ||||
| "USE_TAB_BAR_PIP_BUTTON_DESC" = "앱을 해제하는 대신 PiP를 활성화하기 위해 영상 탭 표시줄에 PiP 버튼을 추가합니다. 앱 재시작이 필요합니다."; | ||||
|  | ||||
| "DISABLE_PIP_MINI_PLAYER" = "미니플레이어 PiP 비활성화"; | ||||
| "DISABLE_PIP_MINI_PLAYER_DESC" = "미니 플레이어에서 영상을 재생하고 있는동안 PiP를 비활성화 합니다."; | ||||
|  | ||||
| "LEGACY_PIP" = "레거시 PiP"; | ||||
| "LEGACY_PIP_DESC" = "PiP용 AVPlayerLayer를 사용합니다. 이것은 속도향상 버그를 수정하지만 모든 영상에서 UHD 옵션(2K/4K)이 제거됩니다. 앱 재시작이 필요합니다."; | ||||
|  | ||||
| "NON_BACKGROUNDABLE_PIP" = "강제 백그라운드 PiP"; | ||||
| "NON_BACKGROUNDABLE_PIP_DESC" = "백그라운드가 되지 않는 영상에 PiP를 활성화합니다."; | ||||
|  | ||||
| "FAKE_YT_VERSION" = "가짜 유튜브 버전"; | ||||
| "FAKE_YT_VERSION_DESC" = "영상 플레이어 아래에 PiP 버튼이 나타날 수 있도록 YouTube 버전을 16.46.5로 설정합니다."; | ||||
|  | ||||
| // Compatibility warning | ||||
| "UNSUPPORTED_YT_VERSION" = "YouTube 버전 %@는 테스트되지 않았으며 YouPiP에서 지원하지 않을 수 있습니다. YouTube를 버전 %@ 이상으로 업그레이드하세요."; | ||||
| @@ -0,0 +1,25 @@ | ||||
| // Settings | ||||
|  | ||||
| "ENABLED" = "Enabled"; | ||||
| "ENABLED_DESC" = "App herstart is vereist."; | ||||
|  | ||||
| "USE_PIP_BUTTON" = "Gebruik PiP Knop"; | ||||
| "USE_PIP_BUTTON_DESC" = "Voegt een PiP knop toe aan de Video Control Overlay om PiP te activeren in plaats van de app af te sluiten."; | ||||
|  | ||||
| "USE_TAB_BAR_PIP_BUTTON" = "Gebruik de knop PiP van de Video-tabbalk"; | ||||
| "USE_TAB_BAR_PIP_BUTTON_DESC" = "Voegt een PiP-knop toe in de videotabbalk om PiP te activeren in plaats van de app af te sluiten. App herstart is vereist."; | ||||
|  | ||||
| "DISABLE_PIP_MINI_PLAYER" = "Schakel PiP uit voor Mini Player"; | ||||
| "DISABLE_PIP_MINI_PLAYER_DESC" = "Schakelt PiP uit tijdens het afspelen van een video in de minispeler."; | ||||
|  | ||||
| "LEGACY_PIP" = "Verouderd PiP"; | ||||
| "LEGACY_PIP_DESC" = "Gebruikt AVPlayerLayer voor PiP. Dit lost versnellingsfouten op, maar verwijdert mogelijke UHD-opties (2K/4K) van alle video's. App herstart is vereist."; | ||||
|  | ||||
| "NON_BACKGROUNDABLE_PIP" = "Niet-achtergrondgebonden PiP"; | ||||
| "NON_BACKGROUNDABLE_PIP_DESC" = "Schakelt PiP in voor niet-achtergrondgebonden video."; | ||||
|  | ||||
| "FAKE_YT_VERSION" = "Vals YouTube versie"; | ||||
| "FAKE_YT_VERSION_DESC" = "Stel de YouTube-versie in op %@ zodat de PiP-knop onder de videospeler kan worden weergegeven."; | ||||
|  | ||||
| // Compatibility warning | ||||
| "UNSUPPORTED_YT_VERSION" = "YouTube versie %@ is niet getest en wordt waarschijnlijk niet ondersteunt door YouPiP, gelieve YouTube te upgraden naar het nieuwste versie %@."; | ||||
| @@ -0,0 +1,25 @@ | ||||
| // Settings | ||||
|  | ||||
| "ENABLED" = "Ativado"; | ||||
| "ENABLED_DESC" = "A reinicialização do app é necessária."; | ||||
|  | ||||
| "USE_PIP_BUTTON" = "Usar o Botão PiP"; | ||||
| "USE_PIP_BUTTON_DESC" = "Adiciona um botão PiP sobre a sobreposição de controle de vídeo para ativar o PiP em vez de dispensar o aplicativo."; | ||||
|  | ||||
| "USE_TAB_BAR_PIP_BUTTON" = "Usar o Botão PiP da Barra de Guia de Vídeo"; | ||||
| "USE_TAB_BAR_PIP_BUTTON_DESC" = "Adiciona um botão PiP na barra de guia de vídeo para ativar o PiP em vez de dispensar o aplicativo. A reinicialização do app é necessária."; | ||||
|  | ||||
| "DISABLE_PIP_MINI_PLAYER" = "Desativar PiP para Mini Player"; | ||||
| "DISABLE_PIP_MINI_PLAYER_DESC" = "Desativa o PiP durante a reprodução de um vídeo no mini player."; | ||||
|  | ||||
| "LEGACY_PIP" = "PiP Legado"; | ||||
| "LEGACY_PIP_DESC" = "Usa AVPlayerLayer para o PiP. Isso corrige o bug de aceleração, mas também remove as opções UHD (2K/4K) de qualquer vídeo. A reinicialização do app é necessária."; | ||||
|  | ||||
| "NON_BACKGROUNDABLE_PIP" = "PiP sem segundo plano"; | ||||
| "NON_BACKGROUNDABLE_PIP_DESC" = "Habilita PiP para vídeo sem segundo plano."; | ||||
|  | ||||
| "FAKE_YT_VERSION" = "Versão Fake do YouTube"; | ||||
| "FAKE_YT_VERSION_DESC" = "Define a versão do YouTube para %@ para que o botão PiP no player de vídeo possa ser exibido."; | ||||
|  | ||||
| // Compatibility warning | ||||
| "UNSUPPORTED_YT_VERSION" = "A versão do YouTube %@ não foi testado e pode não ser compatível com o YouPiP, porfavor atualize o YouTube para pelo menos a versão %@"; | ||||
| @@ -0,0 +1,25 @@ | ||||
| // Settings | ||||
|  | ||||
| "ENABLED" = "Enabled"; | ||||
| "ENABLED_DESC" = "Este necesară repornirea aplicației."; | ||||
|  | ||||
| "USE_PIP_BUTTON" = "Utilizare buton PiP"; | ||||
| "USE_PIP_BUTTON_DESC" = "Adaugă un buton PiP deasupra controalelor video pentru a activa PiP, în loc să închideți aplicația."; | ||||
|  | ||||
| "USE_TAB_BAR_PIP_BUTTON" = "Utilizare buton PiP în bara filei video"; | ||||
| "USE_TAB_BAR_PIP_BUTTON_DESC" = "Adaugă un buton PiP în bara filei video pentru a activa PiP, în loc să închideți aplicația. Este necesară repornirea aplicației."; | ||||
|  | ||||
| "DISABLE_PIP_MINI_PLAYER" = "Dezactivare PiP pentru Mini Player"; | ||||
| "DISABLE_PIP_MINI_PLAYER_DESC" = "Dezactivează PiP în timp ce este redat un videoclip în mini player."; | ||||
|  | ||||
| "LEGACY_PIP" = "PiP Legacy"; | ||||
| "LEGACY_PIP_DESC" = "Utilizează AVPlayerLayer pentru PiP. Acesta rezolvă bug-ul legat de viteza de redare, dar poate elimina opțiunile de calitate UHD (2K/4K) de la videoclip. Este necesară repornirea aplicației."; | ||||
|  | ||||
| "NON_BACKGROUNDABLE_PIP" = "PiP neredabil în fundal"; | ||||
| "NON_BACKGROUNDABLE_PIP_DESC" = "Activează PiP pentru videoclipuri ce nu pot fi redate în fundal."; | ||||
|  | ||||
| "FAKE_YT_VERSION" = "Versiune YouTube falsă"; | ||||
| "FAKE_YT_VERSION_DESC" = "Setare versiune YouTube la %@ pentru a permite apariția butonului PiP sub player-ul video."; | ||||
|  | ||||
| // Compatibility warning | ||||
| "UNSUPPORTED_YT_VERSION" = "Această versiune YouTube (%@) nu a fost testată și este posibil să nu fie suportată de YouPiP. Vă rugăm să actualizați YouTube cel puțin la versiunea %@"; | ||||
| @@ -0,0 +1,25 @@ | ||||
| // Settings | ||||
|  | ||||
| "ENABLED" = "Активировать"; | ||||
| "ENABLED_DESC" = "Потребуется перезапуск YouTube."; | ||||
|  | ||||
| "USE_PIP_BUTTON" = "Активировать PiP вручную"; | ||||
| "USE_PIP_BUTTON_DESC" = "Добавляет кнопку PiP (Картинка в Картинке) в панель навигации для активации PiP вручную. Включите данную опцию, чтобы PiP не срабатывал автоматически."; | ||||
|  | ||||
| "USE_TAB_BAR_PIP_BUTTON" = "Использовать кнопку PiP под плеером"; | ||||
| "USE_TAB_BAR_PIP_BUTTON_DESC" = "Добавляет кнопку PiP (Картинка в Картинке) в панель под видео для активации PiP вручную. Включите данную опцию, чтобы PiP не срабатывал автоматически. Потребуется перезапуск YouTube."; | ||||
|  | ||||
| "DISABLE_PIP_MINI_PLAYER" = "Отключить PiP для мини-плеера"; | ||||
| "DISABLE_PIP_MINI_PLAYER_DESC" = "Предотвращает включение режима Картинка в Картинке, если видео проигрывается в мини-плеере."; | ||||
|  | ||||
| "LEGACY_PIP" = "Старый вариант PiP"; | ||||
| "LEGACY_PIP_DESC" = "На основе AVPlayerLayer, в котором нет багов со скоростью воспроизведения. Данный режим отключает воспроизведение видео в ультра высоком разрешении (2K/4K). Потребуется перезапуск YouTube."; | ||||
|  | ||||
| "NON_BACKGROUNDABLE_PIP" = "Принудительный PiP"; | ||||
| "NON_BACKGROUNDABLE_PIP_DESC" = "Активирует PiP (Картинка в Картинке) для контента, не поддерживающего PiP изначально."; | ||||
|  | ||||
| "FAKE_YT_VERSION" = "Имитация старой версии YouTube"; | ||||
| "FAKE_YT_VERSION_DESC" = "Понижает версию YouTube до %@, в которой корректно отображаются кнопки под плеером."; | ||||
|  | ||||
| // Compatibility warning | ||||
| "UNSUPPORTED_YT_VERSION" = "Данная версия YouTube (%@) еще не протестирована и, возможно, неподдерживается YouPiP-ом. Понизьте YouTube, как минимум, до версии %@"; | ||||
| @@ -0,0 +1,25 @@ | ||||
| // Settings | ||||
|  | ||||
| "ENABLED" = "Etkinleştir"; | ||||
| "ENABLED_DESC" = "Uygulamanın yeniden başlatılması gerekir."; | ||||
|  | ||||
| "USE_PIP_BUTTON" = "PiP Düğmesini Kullan"; | ||||
| "USE_PIP_BUTTON_DESC" = "Uygulamayı kapatmak yerine PiP'yi etkinleştirmek için video kontrol katmanının üzerine bir PiP düğmesi ekler."; | ||||
|  | ||||
| "USE_TAB_BAR_PIP_BUTTON" = "Video Sekme Çubuğu PiP Düğmesini Kullan"; | ||||
| "USE_TAB_BAR_PIP_BUTTON_DESC" = "Uygulamayı arkaplana almak yerine PiP'yi etkinleştirmek için video sekme çubuğuna bir PiP düğmesi ekler. Uygulamanın yeniden başlatılması gerekir."; | ||||
|  | ||||
| "DISABLE_PIP_MINI_PLAYER" = "Mini Oynatıcı için PiP'yi Devre Dışı Bırak"; | ||||
| "DISABLE_PIP_MINI_PLAYER_DESC" = "Mini oynatıcıda video oynatırken PiP'yi devre dışı bırakır."; | ||||
|  | ||||
| "LEGACY_PIP" = "Eski PiP"; | ||||
| "LEGACY_PIP_DESC" = "PiP için AVPlayerLayer'ı kullanır. Bu, hızlandırma hatasını düzeltir ancak herhangi bir videodan UHD seçeneklerini (2K/4K) kaldırabilir. Uygulamanın yeniden başlatılması gerekir."; | ||||
|  | ||||
| "NON_BACKGROUNDABLE_PIP" = "Arkaplansız PiP"; | ||||
| "NON_BACKGROUNDABLE_PIP_DESC" = "Arkaplansız video için PiP'yi etkinleştirir."; | ||||
|  | ||||
| "FAKE_YT_VERSION" = "Sahte YouTube sürümü"; | ||||
| "FAKE_YT_VERSION_DESC" = "Video oynatıcının altındaki PiP düğmesinin gösterilebilmesi için YouTube sürümünü %@ olarak ayarlayın."; | ||||
|  | ||||
| // Compatibility warning | ||||
| "UNSUPPORTED_YT_VERSION" = "YouTube %@ sürümü test edilmemiştir ve YouPiP tarafından desteklenmeyebilir, lütfen YouTube'u en az %@ sürümüne yükseltin"; | ||||
| @@ -0,0 +1,25 @@ | ||||
| // Settings | ||||
|  | ||||
| "ENABLED" = "Kích hoạt"; | ||||
| "ENABLED_DESC" = "Cần khởi động lại ứng dụng."; | ||||
|  | ||||
| "USE_PIP_BUTTON" = "Thêm nút kích hoạt PiP"; | ||||
| "USE_PIP_BUTTON_DESC" = "Thêm một nút để kích hoạt PiP thủ công vào trong trình phát video, thay vì để PiP kích hoạt tự động khi ẩn ứng dụng.";  | ||||
|  | ||||
| "USE_TAB_BAR_PIP_BUTTON" = "Thêm nút kích hoạt PiP vào thanh Tab"; | ||||
| "USE_TAB_BAR_PIP_BUTTON_DESC" = "Thêm một nút để kích hoạt PiP thủ công vào thanh Tab dưới video, thay vì để PiP kích hoạt tự động khi ẩn ứng dụng (chỉ hoạt động khi bạn bật tính năng Fake phiên bản của YouTube ở dưới)."; | ||||
|  | ||||
| "DISABLE_PIP_MINI_PLAYER" = "Vô hiệu hóa PiP khi trình phát thu nhỏ đang chạy"; | ||||
| "DISABLE_PIP_MINI_PLAYER_DESC" = "PiP sẽ không tự động kích hoạt khi video đang chạy ở dạng trình phát thủ nhỏ."; | ||||
|  | ||||
| "LEGACY_PIP" = "Legacy PiP"; | ||||
| "LEGACY_PIP_DESC" = "Sử dụng AVPlayerLayer cho PiP. Bật tính năng này để sửa lỗi speed-up của YouPiP. Tuy nhiên, video sẽ bị mất chất lượng 2K/4K (nếu có), đồng thời tính năng lựa chọn chất lượng video mặc định của uYou cũng sẽ không hoạt động. Cần khởi động lại ứng dụng."; | ||||
|  | ||||
| "NON_BACKGROUNDABLE_PIP" = "Non-backgroundable PiP"; | ||||
| "NON_BACKGROUNDABLE_PIP_DESC" = "Cho phép PiP hoạt động với những video không cho chạy nền (không khuyến khích)."; | ||||
|  | ||||
| "FAKE_YT_VERSION" = "Fake phiên bản của YouTube"; | ||||
| "FAKE_YT_VERSION_DESC" = "Đánh lừa phiên bản của YouTube hiện tại thành phiên bản %@ để tính năng Thêm nút kích hoạt PiP vào thanh Tab có thể hoạt động."; | ||||
|  | ||||
| // Compatibility warning | ||||
| "UNSUPPORTED_YT_VERSION" = "YouTube phiên bản dưới %@ chưa được test và có thể không được hỗ trợ bởi YouPiP, vui lòng cập nhật YouTube lên ít nhất phiên bản %@"; | ||||
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 118 B | 
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 184 B | 
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 244 B | 
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 104 B | 
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 109 B | 
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 112 B | 
| @@ -0,0 +1,25 @@ | ||||
| // Settings | ||||
|  | ||||
| "ENABLED" = "启用"; | ||||
| "ENABLED_DESC" = "更改本设置后需要重启App。"; | ||||
|  | ||||
| "USE_PIP_BUTTON" = "使用画中画按钮"; | ||||
| "USE_PIP_BUTTON_DESC" = "在视频播放器控制界面上显示用于手动启动画中画的按钮。开启本设置后,返回主屏幕时将不会启动画中画。"; | ||||
|  | ||||
| "USE_TAB_BAR_PIP_BUTTON" = "使用视频标签栏画中画按钮"; | ||||
| "USE_TAB_BAR_PIP_BUTTON_DESC" = "在视频标签栏处显示用于手动启动画中画的按钮。开启本设置后,返回主屏幕时将不会启动画中画。"; | ||||
|  | ||||
| "DISABLE_PIP_MINI_PLAYER" = "禁用迷你播放器画中画"; | ||||
| "DISABLE_PIP_MINI_PLAYER_DESC" = "在使用迷你播放器时禁用画中画。"; | ||||
|  | ||||
| "LEGACY_PIP" = "使用传统视频框架"; | ||||
| "LEGACY_PIP_DESC" = "使用 AVPlayerLayer 来进行画中画以修复视频加速播放的问题,但同时也将禁用所有视频的 UHD (2K/4K)画质选项。更改本设置后需要重启App。"; | ||||
|  | ||||
| "NON_BACKGROUNDABLE_PIP" = "强制画中画"; | ||||
| "NON_BACKGROUNDABLE_PIP_DESC" = "为不支持后台播放的视频启用画中画。"; | ||||
|  | ||||
| "FAKE_YT_VERSION" = "伪装 YouTube 版本"; | ||||
| "FAKE_YT_VERSION_DESC" = "将 YouTube App 的版本设为 %@ 以尝试显示播放器下方的画中画按钮。"; | ||||
|  | ||||
| // Compatibility warning | ||||
| "UNSUPPORTED_YT_VERSION" = "YouTube App 版本 %@ 未经测试,可能不支持 YouPiP 画中画插件。请将 YouTube 更新至版本 %@ 或以上。"; | ||||
| @@ -0,0 +1,25 @@ | ||||
| // Settings | ||||
|  | ||||
| "ENABLED" = "啟用"; | ||||
| "ENABLED_DESC" = "需要重新啟動應用程式。"; | ||||
|  | ||||
| "USE_PIP_BUTTON" = "使用子母畫面按鈕"; | ||||
| "USE_PIP_BUTTON_DESC" = "在影片控制層上增加子母畫面按鈕以啟用子母畫面而不是關閉應用程式。"; | ||||
|  | ||||
| "USE_TAB_BAR_PIP_BUTTON" = "使用影片標籤欄子母畫面按鈕"; | ||||
| "USE_TAB_BAR_PIP_BUTTON_DESC" = "在影片標籤欄中增加子母畫面按鈕以啟用子母畫面而不是關閉應用程式。需要重新啟動應用程式。"; | ||||
|  | ||||
| "DISABLE_PIP_MINI_PLAYER" = "為迷你播放器停用子母畫面"; | ||||
| "DISABLE_PIP_MINI_PLAYER_DESC" = "在迷你播放器中播放影片時停用子母畫面。"; | ||||
|  | ||||
| "LEGACY_PIP" = "傳統子母畫面(不建議使用)"; | ||||
| "LEGACY_PIP_DESC" = "使用 AVPlayerLayer 進行子母畫面。這修復了加速錯誤,但也同時從影片中刪除了 UHD 選項(2K/4K)。需要重新啟動應用程式。"; | ||||
|  | ||||
| "NON_BACKGROUNDABLE_PIP" = "非背景子母畫面"; | ||||
| "NON_BACKGROUNDABLE_PIP_DESC" = "為非背景影片啟用子母畫面。"; | ||||
|  | ||||
| "FAKE_YT_VERSION" = "假 YouTube 版本"; | ||||
| "FAKE_YT_VERSION_DESC" = "將 YouTube 版本設定為 %@,讓子母畫面按鈕正常顯示。"; | ||||
|  | ||||
| // Compatibility warning | ||||
| "UNSUPPORTED_YT_VERSION" = "YouTube 版本 %@ 未經測試,YouPiP 可能不支援,請將 YouTube 升級 %@ 版本"; | ||||
		Reference in New Issue
	
	Block a user
	 Balackburn
					Balackburn