Create stream view hierarchy programmatically

This commit is contained in:
Cameron Gutman
2020-11-01 20:00:39 -06:00
parent b799978cac
commit 0d75dd4efb
13 changed files with 111 additions and 163 deletions

View File

@@ -30,8 +30,6 @@
@interface StreamView : UIView <X1KitMouseDelegate, UITextFieldDelegate, UIPointerInteractionDelegate>
#endif
@property (nonatomic, retain) IBOutlet UITextField* keyInputField;
- (void) setupStreamView:(ControllerSupport*)controllerSupport
swipeDelegate:(id<EdgeDetectionDelegate>)swipeDelegate
interactionDelegate:(id<UserInteractionDelegate>)interactionDelegate

View File

@@ -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;
}

View File

@@ -19,19 +19,6 @@ typedef struct {
int networkDroppedFrames;
} video_stats_t;
@protocol ConnectionCallbacks <NSObject>
- (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 <NSStreamDelegate>
-(id) initWithConfig:(StreamConfiguration*)config renderer:(VideoDecoderRenderer*)myRenderer connectionCallbacks:(id<ConnectionCallbacks>)callbacks;

View File

@@ -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 <NSObject>
- (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

View File

@@ -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];

View File

@@ -8,9 +8,11 @@
@import AVFoundation;
#import "ConnectionCallbacks.h"
@interface VideoDecoderRenderer : NSObject
- (id)initWithView:(UIView*)view;
- (id)initWithView:(UIView*)view callbacks:(id<ConnectionCallbacks>)callbacks;
- (void)setupWithVideoFormat:(int)videoFormat refreshRate:(int)refreshRate;

View File

@@ -13,6 +13,7 @@
@implementation VideoDecoderRenderer {
StreamView* _view;
id<ConnectionCallbacks> _callbacks;
AVSampleBufferDisplayLayer* displayLayer;
Boolean waitingForSps, waitingForPps, waitingForVps;
@@ -61,11 +62,12 @@
}
}
- (id)initWithView:(StreamView*)view
- (id)initWithView:(StreamView*)view callbacks:(id<ConnectionCallbacks>)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

View File

@@ -19,9 +19,6 @@
#else
@interface StreamFrameViewController : UIViewController <ConnectionCallbacks, EdgeDetectionDelegate, InputPresenceDelegate, UserInteractionDelegate>
#endif
@property (strong, nonatomic) IBOutlet UILabel *stageLabel;
@property (strong, nonatomic) IBOutlet UILabel *tipLabel;
@property (strong, nonatomic) IBOutlet UIActivityIndicatorView *spinner;
@property (nonatomic) StreamConfiguration* streamConfig;
@end

View File

@@ -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];