2014-08-31 13:13:46 -04:00
//
// StreamFrameViewController . m
2015-05-12 21:48:30 -04:00
// Moonlight
2014-08-31 13:13:46 -04:00
//
// Created by Diego Waxemberg on 1 / 18 / 14.
2015-05-12 21:48:30 -04:00
// Copyright ( c ) 2015 Moonlight Stream . All rights reserved .
2014-08-31 13:13:46 -04:00
//
# import "StreamFrameViewController.h"
# import "MainFrameViewController.h"
2014-10-18 22:43:52 -04:00
# import "VideoDecoderRenderer.h"
2014-10-20 19:20:50 -04:00
# import "StreamManager.h"
2014-10-20 21:13:13 -04:00
# import "ControllerSupport.h"
2014-08-31 13:13:46 -04:00
# include < sys / socket . h >
# include < netinet / in . h >
# include < arpa / inet . h >
2019-07-23 18:43:41 -04:00
# include < Limelight . h >
2014-08-31 13:13:46 -04:00
2014-10-20 21:13:13 -04:00
@ implementation StreamFrameViewController {
ControllerSupport * _controllerSupport ;
2014-10-21 01:54:31 -04:00
StreamManager * _streamMan ;
2018-06-09 16:29:48 -07:00
NSTimer * _inactivityTimer ;
2018-10-23 23:18:56 -04:00
UITapGestureRecognizer * _menuGestureRecognizer ;
UITapGestureRecognizer * _menuDoubleTapGestureRecognizer ;
2019-07-23 18:43:41 -04:00
UITextView * _overlayView ;
2019-10-22 00:16:20 -04:00
StreamView * _streamView ;
2019-11-02 15:17:52 -07:00
BOOL _userIsInteracting ;
2014-10-20 21:13:13 -04:00
}
2014-08-31 13:13:46 -04:00
2017-09-15 00:30:51 -07:00
- ( void ) viewDidAppear : ( BOOL ) animated
{
[ super viewDidAppear : animated ] ;
2018-08-26 11:31:20 -07:00
# if ! TARGET_OS _TV
2017-09-15 00:30:51 -07:00
[ [ self revealViewController ] setPrimaryViewController : self ] ;
2018-08-26 11:31:20 -07:00
# endif
2017-09-15 00:30:51 -07:00
}
2019-10-12 19:20:26 -07:00
# if TARGET_OS _TV
2018-10-23 23:18:56 -04:00
- ( void ) controllerPauseButtonPressed : ( id ) sender { }
- ( void ) controllerPauseButtonDoublePressed : ( id ) sender {
Log ( LOG_I , @ "Menu double-pressed -- backing out of stream" ) ;
[ self returnToMainFrame ] ;
}
# endif
2014-08-31 13:13:46 -04:00
- ( void ) viewDidLoad
{
[ super viewDidLoad ] ;
2014-10-21 17:18:30 -04:00
2014-10-29 00:47:51 -04:00
[ self . navigationController setNavigationBarHidden : YES animated : YES ] ;
2018-06-01 19:11:10 -07:00
[ self . stageLabel setText : [ NSString stringWithFormat : @ "Starting %@..." , self . streamConfig . appName ] ] ;
2014-10-26 02:15:53 -04:00
[ self . stageLabel sizeToFit ] ;
2018-06-01 19:11:10 -07:00
self . stageLabel . textAlignment = NSTextAlignmentCenter ;
2014-11-27 00:02:18 -05:00
self . stageLabel . center = CGPointMake ( self . view . frame . size . width / 2 , self . view . frame . size . height / 2 ) ;
2014-11-27 16:23:06 -05:00
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 ) ;
2014-08-31 13:13:46 -04:00
[ UIApplication sharedApplication ] . idleTimerDisabled = YES ;
2014-10-19 00:09:47 -04:00
2019-10-22 00:32:56 -04:00
_controllerSupport = [ [ ControllerSupport alloc ] initWithConfig : self . streamConfig presenceDelegate : self ] ;
2018-06-09 16:29:48 -07:00
_inactivityTimer = nil ;
2019-10-22 00:16:20 -04:00
_streamView = ( StreamView * ) self . view ;
2020-04-18 14:00:26 -07:00
[ _streamView setupStreamView : _controllerSupport swipeDelegate : self interactionDelegate : self config : self . streamConfig ] ;
2019-10-22 00:16:20 -04:00
2018-10-23 23:18:56 -04:00
# if TARGET_OS _TV
if ( ! _menuGestureRecognizer || ! _menuDoubleTapGestureRecognizer ) {
_menuGestureRecognizer = [ [ UITapGestureRecognizer alloc ] initWithTarget : self action : @ selector ( controllerPauseButtonPressed : ) ] ;
_menuGestureRecognizer . allowedPressTypes = @ [ @ ( UIPressTypeMenu ) ] ;
_menuDoubleTapGestureRecognizer = [ [ UITapGestureRecognizer alloc ] initWithTarget : self action : @ selector ( controllerPauseButtonDoublePressed : ) ] ;
_menuDoubleTapGestureRecognizer . numberOfTapsRequired = 2 ;
[ _menuGestureRecognizer requireGestureRecognizerToFail : _menuDoubleTapGestureRecognizer ] ;
_menuDoubleTapGestureRecognizer . allowedPressTypes = @ [ @ ( UIPressTypeMenu ) ] ;
}
[ self . view addGestureRecognizer : _menuGestureRecognizer ] ;
[ self . view addGestureRecognizer : _menuDoubleTapGestureRecognizer ] ;
# endif
2018-12-22 21:43:37 -08:00
# if TARGET_OS _TV
[ self . 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" ] ;
# endif
[ self . tipLabel sizeToFit ] ;
self . tipLabel . textAlignment = NSTextAlignmentCenter ;
2018-12-28 20:32:22 -08:00
self . tipLabel . center = CGPointMake ( self . view . frame . size . width / 2 , self . view . frame . size . height * 0.9 ) ;
2018-12-22 21:43:37 -08:00
2015-02-01 19:46:28 -05:00
_streamMan = [ [ StreamManager alloc ] initWithConfig : self . streamConfig
2014-10-21 14:54:10 -04:00
renderView : self . view
connectionCallbacks : self ] ;
2014-08-31 13:13:46 -04:00
NSOperationQueue * opQueue = [ [ NSOperationQueue alloc ] init ] ;
2014-10-21 01:54:31 -04:00
[ opQueue addOperation : _streamMan ] ;
[ [ NSNotificationCenter defaultCenter ] addObserver : self
selector : @ selector ( applicationWillResignActive : )
name : UIApplicationWillResignActiveNotification
object : nil ] ;
2018-06-09 16:29:48 -07:00
[ [ NSNotificationCenter defaultCenter ] addObserver : self
selector : @ selector ( applicationDidBecomeActive : )
name : UIApplicationDidBecomeActiveNotification
object : nil ] ;
[ [ NSNotificationCenter defaultCenter ] addObserver : self
selector : @ selector ( applicationDidEnterBackground : )
name : UIApplicationDidEnterBackgroundNotification
object : nil ] ;
}
2018-08-29 09:21:49 -07:00
- ( void ) willMoveToParentViewController : ( UIViewController * ) parent {
2018-12-28 02:03:21 -08:00
// Only cleanup when we ' re being destroyed
if ( parent = = nil ) {
[ _controllerSupport cleanup ] ;
[ UIApplication sharedApplication ] . idleTimerDisabled = NO ;
[ _streamMan stopStream ] ;
if ( _inactivityTimer ! = nil ) {
[ _inactivityTimer invalidate ] ;
_inactivityTimer = nil ;
}
[ [ NSNotificationCenter defaultCenter ] removeObserver : self ] ;
2018-06-09 16:29:48 -07:00
}
2014-10-21 01:54:31 -04:00
}
2019-07-23 18:43:41 -04:00
- ( void ) updateOverlayText : ( NSString * ) text {
if ( _overlayView = = nil ) {
_overlayView = [ [ UITextView alloc ] init ] ;
2019-10-12 19:20:26 -07:00
# if ! TARGET_OS _TV
2019-07-23 18:43:41 -04:00
[ _overlayView setEditable : NO ] ;
2019-08-07 00:53:58 -07:00
# endif
[ _overlayView setUserInteractionEnabled : NO ] ;
2019-07-23 18:43:41 -04:00
[ _overlayView setSelectable : NO ] ;
[ _overlayView setScrollEnabled : NO ] ;
[ _overlayView setTextAlignment : NSTextAlignmentCenter ] ;
[ _overlayView setTextColor : [ OSColor lightGrayColor ] ] ;
[ _overlayView setBackgroundColor : [ OSColor blackColor ] ] ;
2019-09-24 17:36:26 -07:00
# if TARGET_OS _TV
[ _overlayView setFont : [ UIFont systemFontOfSize : 24 ] ] ;
# else
2019-07-23 18:43:41 -04:00
[ _overlayView setFont : [ UIFont systemFontOfSize : 12 ] ] ;
2019-09-24 17:36:26 -07:00
# endif
2019-07-23 18:43:41 -04:00
[ _overlayView setAlpha : 0.5 ] ;
[ self . view addSubview : _overlayView ] ;
}
if ( text ! = nil ) {
[ _overlayView setText : text ] ;
[ _overlayView sizeToFit ] ;
[ _overlayView setCenter : CGPointMake ( self . view . frame . size . width / 2 , _overlayView . frame . size . height / 2 ) ] ;
[ _overlayView setHidden : NO ] ;
}
else {
[ _overlayView setHidden : YES ] ;
}
}
2014-10-29 00:47:51 -04:00
- ( void ) returnToMainFrame {
[ self . navigationController popToRootViewControllerAnimated : YES ] ;
}
2018-06-09 16:29:48 -07:00
// This will fire if the user opens control center or gets a low battery message
2014-10-21 01:54:31 -04:00
- ( void ) applicationWillResignActive : ( NSNotification * ) notification {
2018-06-09 16:29:48 -07:00
if ( _inactivityTimer ! = nil ) {
[ _inactivityTimer invalidate ] ;
}
2019-09-24 17:37:35 -07:00
# if TARGET_OS _TV
// Terminate the stream immediately on tvOS
Log ( LOG_I , @ "Terminating stream after resigning active" ) ;
[ self returnToMainFrame ] ;
# else
2018-06-09 16:29:48 -07:00
// Terminate the stream if the app is inactive for 10 seconds
Log ( LOG_I , @ "Starting inactivity termination timer" ) ;
_inactivityTimer = [ NSTimer scheduledTimerWithTimeInterval : 10
target : self
selector : @ selector ( inactiveTimerExpired : )
userInfo : nil
repeats : NO ] ;
2019-09-24 17:37:35 -07:00
# endif
2018-06-09 16:29:48 -07:00
}
- ( void ) inactiveTimerExpired : ( NSTimer * ) timer {
Log ( LOG_I , @ "Terminating stream after inactivity" ) ;
[ self returnToMainFrame ] ;
_inactivityTimer = nil ;
}
- ( void ) applicationDidBecomeActive : ( NSNotification * ) notification {
// Stop the background timer , since we ' re foregrounded again
if ( _inactivityTimer ! = nil ) {
Log ( LOG_I , @ "Stopping inactivity timer after becoming active again" ) ;
[ _inactivityTimer invalidate ] ;
_inactivityTimer = nil ;
}
}
// This fires when the home button is pressed
- ( void ) applicationDidEnterBackground : ( UIApplication * ) application {
Log ( LOG_I , @ "Terminating stream immediately for backgrounding" ) ;
if ( _inactivityTimer ! = nil ) {
[ _inactivityTimer invalidate ] ;
_inactivityTimer = nil ;
}
2014-10-29 00:47:51 -04:00
[ self returnToMainFrame ] ;
2014-08-31 13:13:46 -04:00
}
2015-01-09 15:22:12 -05:00
- ( void ) edgeSwiped {
2018-06-09 16:29:48 -07:00
Log ( LOG_I , @ "User swiped to end stream" ) ;
2015-01-09 15:22:12 -05:00
[ self returnToMainFrame ] ;
}
2014-10-21 14:54:10 -04:00
- ( void ) connectionStarted {
2015-02-10 21:26:55 -05:00
Log ( LOG_I , @ "Connection started" ) ;
2014-10-21 17:18:30 -04:00
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
2018-06-02 01:02:10 -07:00
// Leave the spinner spinning until it ' s obscured by
// the first frame of video .
self . stageLabel . hidden = YES ;
2018-12-22 21:43:37 -08:00
self . tipLabel . hidden = YES ;
2019-10-22 00:16:20 -04:00
[ self -> _streamView showOnScreenControls ] ;
2014-10-21 17:18:30 -04:00
} ) ;
2014-10-21 14:54:10 -04:00
}
2020-02-25 00:00:00 -08:00
- ( void ) connectionTerminated : ( int ) errorCode {
Log ( LOG_I , @ "Connection terminated: %d" , errorCode ) ;
2014-10-21 14:54:10 -04:00
2015-01-09 17:54:21 -05:00
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
2018-05-30 22:04:06 -07:00
// Allow the display to go to sleep now
[ UIApplication sharedApplication ] . idleTimerDisabled = NO ;
2019-02-14 00:03:09 -05:00
if ( errorCode = = 0 ) {
2015-01-09 17:54:21 -05:00
[ self returnToMainFrame ] ;
2019-02-14 00:03:09 -05:00
} else {
UIAlertController * conTermAlert = [ UIAlertController alertControllerWithTitle : @ "Connection Terminated" message : @ "The connection was terminated" preferredStyle : UIAlertControllerStyleAlert ] ;
[ conTermAlert addAction : [ UIAlertAction actionWithTitle : @ "OK" style : UIAlertActionStyleDefault handler : ^ ( UIAlertAction * action ) {
[ self returnToMainFrame ] ;
} ] ] ;
[ self presentViewController : conTermAlert animated : YES completion : nil ] ;
}
2015-01-09 17:54:21 -05:00
} ) ;
2016-04-12 00:50:59 -04:00
[ _streamMan stopStream ] ;
2014-10-21 01:02:04 -04:00
}
2016-07-31 17:10:32 -07:00
- ( void ) stageStarting : ( const char * ) stageName {
2015-02-10 21:26:55 -05:00
Log ( LOG_I , @ "Starting %s" , stageName ) ;
2014-10-21 17:18:30 -04:00
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 ] ;
2014-10-26 02:15:53 -04:00
[ self . stageLabel sizeToFit ] ;
self . stageLabel . center = CGPointMake ( self . view . frame . size . width / 2 , self . stageLabel . center . y ) ;
2014-10-21 17:18:30 -04:00
} ) ;
2014-10-21 14:54:10 -04:00
}
2016-07-31 17:10:32 -07:00
- ( void ) stageComplete : ( const char * ) stageName {
2014-10-21 14:54:10 -04:00
}
2020-02-25 00:00:00 -08:00
- ( void ) stageFailed : ( const char * ) stageName withError : ( int ) errorCode {
Log ( LOG_I , @ "Stage %s failed: %d" , stageName , errorCode ) ;
2015-06-23 21:46:58 -07:00
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
2018-05-30 22:04:06 -07:00
// Allow the display to go to sleep now
[ UIApplication sharedApplication ] . idleTimerDisabled = NO ;
2015-06-23 21:46:58 -07:00
UIAlertController * alert = [ UIAlertController alertControllerWithTitle : @ "Connection Failed"
2020-02-25 00:00:00 -08:00
message : [ NSString stringWithFormat : @ "%s failed with error %d" ,
2015-06-23 21:46:58 -07:00
stageName , errorCode ]
preferredStyle : UIAlertControllerStyleAlert ] ;
2018-08-29 09:36:51 -07:00
[ Utils addHelpOptionToDialog : alert ] ;
2018-05-28 18:20:59 -07:00
[ alert addAction : [ UIAlertAction actionWithTitle : @ "OK" style : UIAlertActionStyleDefault handler : ^ ( UIAlertAction * action ) {
2015-06-23 21:46:58 -07:00
[ self returnToMainFrame ] ;
} ] ] ;
[ self presentViewController : alert animated : YES completion : nil ] ;
} ) ;
2015-10-17 03:45:20 -07:00
2016-04-12 00:50:59 -04:00
[ _streamMan stopStream ] ;
2014-10-21 14:54:10 -04:00
}
2014-10-23 23:41:07 -04:00
- ( void ) launchFailed : ( NSString * ) message {
2015-06-23 21:46:58 -07:00
Log ( LOG_I , @ "Launch failed: %@" , message ) ;
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
2018-05-30 22:04:06 -07:00
// Allow the display to go to sleep now
[ UIApplication sharedApplication ] . idleTimerDisabled = NO ;
2019-10-12 19:20:53 -07:00
UIAlertController * alert = [ UIAlertController alertControllerWithTitle : @ "Connection Error"
2015-06-23 21:46:58 -07:00
message : message
preferredStyle : UIAlertControllerStyleAlert ] ;
2018-08-29 09:36:51 -07:00
[ Utils addHelpOptionToDialog : alert ] ;
2018-05-28 18:20:59 -07:00
[ alert addAction : [ UIAlertAction actionWithTitle : @ "OK" style : UIAlertActionStyleDefault handler : ^ ( UIAlertAction * action ) {
2015-06-23 21:46:58 -07:00
[ self returnToMainFrame ] ;
} ] ] ;
[ self presentViewController : alert animated : YES completion : nil ] ;
} ) ;
2014-10-21 14:54:10 -04:00
}
2019-02-11 19:22:18 -08:00
- ( void ) rumble : ( unsigned short ) controllerNumber lowFreqMotor : ( unsigned short ) lowFreqMotor highFreqMotor : ( unsigned short ) highFreqMotor {
Log ( LOG_I , @ "Rumble on gamepad %d: %04x %04x" , controllerNumber , lowFreqMotor , highFreqMotor ) ;
[ _controllerSupport rumble : controllerNumber lowFreqMotor : lowFreqMotor highFreqMotor : highFreqMotor ] ;
}
2019-07-23 18:43:41 -04:00
- ( void ) connectionStatusUpdate : ( int ) status {
Log ( LOG_W , @ "Connection status update: %d" , status ) ;
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
switch ( status ) {
case CONN_STATUS _OKAY :
[ self updateOverlayText : nil ] ;
break ;
case CONN_STATUS _POOR :
if ( self -> _streamConfig . bitRate > 5000 ) {
[ self updateOverlayText : @ "Slow connection to PC\nReduce your bitrate" ] ;
}
else {
[ self updateOverlayText : @ "Poor connection to PC" ] ;
}
break ;
}
} ) ;
}
2014-08-31 13:13:46 -04:00
- ( void ) didReceiveMemoryWarning
{
[ super didReceiveMemoryWarning ] ;
// Dispose of any resources that can be recreated .
}
2019-10-22 00:32:56 -04:00
- ( void ) gamepadPresenceChanged {
# if ! TARGET_OS _TV
if ( @ available ( iOS 11.0 , * ) ) {
[ self setNeedsUpdateOfHomeIndicatorAutoHidden ] ;
}
# endif
}
2019-11-02 15:17:52 -07:00
- ( void ) userInteractionBegan {
// Disable hiding home bar when user is interacting .
// iOS will force it to be shown anyway , but it will
// also discard our edges deferring system gestures unless
// we willingly give up home bar hiding preference .
_userIsInteracting = YES ;
# if ! TARGET_OS _TV
if ( @ available ( iOS 11.0 , * ) ) {
[ self setNeedsUpdateOfHomeIndicatorAutoHidden ] ;
}
# endif
}
- ( void ) userInteractionEnded {
// Enable home bar hiding again if conditions allow
_userIsInteracting = NO ;
# if ! TARGET_OS _TV
if ( @ available ( iOS 11.0 , * ) ) {
[ self setNeedsUpdateOfHomeIndicatorAutoHidden ] ;
}
# endif
}
2018-08-26 11:31:20 -07:00
# if ! TARGET_OS _TV
2017-09-15 00:30:51 -07:00
// Require a confirmation when streaming to activate a system gesture
- ( UIRectEdge ) preferredScreenEdgesDeferringSystemGestures {
return UIRectEdgeAll ;
}
2019-10-22 00:32:56 -04:00
- ( BOOL ) prefersHomeIndicatorAutoHidden {
if ( [ _controllerSupport getConnectedGamepadCount ] > 0 &&
2019-11-02 15:17:52 -07:00
[ _streamView getCurrentOscState ] = = OnScreenControlsLevelOff &&
_userIsInteracting = = NO ) {
2019-10-22 00:32:56 -04:00
// Autohide the home bar when a gamepad is connected
// and the on - screen controls are disabled . We can ' t
// do this all the time because any touch on the display
// will cause the home indicator to reappear , and our
// preferredScreenEdgesDeferringSystemGestures will also
// be suppressed ( leading to possible errant exits of the
// stream ) .
return YES ;
}
return NO ;
}
2014-10-20 21:39:40 -04:00
- ( BOOL ) shouldAutorotate {
return YES ;
}
2018-08-26 11:31:20 -07:00
# endif
2014-08-31 13:13:46 -04:00
@ end