diff --git a/Limelight/Base.lproj/iPad.storyboard b/Limelight/Base.lproj/iPad.storyboard index 800737c5..eb385bf3 100644 --- a/Limelight/Base.lproj/iPad.storyboard +++ b/Limelight/Base.lproj/iPad.storyboard @@ -1,9 +1,9 @@ - + - + @@ -12,7 +12,7 @@ - + @@ -547,6 +547,23 @@ ICAgICAgICAgICAgICA + + + + + + + + + + + @@ -559,6 +576,7 @@ ICAgICAgICAgICAgICA + @@ -607,7 +625,7 @@ ICAgICAgICAgICAgICA - + @@ -647,7 +665,7 @@ ICAgICAgICAgICAgICA - + @@ -669,7 +687,7 @@ ICAgICAgICAgICAgICA - + @@ -696,7 +714,7 @@ ICAgICAgICAgICAgICA - + @@ -893,11 +911,11 @@ ICAgICAgICAgICAgICA - + - + diff --git a/Limelight/Base.lproj/iPhone.storyboard b/Limelight/Base.lproj/iPhone.storyboard index 11ded364..1cc95c98 100644 --- a/Limelight/Base.lproj/iPhone.storyboard +++ b/Limelight/Base.lproj/iPhone.storyboard @@ -1,9 +1,9 @@ - + - + @@ -563,6 +563,23 @@ ICAgICAgICAgICAgIA + + + + + + + + + + + @@ -575,6 +592,7 @@ ICAgICAgICAgICAgIA + diff --git a/Limelight/Database/DataManager.h b/Limelight/Database/DataManager.h index fd6e03ec..d4708696 100644 --- a/Limelight/Database/DataManager.h +++ b/Limelight/Database/DataManager.h @@ -43,7 +43,8 @@ touchMode:(NSInteger)touchMode statsOverlay:(BOOL)statsOverlay allowPortrait:(BOOL)allowPortrait - resolutionSelected:(NSInteger)resolutionSelected; + resolutionSelected:(NSInteger)resolutionSelected + externalDisplayMode:(NSInteger)externalDisplayMode; - (NSArray*) getHosts; - (void) updateHost:(TemporaryHost*)host; diff --git a/Limelight/Database/DataManager.m b/Limelight/Database/DataManager.m index 95be2e2d..ef1de168 100644 --- a/Limelight/Database/DataManager.m +++ b/Limelight/Database/DataManager.m @@ -82,7 +82,8 @@ touchMode:(NSInteger)touchMode statsOverlay:(BOOL)statsOverlay allowPortrait:(BOOL)allowPortrait - resolutionSelected:(NSInteger)resolutionSelected { + resolutionSelected:(NSInteger)resolutionSelected + externalDisplayMode:(NSInteger)externalDisplayMode { [_managedObjectContext performBlockAndWait:^{ Settings* settingsToSave = [self retrieveSettings]; @@ -117,7 +118,7 @@ settingsToSave.statsOverlay = statsOverlay; settingsToSave.allowPortrait = allowPortrait; settingsToSave.resolutionSelected = [NSNumber numberWithInteger:resolutionSelected]; - + settingsToSave.externalDisplayMode = [NSNumber numberWithInteger:externalDisplayMode]; [self saveData]; }]; } diff --git a/Limelight/Database/TemporarySettings.h b/Limelight/Database/TemporarySettings.h index 1c238c05..b536d596 100644 --- a/Limelight/Database/TemporarySettings.h +++ b/Limelight/Database/TemporarySettings.h @@ -34,6 +34,7 @@ @property (nonatomic, retain) NSNumber * pointerVelocityModeDivider; @property (nonatomic, retain) NSString * uniqueId; @property (nonatomic, retain) NSNumber * resolutionSelected; +@property (nonatomic, retain) NSNumber * externalDisplayMode; @property (nonatomic) enum { CODEC_PREF_AUTO, CODEC_PREF_H264, diff --git a/Limelight/Database/TemporarySettings.m b/Limelight/Database/TemporarySettings.m index c3eef3c0..837ebbc6 100644 --- a/Limelight/Database/TemporarySettings.m +++ b/Limelight/Database/TemporarySettings.m @@ -100,6 +100,7 @@ self.pointerVelocityModeDivider = settings.pointerVelocityModeDivider; self.allowPortrait = settings.allowPortrait; self.resolutionSelected = settings.resolutionSelected; + self.externalDisplayMode = settings.externalDisplayMode; #endif self.uniqueId = settings.uniqueId; diff --git a/Limelight/Limelight-Info.plist b/Limelight/Limelight-Info.plist index f29ea4d0..541e31d5 100644 --- a/Limelight/Limelight-Info.plist +++ b/Limelight/Limelight-Info.plist @@ -74,7 +74,7 @@ armv7 UIRequiresFullScreen - + UIStatusBarHidden UISupportedInterfaceOrientations diff --git a/Limelight/Limelight.xcdatamodeld/Moonlight v1.10.xcdatamodel/contents b/Limelight/Limelight.xcdatamodeld/Moonlight v1.10.xcdatamodel/contents index 6bb357d8..bc7f12b1 100644 --- a/Limelight/Limelight.xcdatamodeld/Moonlight v1.10.xcdatamodel/contents +++ b/Limelight/Limelight.xcdatamodeld/Moonlight v1.10.xcdatamodel/contents @@ -27,6 +27,7 @@ + diff --git a/Limelight/Limelight.xcdatamodeld/Moonlight v1.11.xcdatamodel/contents b/Limelight/Limelight.xcdatamodeld/Moonlight v1.11.xcdatamodel/contents index 2f8369a0..77d2f0e8 100644 --- a/Limelight/Limelight.xcdatamodeld/Moonlight v1.11.xcdatamodel/contents +++ b/Limelight/Limelight.xcdatamodeld/Moonlight v1.11.xcdatamodel/contents @@ -27,6 +27,7 @@ + diff --git a/Limelight/Stream/VideoDecoderRenderer.m b/Limelight/Stream/VideoDecoderRenderer.m index a2adf08d..9c019a9e 100644 --- a/Limelight/Stream/VideoDecoderRenderer.m +++ b/Limelight/Stream/VideoDecoderRenderer.m @@ -90,6 +90,11 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size, [self reinitializeDisplayLayer]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(reinitializeDisplayLayer) + name:@"ScreenChanged" + object:nil]; + return self; } diff --git a/Limelight/ViewControllers/MainFrameViewController.m b/Limelight/ViewControllers/MainFrameViewController.m index 9a3dd723..fc03be11 100644 --- a/Limelight/ViewControllers/MainFrameViewController.m +++ b/Limelight/ViewControllers/MainFrameViewController.m @@ -617,6 +617,7 @@ static NSMutableSet* hostList; } - (void) prepareToStreamApp:(TemporaryApp *)app { + [self updateResolutionAccordingly]; self.revealViewController.isStreaming = true; // tell the revealViewController streaming is started. _streamConfig = [[StreamConfiguration alloc] init]; _streamConfig.host = app.host.activeAddress; @@ -634,9 +635,14 @@ static NSMutableSet* hostList; _streamConfig.frameRate = [streamSettings.framerate intValue]; if (@available(iOS 10.3, *)) { + UIWindow *window = UIApplication.sharedApplication.windows.firstObject; + NSInteger maximumFramesPerSecond = window.screen.maximumFramesPerSecond; + if(UIScreen.screens.count > 1 && streamSettings.externalDisplayMode.intValue == 1){ //AirPlaying + maximumFramesPerSecond = UIScreen.screens.lastObject.maximumFramesPerSecond; + } // Don't stream more FPS than the display can show - if (_streamConfig.frameRate > [UIScreen mainScreen].maximumFramesPerSecond) { - _streamConfig.frameRate = (int)[UIScreen mainScreen].maximumFramesPerSecond; + if (_streamConfig.frameRate > maximumFramesPerSecond) { + _streamConfig.frameRate = (int)maximumFramesPerSecond; Log(LOG_W, @"Clamping FPS to maximum refresh rate: %d", _streamConfig.frameRate); } } @@ -1171,7 +1177,7 @@ static NSMutableSet* hostList; //if([SettingsViewController isLandscapeNow] != _streamConfig.width > _streamConfig.height) //[self simulateSettingsButtonPress]; //force expand setting view if orientation changed since last quit from app. //[self simulateSettingsButtonPress]; //force expand setting view if orientation changed since last quit from app. - [self updateResolutionAccordingly]; + //[self updateResolutionAccordingly]; // SettingsViewController* settingsViewController = (SettingsViewController*)[self.revealViewController rearViewController]; // [settingsViewController updateResolutionTable]; @@ -1219,17 +1225,17 @@ static NSMutableSet* hostList; } // this will also be called back when device orientation changes -- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { - [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; - - double delayInSeconds = 0.7; - // Convert the delay into a dispatch_time_t value - dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); - // Perform some task after the delay - dispatch_after(delayTime, dispatch_get_main_queue(), ^{// Code to execute after the delay - [self updateResolutionAccordingly]; - }); -} +//- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { +// [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; +// +// double delayInSeconds = 0.7; +// // Convert the delay into a dispatch_time_t value +// dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); +// // Perform some task after the delay +// dispatch_after(delayTime, dispatch_get_main_queue(), ^{// Code to execute after the delay +// [self updateResolutionAccordingly]; +// }); +//} -(void) updateResolutionAccordingly { DataManager* dataMan = [[DataManager alloc] init]; @@ -1240,6 +1246,13 @@ static NSMutableSet* hostList; CGFloat appWindowHeight = CGRectGetHeight(window.frame) * screenScale; CGFloat screenWidthInPoints = CGRectGetWidth([[UIScreen mainScreen] bounds]); CGFloat screenHeightInPoints = CGRectGetHeight([[UIScreen mainScreen] bounds]); + + if(currentSettings.externalDisplayMode.intValue == 1 && UIScreen.screens.count > 1){ + CGRect bounds = [UIScreen.screens.lastObject bounds]; + screenScale = [UIScreen.screens.lastObject scale]; + appWindowWidth = bounds.size.width * screenScale; + appWindowHeight = bounds.size.height * screenScale; + } bool needSwap = false; @@ -1398,7 +1411,7 @@ static NSMutableSet* hostList; object: nil]; //[self simulateSettingsButtonPress]; //force reload resolution table in the setting //[self simulateSettingsButtonPress]; - [self updateResolutionAccordingly]; + //[self updateResolutionAccordingly]; } diff --git a/Limelight/ViewControllers/SettingsViewController.h b/Limelight/ViewControllers/SettingsViewController.h index 852a5e77..360d101e 100644 --- a/Limelight/ViewControllers/SettingsViewController.h +++ b/Limelight/ViewControllers/SettingsViewController.h @@ -57,6 +57,7 @@ @property (strong, nonatomic) LayoutOnScreenControlsViewController *layoutOnScreenControlsVC; @property (nonatomic, strong) MainFrameViewController *mainFrameViewController; +@property (strong, nonatomic) IBOutlet UISegmentedControl *externalDisplayModeSelector; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunguarded-availability" diff --git a/Limelight/ViewControllers/SettingsViewController.m b/Limelight/ViewControllers/SettingsViewController.m index 02a3a5a3..6672229d 100644 --- a/Limelight/ViewControllers/SettingsViewController.m +++ b/Limelight/ViewControllers/SettingsViewController.m @@ -222,6 +222,9 @@ BOOL isCustomResolution(CGSize res) { return true; } +- (bool)isAirPlayEnabled{ + return [self.externalDisplayModeSelector selectedSegmentIndex] == 1; +} - (void)updateResolutionTable{ UIWindow *window = UIApplication.sharedApplication.windows.firstObject; @@ -229,6 +232,12 @@ BOOL isCustomResolution(CGSize res) { CGFloat safeAreaWidth = (window.frame.size.width - window.safeAreaInsets.left - window.safeAreaInsets.right) * screenScale; CGFloat appWindowWidth = window.frame.size.width * screenScale; CGFloat appWindowHeight = window.frame.size.height * screenScale; + if([self isAirPlayEnabled] && UIScreen.screens.count > 1){ + CGRect bounds = [UIScreen.screens.lastObject bounds]; + screenScale = [UIScreen.screens.lastObject scale]; + appWindowWidth = bounds.size.width * screenScale; + appWindowHeight = bounds.size.height * screenScale; + } bool needSwapWidthAndHeight = appWindowWidth > appWindowHeight; resolutionTable[4] = CGSizeMake(safeAreaWidth, appWindowHeight); @@ -248,7 +257,7 @@ BOOL isCustomResolution(CGSize res) { // this will also be called back when device orientation changes - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; - double delayInSeconds = 1.0; + double delayInSeconds = 0.2; // Convert the delay into a dispatch_time_t value dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); // Perform some task after the delay @@ -549,6 +558,8 @@ BOOL isCustomResolution(CGSize res) { self.layoutOnScreenControlsVC = [storyboard instantiateViewControllerWithIdentifier:@"LayoutOnScreenControlsViewController"]; self.layoutOnScreenControlsVC.modalPresentationStyle = UIModalPresentationFullScreen; } + + [self.externalDisplayModeSelector setSelectedSegmentIndex:currentSettings.externalDisplayMode.integerValue]; } - (void)slideToSettingsScreenEdgeChanged{ @@ -1055,6 +1066,7 @@ BOOL isCustomResolution(CGSize res) { BOOL enableHdr = [self.hdrSelector selectedSegmentIndex] == 1; BOOL allowPortrait = [self.allowPortraitSelector selectedSegmentIndex] == 1; NSInteger resolutionSelected = [self.resolutionSelector selectedSegmentIndex]; + NSInteger externalDisplayMode = [self.externalDisplayModeSelector selectedSegmentIndex]; [dataMan saveSettingsWithBitrate:_bitrate framerate:framerate height:height @@ -1085,7 +1097,8 @@ BOOL isCustomResolution(CGSize res) { touchMode:touchMode statsOverlay:statsOverlay allowPortrait:allowPortrait - resolutionSelected:resolutionSelected]; + resolutionSelected:resolutionSelected + externalDisplayMode:externalDisplayMode]; } - (void)didReceiveMemoryWarning { diff --git a/Limelight/ViewControllers/StreamFrameViewController.m b/Limelight/ViewControllers/StreamFrameViewController.m index babc59fd..07ff44ba 100644 --- a/Limelight/ViewControllers/StreamFrameViewController.m +++ b/Limelight/ViewControllers/StreamFrameViewController.m @@ -54,6 +54,10 @@ UIScrollView *_scrollView; BOOL _userIsInteracting; CGSize _keyboardSize; + UIWindow *_extWindow; + UIView *_renderView; + UIWindow *_deviceWindow; + dispatch_block_t _delayedRemoveExtScreen; #if !TARGET_OS_TV CustomEdgeSlideGestureRecognizer *_slideToSettingsRecognizer; CustomEdgeSlideGestureRecognizer *_slideToCmdToolRecognizer; @@ -162,6 +166,8 @@ [self->_streamView reloadOnScreenControlsRealtimeWith:(ControllerSupport*)_controllerSupport andConfig:(StreamConfiguration*)_streamConfig]; //reload OSC here. [self->_streamView reloadOnScreenButtonViews]; //reload keyboard buttons here. the keyboard button view will be added to the streamframe view instead streamview, the highest layer, which saves a lot of reengineering + [self reloadAirPlayConfig]; + //reconfig statsOverlay self->_statsUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self @@ -179,6 +185,29 @@ - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; + + _deviceWindow = self.view.window; + + if (UIScreen.screens.count > 1 && [self isAirPlayEnabled]) { + [self prepExtScreen:UIScreen.screens.lastObject]; + } + else { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.view insertSubview:self->_renderView atIndex:0]; + }); + } + + // check to see if external screen is connected/disconnected + + [[NSNotificationCenter defaultCenter] addObserver: self + selector: @selector(extScreenDidConnect:) + name: UIScreenDidConnectNotification + object: nil]; + + [[NSNotificationCenter defaultCenter] addObserver: self + selector: @selector(extScreenDidDisconnect:) + name: UIScreenDidDisconnectNotification + object: nil]; #if !TARGET_OS_TV [[self revealViewController] setPrimaryViewController:self]; @@ -239,7 +268,9 @@ _controllerSupport = [[ControllerSupport alloc] initWithConfig:self.streamConfig delegate:self]; _inactivityTimer = nil; + _renderView = (StreamView*)[[UIView alloc] initWithFrame:self.view.frame]; _streamView = [[StreamView alloc] initWithFrame:self.view.frame]; + _renderView.bounds = _streamView.bounds; //[_streamView setupStreamView:_controllerSupport interactionDelegate:self config:self.streamConfig]; [self reConfigStreamViewRealtime]; // call this method again to make sure all gestures are configured & added to the superview(self.view), including the gestures added from inside the streamview. @@ -281,7 +312,7 @@ _tipLabel.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height * 0.9); _streamMan = [[StreamManager alloc] initWithConfig:self.streamConfig - renderView:_streamView + renderView:_renderView connectionCallbacks:self]; NSOperationQueue* opQueue = [[NSOperationQueue alloc] init]; [opQueue addOperation:_streamMan]; @@ -449,8 +480,86 @@ _statsUpdateTimer = nil; [self.navigationController popToRootViewControllerAnimated:YES]; + + _extWindow = nil; } +// External Screen connected +- (void)extScreenDidConnect:(NSNotification *)notification { + Log(LOG_I, @"External Screen Connected"); + dispatch_async(dispatch_get_main_queue(), ^{ + [self prepExtScreen:notification.object]; + }); +} + +// External Screen disconnected +- (void)extScreenDidDisconnect:(NSNotification *)notification { + Log(LOG_I, @"External Screen Disconnected"); + if(UIScreen.screens.count < 2) + { + dispatch_async(dispatch_get_main_queue(), ^{ + [self removeExtScreen]; + }); + } +} + +- (BOOL) isAirPlaying{ + return _extWindow != nil && _extWindow.hidden == NO; +} + +- (BOOL) isAirPlayEnabled{ + return _settings.externalDisplayMode.intValue == 1; +} + +- (void) reloadAirPlayConfig{ + if (UIScreen.screens.count == 1){return;} + if (![self isAirPlaying] && [self isAirPlayEnabled]){ + [self prepExtScreen:UIScreen.screens.lastObject]; + }else if ([self isAirPlaying] && ![self isAirPlayEnabled]){ + [self removeExtScreen]; + } +} + +// Prepare Screen +- (void)prepExtScreen:(UIScreen*)extScreen { + Log(LOG_I, @"Preparing External Screen"); + if(![self isAirPlayEnabled]){ + return; + } + CGRect frame = extScreen.bounds; + extScreen.overscanCompensation = UIScreenOverscanCompensationNone; + if(_extWindow==nil){ + _extWindow = [[UIWindow alloc] initWithFrame:frame]; + } + _extWindow.screen = extScreen; + _renderView.bounds = frame; + _renderView.frame = frame; + NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; + [nc postNotificationName:@"ScreenChanged" object:self]; + [_extWindow addSubview:_renderView]; + _extWindow.hidden = NO; +} + +- (void)removeExtScreen { + Log(LOG_I, @"Removing External Screen"); + _extWindow.hidden = YES; + [self handleViewResize]; + [self.view insertSubview:_renderView atIndex:0]; +} + +- (void) handleViewResize{ + _streamView.bounds = _deviceWindow.bounds; + _streamView.frame = _deviceWindow.frame; + if(![self isAirPlaying]){ + _renderView.bounds = _deviceWindow.bounds; + _renderView.frame = _deviceWindow.frame; + NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; + [nc postNotificationName:@"ScreenChanged" object:self]; + } + [self reConfigStreamViewRealtime]; +} + + // This will fire if the user opens control center or gets a low battery message - (void)applicationWillResignActive:(NSNotification *)notification { if (_inactivityTimer != nil) { @@ -523,6 +632,7 @@ // the first frame of video. self->_stageLabel.hidden = YES; self->_tipLabel.hidden = YES; + self->_spinner.hidden = YES; [self->_streamView showOnScreenControls]; @@ -864,7 +974,17 @@ [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; Log(LOG_I, @"View size changed, terminating stream"); - // [self returnToMainFrame]; + + double delayInSeconds = 0.2; + if (_delayedRemoveExtScreen) { + dispatch_block_cancel(_delayedRemoveExtScreen); + } + dispatch_block_t block = dispatch_block_create(0, ^{ + [self handleViewResize]; + }); + _delayedRemoveExtScreen = block; + dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); + dispatch_after(delayTime, dispatch_get_main_queue(), block); } @end diff --git a/Limelight/mul.lproj/iPad.xcstrings b/Limelight/mul.lproj/iPad.xcstrings index dc2f0eaf..2b332554 100644 --- a/Limelight/mul.lproj/iPad.xcstrings +++ b/Limelight/mul.lproj/iPad.xcstrings @@ -91,6 +91,36 @@ } } }, + "4Qy-hB-oa5.text" : { + "comment" : "Class = \"UILabel\"; text = \"External Display Mode\"; ObjectID = \"4Qy-hB-oa5\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "External Display Mode" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "外接显示模式" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "外接显示模式" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "外接显示模式" + } + } + } + }, "6w5-Md-NpV.text" : { "comment" : "Class = \"UILabel\"; text = \"Preferred Codec \"; ObjectID = \"6w5-Md-NpV\";", "extractionState" : "extracted_with_value", @@ -2191,6 +2221,66 @@ } } }, + "VWB-YB-Ptj.segmentTitles[0]" : { + "comment" : "Class = \"UISegmentedControl\"; VWB-YB-Ptj.segmentTitles[0] = \"Stage Manager\"; ObjectID = \"VWB-YB-Ptj\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Stage Manager" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "台前调度" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "台前调度" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "台前调度" + } + } + } + }, + "VWB-YB-Ptj.segmentTitles[1]" : { + "comment" : "Class = \"UISegmentedControl\"; VWB-YB-Ptj.segmentTitles[1] = \"AirPlay\"; ObjectID = \"VWB-YB-Ptj\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "AirPlay" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "隔空播放" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "隔空播放" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "隔空播放" + } + } + } + }, "WDY-9o-6Hb.segmentTitles[0]" : { "comment" : "Class = \"UISegmentedControl\"; WDY-9o-6Hb.segmentTitles[0] = \"No\"; ObjectID = \"WDY-9o-6Hb\";", "extractionState" : "extracted_with_value", diff --git a/Limelight/mul.lproj/iPhone.xcstrings b/Limelight/mul.lproj/iPhone.xcstrings index ec7663ec..26e95e57 100644 --- a/Limelight/mul.lproj/iPhone.xcstrings +++ b/Limelight/mul.lproj/iPhone.xcstrings @@ -271,6 +271,36 @@ } } }, + "4Qy-hB-oa5.text" : { + "comment" : "Class = \"UILabel\"; text = \"External Display Mode\"; ObjectID = \"4Qy-hB-oa5\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "External Display Mode" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "外接显示模式" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "外接显示模式" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "外接显示模式" + } + } + } + }, "5pN-Sn-SCN.title" : { "comment" : "Class = \"UINavigationItem\"; title = \"Controller & Button Profiles\"; ObjectID = \"5pN-Sn-SCN\";", "extractionState" : "extracted_with_value", @@ -2011,6 +2041,66 @@ } } }, + "VWB-YB-Ptj.segmentTitles[0]" : { + "comment" : "Class = \"UISegmentedControl\"; VWB-YB-Ptj.segmentTitles[0] = \"Stage Manager\"; ObjectID = \"VWB-YB-Ptj\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Stage Manager" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "台前调度" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "台前调度" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "台前调度" + } + } + } + }, + "VWB-YB-Ptj.segmentTitles[1]" : { + "comment" : "Class = \"UISegmentedControl\"; VWB-YB-Ptj.segmentTitles[1] = \"AirPlay\"; ObjectID = \"VWB-YB-Ptj\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "AirPlay" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "隔空播放" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "隔空播放" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "隔空播放" + } + } + } + }, "WGf-9d-eAm.segmentTitles[0]" : { "comment" : "Class = \"UISegmentedControl\"; WGf-9d-eAm.segmentTitles[0] = \"Off\"; ObjectID = \"WGf-9d-eAm\";", "extractionState" : "extracted_with_value",