diff --git a/Limelight/Input/StreamView.h b/Limelight/Input/StreamView.h index ab83d81..adfcd1d 100644 --- a/Limelight/Input/StreamView.h +++ b/Limelight/Input/StreamView.h @@ -30,8 +30,6 @@ @interface StreamView : UIView #endif -@property (nonatomic, retain) IBOutlet UITextField* keyInputField; - - (void) setupStreamView:(ControllerSupport*)controllerSupport swipeDelegate:(id)swipeDelegate interactionDelegate:(id)interactionDelegate diff --git a/Limelight/Input/StreamView.m b/Limelight/Input/StreamView.m index 576baf5..0b6ab04 100644 --- a/Limelight/Input/StreamView.m +++ b/Limelight/Input/StreamView.m @@ -19,6 +19,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5; @implementation StreamView { OnScreenControls* onScreenControls; + UITextField* keyInputField; BOOL isInputingText; float streamAspectRatio; @@ -51,6 +52,9 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5; TemporarySettings* settings = [[[DataManager alloc] init] getSettings]; + keyInputField = [[UITextField alloc] initWithFrame:CGRectZero]; + [self addSubview:keyInputField]; + #if TARGET_OS_TV // tvOS requires RelativeTouchHandler to manage Apple Remote input self->touchHandler = [[RelativeTouchHandler alloc] initWithView:self]; @@ -164,18 +168,18 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5; if ([[event allTouches] count] == 3) { if (isInputingText) { Log(LOG_D, @"Closing the keyboard"); - [_keyInputField resignFirstResponder]; + [keyInputField resignFirstResponder]; isInputingText = false; } else { Log(LOG_D, @"Opening the keyboard"); // Prepare the textbox used to capture keyboard events. - _keyInputField.delegate = self; - _keyInputField.text = @"0"; - [_keyInputField becomeFirstResponder]; - [_keyInputField addTarget:self action:@selector(onKeyboardPressed:) forControlEvents:UIControlEventEditingChanged]; + keyInputField.delegate = self; + keyInputField.text = @"0"; + [keyInputField becomeFirstResponder]; + [keyInputField addTarget:self action:@selector(onKeyboardPressed:) forControlEvents:UIControlEventEditingChanged]; // Undo causes issues for our state management, so turn it off - [_keyInputField.undoManager disableUndoRegistration]; + [keyInputField.undoManager disableUndoRegistration]; isInputingText = true; } diff --git a/Limelight/Stream/Connection.h b/Limelight/Stream/Connection.h index 850ca50..268b7fc 100644 --- a/Limelight/Stream/Connection.h +++ b/Limelight/Stream/Connection.h @@ -19,19 +19,6 @@ typedef struct { int networkDroppedFrames; } video_stats_t; -@protocol ConnectionCallbacks - -- (void) connectionStarted; -- (void) connectionTerminated:(int)errorCode; -- (void) stageStarting:(const char*)stageName; -- (void) stageComplete:(const char*)stageName; -- (void) stageFailed:(const char*)stageName withError:(int)errorCode portTestFlags:(int)portTestFlags; -- (void) launchFailed:(NSString*)message; -- (void) rumble:(unsigned short)controllerNumber lowFreqMotor:(unsigned short)lowFreqMotor highFreqMotor:(unsigned short)highFreqMotor; -- (void) connectionStatusUpdate:(int)status; - -@end - @interface Connection : NSOperation -(id) initWithConfig:(StreamConfiguration*)config renderer:(VideoDecoderRenderer*)myRenderer connectionCallbacks:(id)callbacks; diff --git a/Limelight/Stream/ConnectionCallbacks.h b/Limelight/Stream/ConnectionCallbacks.h new file mode 100644 index 0000000..c7acac9 --- /dev/null +++ b/Limelight/Stream/ConnectionCallbacks.h @@ -0,0 +1,21 @@ +// +// ConnectionCallbacks.h +// Moonlight +// +// Created by Cameron Gutman on 11/1/20. +// Copyright © 2020 Moonlight Game Streaming Project. All rights reserved. +// + +@protocol ConnectionCallbacks + +- (void) connectionStarted; +- (void) connectionTerminated:(int)errorCode; +- (void) stageStarting:(const char*)stageName; +- (void) stageComplete:(const char*)stageName; +- (void) stageFailed:(const char*)stageName withError:(int)errorCode portTestFlags:(int)portTestFlags; +- (void) launchFailed:(NSString*)message; +- (void) rumble:(unsigned short)controllerNumber lowFreqMotor:(unsigned short)lowFreqMotor highFreqMotor:(unsigned short)highFreqMotor; +- (void) connectionStatusUpdate:(int)status; +- (void) videoContentShown; + +@end diff --git a/Limelight/Stream/StreamManager.m b/Limelight/Stream/StreamManager.m index a79ec46..3e9a354 100644 --- a/Limelight/Stream/StreamManager.m +++ b/Limelight/Stream/StreamManager.m @@ -84,7 +84,7 @@ // Initializing the renderer must be done on the main thread dispatch_async(dispatch_get_main_queue(), ^{ - VideoDecoderRenderer* renderer = [[VideoDecoderRenderer alloc] initWithView:self->_renderView]; + VideoDecoderRenderer* renderer = [[VideoDecoderRenderer alloc] initWithView:self->_renderView callbacks:self->_callbacks]; self->_connection = [[Connection alloc] initWithConfig:self->_config renderer:renderer connectionCallbacks:self->_callbacks]; NSOperationQueue* opQueue = [[NSOperationQueue alloc] init]; [opQueue addOperation:self->_connection]; diff --git a/Limelight/Stream/VideoDecoderRenderer.h b/Limelight/Stream/VideoDecoderRenderer.h index b15a82d..dd3e426 100644 --- a/Limelight/Stream/VideoDecoderRenderer.h +++ b/Limelight/Stream/VideoDecoderRenderer.h @@ -8,9 +8,11 @@ @import AVFoundation; +#import "ConnectionCallbacks.h" + @interface VideoDecoderRenderer : NSObject -- (id)initWithView:(UIView*)view; +- (id)initWithView:(UIView*)view callbacks:(id)callbacks; - (void)setupWithVideoFormat:(int)videoFormat refreshRate:(int)refreshRate; diff --git a/Limelight/Stream/VideoDecoderRenderer.m b/Limelight/Stream/VideoDecoderRenderer.m index 8ae7388..aa74a00 100644 --- a/Limelight/Stream/VideoDecoderRenderer.m +++ b/Limelight/Stream/VideoDecoderRenderer.m @@ -13,6 +13,7 @@ @implementation VideoDecoderRenderer { StreamView* _view; + id _callbacks; AVSampleBufferDisplayLayer* displayLayer; Boolean waitingForSps, waitingForPps, waitingForVps; @@ -61,11 +62,12 @@ } } -- (id)initWithView:(StreamView*)view +- (id)initWithView:(StreamView*)view callbacks:(id)callbacks { self = [super init]; _view = view; + _callbacks = callbacks; [self reinitializeDisplayLayer]; @@ -371,6 +373,9 @@ if ([self isNalReferencePicture:nalType]) { // Ensure the layer is visible now self->displayLayer.hidden = NO; + + // Tell our parent VC to hide the progress indicator + [self->_callbacks videoContentShown]; } // Dereference the buffers diff --git a/Limelight/ViewControllers/StreamFrameViewController.h b/Limelight/ViewControllers/StreamFrameViewController.h index 01aa666..7ecb3fa 100644 --- a/Limelight/ViewControllers/StreamFrameViewController.h +++ b/Limelight/ViewControllers/StreamFrameViewController.h @@ -19,9 +19,6 @@ #else @interface StreamFrameViewController : UIViewController #endif -@property (strong, nonatomic) IBOutlet UILabel *stageLabel; -@property (strong, nonatomic) IBOutlet UILabel *tipLabel; -@property (strong, nonatomic) IBOutlet UIActivityIndicatorView *spinner; @property (nonatomic) StreamConfiguration* streamConfig; @end diff --git a/Limelight/ViewControllers/StreamFrameViewController.m b/Limelight/ViewControllers/StreamFrameViewController.m index 3ac7448..571880a 100644 --- a/Limelight/ViewControllers/StreamFrameViewController.m +++ b/Limelight/ViewControllers/StreamFrameViewController.m @@ -26,6 +26,9 @@ UITapGestureRecognizer *_menuGestureRecognizer; UITapGestureRecognizer *_menuDoubleTapGestureRecognizer; UITextView *_overlayView; + UILabel *_stageLabel; + UILabel *_tipLabel; + UIActivityIndicatorView *_spinner; StreamView *_streamView; BOOL _userIsInteracting; } @@ -54,17 +57,31 @@ [self.navigationController setNavigationBarHidden:YES animated:YES]; - [self.stageLabel setText:[NSString stringWithFormat:@"Starting %@...", self.streamConfig.appName]]; - [self.stageLabel sizeToFit]; - self.stageLabel.textAlignment = NSTextAlignmentCenter; - self.stageLabel.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2); - self.spinner.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2 - self.stageLabel.frame.size.height - self.spinner.frame.size.height); [UIApplication sharedApplication].idleTimerDisabled = YES; + _stageLabel = [[UILabel alloc] init]; + [_stageLabel setUserInteractionEnabled:NO]; + [_stageLabel setText:[NSString stringWithFormat:@"Starting %@...", self.streamConfig.appName]]; + [_stageLabel sizeToFit]; + _stageLabel.textAlignment = NSTextAlignmentCenter; + _stageLabel.textColor = [UIColor whiteColor]; + _stageLabel.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2); + + _spinner = [[UIActivityIndicatorView alloc] init]; + [_spinner setUserInteractionEnabled:NO]; +#if TARGET_OS_TV + [_spinner setActivityIndicatorViewStyle:UIActivityIndicatorViewStyleWhiteLarge]; +#else + [_spinner setActivityIndicatorViewStyle:UIActivityIndicatorViewStyleWhite]; +#endif + [_spinner sizeToFit]; + [_spinner startAnimating]; + _spinner.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2 - _stageLabel.frame.size.height - _spinner.frame.size.height); + _controllerSupport = [[ControllerSupport alloc] initWithConfig:self.streamConfig presenceDelegate:self]; _inactivityTimer = nil; - _streamView = (StreamView*)self.view; + _streamView = [[StreamView alloc] initWithFrame:self.view.frame]; [_streamView setupStreamView:_controllerSupport swipeDelegate:self interactionDelegate:self config:self.streamConfig]; #if TARGET_OS_TV @@ -82,18 +99,23 @@ [self.view addGestureRecognizer:_menuDoubleTapGestureRecognizer]; #endif + + _tipLabel = [[UILabel alloc] init]; + [_tipLabel setUserInteractionEnabled:NO]; + #if TARGET_OS_TV - [self.tipLabel setText:@"Tip: Double tap the Menu button to disconnect from your PC"]; + [_tipLabel setText:@"Tip: Double tap the Menu button to disconnect from your PC"]; #else - [self.tipLabel setText:@"Tip: Swipe from the left edge to disconnect from your PC"]; + [_tipLabel setText:@"Tip: Swipe from the left edge to disconnect from your PC"]; #endif - [self.tipLabel sizeToFit]; - self.tipLabel.textAlignment = NSTextAlignmentCenter; - self.tipLabel.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height * 0.9); + [_tipLabel sizeToFit]; + _tipLabel.textColor = [UIColor whiteColor]; + _tipLabel.textAlignment = NSTextAlignmentCenter; + _tipLabel.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height * 0.9); _streamMan = [[StreamManager alloc] initWithConfig:self.streamConfig - renderView:self.view + renderView:_streamView connectionCallbacks:self]; NSOperationQueue* opQueue = [[NSOperationQueue alloc] init]; [opQueue addOperation:_streamMan]; @@ -112,6 +134,11 @@ selector: @selector(applicationDidEnterBackground:) name: UIApplicationDidEnterBackgroundNotification object: nil]; + + [self.view addSubview:_streamView]; + [self.view addSubview:_stageLabel]; + [self.view addSubview:_spinner]; + [self.view addSubview:_tipLabel]; } - (void)willMoveToParentViewController:(UIViewController *)parent { @@ -244,8 +271,8 @@ dispatch_async(dispatch_get_main_queue(), ^{ // Leave the spinner spinning until it's obscured by // the first frame of video. - self.stageLabel.hidden = YES; - self.tipLabel.hidden = YES; + self->_stageLabel.hidden = YES; + self->_tipLabel.hidden = YES; [self->_streamView showOnScreenControls]; @@ -318,9 +345,9 @@ dispatch_async(dispatch_get_main_queue(), ^{ NSString* lowerCase = [NSString stringWithFormat:@"%s in progress...", stageName]; NSString* titleCase = [[[lowerCase substringToIndex:1] uppercaseString] stringByAppendingString:[lowerCase substringFromIndex:1]]; - [self.stageLabel setText:titleCase]; - [self.stageLabel sizeToFit]; - self.stageLabel.center = CGPointMake(self.view.frame.size.width / 2, self.stageLabel.center.y); + [self->_stageLabel setText:titleCase]; + [self->_stageLabel sizeToFit]; + self->_stageLabel.center = CGPointMake(self.view.frame.size.width / 2, self->_stageLabel.center.y); }); } @@ -404,6 +431,10 @@ }); } +- (void) videoContentShown { + [_spinner stopAnimating]; +} + - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; diff --git a/Moonlight TV/Base.lproj/Main.storyboard b/Moonlight TV/Base.lproj/Main.storyboard index 0b322ef..7c0413f 100644 --- a/Moonlight TV/Base.lproj/Main.storyboard +++ b/Moonlight TV/Base.lproj/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -87,8 +87,8 @@ - + @@ -106,36 +106,11 @@ - + - - - - - - - - - - - - - diff --git a/Moonlight.xcodeproj/project.pbxproj b/Moonlight.xcodeproj/project.pbxproj index 369d72d..9496b5f 100644 --- a/Moonlight.xcodeproj/project.pbxproj +++ b/Moonlight.xcodeproj/project.pbxproj @@ -161,6 +161,7 @@ /* Begin PBXFileReference section */ 693B3A9A218638CD00982F7B /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = ""; }; + 9803CCAB254F9EAF00EE185E /* ConnectionCallbacks.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ConnectionCallbacks.h; sourceTree = ""; }; 98132E8C20BC9A62007A053F /* Moonlight v1.1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Moonlight v1.1.xcdatamodel"; sourceTree = ""; }; 9819CC12254F107A008A7C8E /* RelativeTouchHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RelativeTouchHandler.h; sourceTree = ""; }; 9819CC13254F107A008A7C8E /* RelativeTouchHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RelativeTouchHandler.m; sourceTree = ""; }; @@ -580,6 +581,7 @@ FB89461B19F646E200339C8A /* StreamManager.m */, FB89461C19F646E200339C8A /* VideoDecoderRenderer.h */, FB89461D19F646E200339C8A /* VideoDecoderRenderer.m */, + 9803CCAB254F9EAF00EE185E /* ConnectionCallbacks.h */, ); path = Stream; sourceTree = ""; diff --git a/iPad.storyboard b/iPad.storyboard index c57de78..0a4a17c 100644 --- a/iPad.storyboard +++ b/iPad.storyboard @@ -322,56 +322,19 @@ - + - - - - - - - - - - - - - + - - - - - - + diff --git a/iPhone.storyboard b/iPhone.storyboard index 62fc8c7..e9e031c 100644 --- a/iPhone.storyboard +++ b/iPhone.storyboard @@ -1,6 +1,6 @@ - + @@ -12,7 +12,7 @@ - + @@ -60,7 +60,7 @@ - + @@ -82,7 +82,7 @@ - + @@ -383,11 +346,11 @@ - + - +