2014-10-29 00:47:51 -04:00
//
// SettingsViewController . m
2015-05-12 21:48:30 -04:00
// Moonlight
2014-10-29 00:47:51 -04:00
//
// Created by Diego Waxemberg on 10 / 27 / 14.
2015-05-12 21:48:30 -04:00
// Copyright ( c ) 2014 Moonlight Stream . All rights reserved .
2014-10-29 00:47:51 -04:00
//
# import "SettingsViewController.h"
2015-12-02 00:25:38 -08:00
# import "TemporarySettings.h"
2014-10-29 00:47:51 -04:00
# import "DataManager.h"
2019-06-05 18:41:30 -07:00
# import < VideoToolbox / VideoToolbox . h >
2014-10-29 00:47:51 -04:00
@ implementation SettingsViewController {
NSInteger _bitrate ;
}
2019-08-30 19:55:49 -07:00
@ dynamic overrideUserInterfaceStyle ;
2015-02-24 00:02:37 -05:00
static NSString * bitrateFormat = @ "Bitrate: %.1f Mbps" ;
2018-06-02 19:09:38 -07:00
static const int bitrateTable [ ] = {
500 ,
1000 ,
1500 ,
2000 ,
2500 ,
3000 ,
4000 ,
5000 ,
6000 ,
7000 ,
8000 ,
9000 ,
10000 ,
12000 ,
15000 ,
18000 ,
20000 ,
30000 ,
40000 ,
2018-10-22 19:27:49 -07:00
50000 ,
60000 ,
70000 ,
80000 ,
2020-10-28 01:04:34 +08:00
100000 ,
120000 ,
150000 ,
2018-06-02 19:09:38 -07:00
} ;
2022-02-05 17:55:32 +01:00
const int RESOLUTION_TABLE _SIZE = 5 ;
const int RESOLUTION_TABLE _CUSTOM _INDEX = RESOLUTION_TABLE _SIZE - 1 ;
const int RESOLUTION_TABLE _DEFAULT _INDEX = 1 ;
CGSize resolutionTable [ RESOLUTION_TABLE _SIZE ] ;
2018-06-02 19:09:38 -07:00
- ( int ) getSliderValueForBitrate : ( NSInteger ) bitrate {
int i ;
for ( i = 0 ; i < ( sizeof ( bitrateTable ) / sizeof ( * bitrateTable ) ) ; i + + ) {
if ( bitrate <= bitrateTable [ i ] ) {
return i ;
}
}
// Return the last entry in the table
return i - 1 ;
}
2014-10-29 00:47:51 -04:00
2022-02-06 18:48:17 -06:00
// This view is rooted at a ScrollView . To make it scrollable ,
// we ' ll update content size here .
2018-05-28 11:39:04 -07:00
- ( void ) viewDidLayoutSubviews {
2022-02-06 18:48:17 -06:00
CGFloat highestViewY = 0 ;
// Enumerate the scroll view ' s subviews looking for the
// highest view Y value to set our scroll view ' s content
// size .
for ( UIView * view in self . scrollView . subviews ) {
// UIScrollViews have 2 default child views
// which represent the horizontal and vertical scrolling
// indicators . Ignore any views we don ' t recognize .
if ( ! [ view isKindOfClass : [ UILabel class ] ] &&
! [ view isKindOfClass : [ UISegmentedControl class ] ] &&
! [ view isKindOfClass : [ UISlider class ] ] ) {
continue ;
2018-05-28 11:39:04 -07:00
}
2022-02-06 18:48:17 -06:00
CGFloat currentViewY = view . frame . origin . y + view . frame . size . height ;
if ( currentViewY > highestViewY ) {
highestViewY = currentViewY ;
}
2018-05-28 11:39:04 -07:00
}
2022-02-06 18:48:17 -06:00
// Add a bit of padding so the view doesn ' t end right at the button of the display
self . scrollView . contentSize = CGSizeMake ( self . scrollView . contentSize . width ,
highestViewY + 20 ) ;
2019-09-01 12:26:39 -07:00
}
// Adjust the subviews for the safe area on the iPhone X .
- ( void ) viewSafeAreaInsetsDidChange {
[ super viewSafeAreaInsetsDidChange ] ;
2017-09-14 23:12:52 -07:00
2019-09-01 12:26:39 -07:00
if ( @ available ( iOS 11.0 , * ) ) {
for ( UIView * view in self . view . subviews ) {
// HACK : The official safe area is much too large for our purposes
// so we ' ll just use the presence of any safe area to indicate we should
// pad by 20.
if ( self . view . safeAreaInsets . left >= 20 || self . view . safeAreaInsets . right >= 20 ) {
view . frame = CGRectMake ( view . frame . origin . x + 20 , view . frame . origin . y , view . frame . size . width , view . frame . size . height ) ;
2017-09-14 23:12:52 -07:00
}
}
}
}
2014-10-29 00:47:51 -04:00
- ( void ) viewDidLoad {
[ super viewDidLoad ] ;
2019-08-30 17:16:56 -07:00
// Always run settings in dark mode because we want the light fonts
2019-08-30 19:55:49 -07:00
if ( @ available ( iOS 13.0 , tvOS 13.0 , * ) ) {
2019-08-30 17:16:56 -07:00
self . overrideUserInterfaceStyle = UIUserInterfaceStyleDark ;
}
2014-10-29 00:47:51 -04:00
DataManager * dataMan = [ [ DataManager alloc ] init ] ;
2015-12-02 00:25:38 -08:00
TemporarySettings * currentSettings = [ dataMan getSettings ] ;
2014-10-29 00:47:51 -04:00
2018-06-02 19:09:38 -07:00
// Ensure we pick a bitrate that falls exactly onto a slider notch
_bitrate = bitrateTable [ [ self getSliderValueForBitrate : [ currentSettings . bitrate intValue ] ] ] ;
2022-02-05 17:55:32 +01:00
resolutionTable [ 0 ] = CGSizeMake ( 640 , 360 ) ;
resolutionTable [ 1 ] = CGSizeMake ( 1280 , 720 ) ;
resolutionTable [ 2 ] = CGSizeMake ( 1920 , 1080 ) ;
resolutionTable [ 3 ] = CGSizeMake ( 3840 , 2160 ) ;
resolutionTable [ 4 ] = CGSizeMake ( [ currentSettings . width integerValue ] , [ currentSettings . height integerValue ] ) ; // custom initial value
2018-10-22 20:24:51 -07:00
NSInteger framerate ;
switch ( [ currentSettings . framerate integerValue ] ) {
case 30 :
framerate = 0 ;
break ;
default :
case 60 :
framerate = 1 ;
break ;
case 120 :
framerate = 2 ;
break ;
}
2022-02-05 17:55:32 +01:00
NSInteger resolution = RESOLUTION_TABLE _DEFAULT _INDEX ;
for ( int i = 0 ; i < RESOLUTION_TABLE _SIZE ; i + + ) {
if ( ( int ) resolutionTable [ i ] . height = = [ currentSettings . height intValue ]
&& ( int ) resolutionTable [ i ] . width = = [ currentSettings . width intValue ] ) {
resolution = i ;
2019-05-27 10:37:26 -07:00
break ;
2022-02-05 17:55:32 +01:00
}
2022-02-06 19:04:18 -06:00
}
2018-10-22 20:24:51 -07:00
// Only show the 120 FPS option if we have a > 60 - ish Hz display
bool enable120Fps = false ;
2019-06-05 18:59:40 -07:00
if ( @ available ( iOS 10.3 , tvOS 10.3 , * ) ) {
2018-10-22 20:24:51 -07:00
if ( [ UIScreen mainScreen ] . maximumFramesPerSecond > 62 ) {
enable120Fps = true ;
}
}
if ( ! enable120Fps ) {
[ self . framerateSelector removeSegmentAtIndex : 2 animated : NO ] ;
2015-01-10 11:44:22 -05:00
}
2018-05-28 13:50:20 -07:00
2019-06-05 18:59:40 -07:00
// Only show the 4 K option for "recent" devices . We ' ll judge that by whether
// they support HEVC decoding ( A9 or later ) .
if ( @ available ( iOS 11.0 , tvOS 11.0 , * ) ) {
if ( ! VTIsHardwareDecodeSupported ( kCMVideoCodecType_HEVC ) ) {
2022-02-05 17:55:32 +01:00
[ self . resolutionSelector removeSegmentAtIndex : 3 animated : NO ] ;
if ( resolution >= 3 ) resolution - - ;
2019-06-05 18:59:40 -07:00
}
}
else {
2022-02-05 17:55:32 +01:00
[ self . resolutionSelector removeSegmentAtIndex : 3 animated : NO ] ;
if ( resolution >= 3 ) resolution - - ;
2019-06-05 18:59:40 -07:00
}
2022-02-05 17:55:32 +01:00
2019-06-05 18:41:30 -07:00
// Disable the HEVC selector if HEVC is not supported by the hardware
2019-06-05 18:59:40 -07:00
// or the version of iOS . See comment in Connection . m for reasoning behind
// the iOS 11.3 check .
2019-06-05 18:41:30 -07:00
if ( @ available ( iOS 11.3 , tvOS 11.3 , * ) ) {
if ( VTIsHardwareDecodeSupported ( kCMVideoCodecType_HEVC ) ) {
[ self . hevcSelector setSelectedSegmentIndex : currentSettings . useHevc ? 1 : 0 ] ;
}
else {
[ self . hevcSelector removeAllSegments ] ;
[ self . hevcSelector insertSegmentWithTitle : @ "Unsupported on this device" atIndex : 0 animated : NO ] ;
[ self . hevcSelector setEnabled : NO ] ;
}
}
else {
[ self . hevcSelector removeAllSegments ] ;
[ self . hevcSelector insertSegmentWithTitle : @ "Requires iOS 11.3 or later" atIndex : 0 animated : NO ] ;
[ self . hevcSelector setEnabled : NO ] ;
}
2020-11-01 11:50:00 -06:00
[ self . touchModeSelector setSelectedSegmentIndex : currentSettings . absoluteTouchMode ? 1 : 0 ] ;
[ self . touchModeSelector addTarget : self action : @ selector ( touchModeChanged ) forControlEvents : UIControlEventValueChanged ] ;
[ self . statsOverlaySelector setSelectedSegmentIndex : currentSettings . statsOverlay ? 1 : 0 ] ;
2020-03-27 20:26:01 -07:00
[ self . btMouseSelector setSelectedSegmentIndex : currentSettings . btMouseSupport ? 1 : 0 ] ;
2018-06-02 18:13:32 -07:00
[ self . optimizeSettingsSelector setSelectedSegmentIndex : currentSettings . optimizeGames ? 1 : 0 ] ;
2022-02-02 13:37:07 -03:00
[ self . framePacingSelector setSelectedSegmentIndex : currentSettings . useFramePacing ? 1 : 0 ] ;
2018-06-02 18:13:32 -07:00
[ self . multiControllerSelector setSelectedSegmentIndex : currentSettings . multiController ? 1 : 0 ] ;
[ self . audioOnPCSelector setSelectedSegmentIndex : currentSettings . playAudioOnPC ? 1 : 0 ] ;
2014-12-29 13:03:07 -05:00
NSInteger onscreenControls = [ currentSettings . onscreenControls integerValue ] ;
2014-10-29 00:47:51 -04:00
[ self . resolutionSelector setSelectedSegmentIndex : resolution ] ;
2022-02-05 17:55:32 +01:00
[ self . resolutionSelector addTarget : self action : @ selector ( newResolutionChosen ) forControlEvents : UIControlEventValueChanged ] ;
2014-10-29 00:47:51 -04:00
[ self . framerateSelector setSelectedSegmentIndex : framerate ] ;
2022-02-05 17:55:32 +01:00
[ self . framerateSelector addTarget : self action : @ selector ( updateBitrate ) forControlEvents : UIControlEventValueChanged ] ;
2014-12-29 13:03:07 -05:00
[ self . onscreenControlSelector setSelectedSegmentIndex : onscreenControls ] ;
2020-11-01 11:50:00 -06:00
[ self . onscreenControlSelector setEnabled : ! currentSettings . absoluteTouchMode ] ;
2018-06-02 19:09:38 -07:00
[ self . bitrateSlider setMinimumValue : 0 ] ;
[ self . bitrateSlider setMaximumValue : ( sizeof ( bitrateTable ) / sizeof ( * bitrateTable ) ) - 1 ] ;
[ self . bitrateSlider setValue : [ self getSliderValueForBitrate : _bitrate ] animated : YES ] ;
2014-10-29 00:47:51 -04:00
[ self . bitrateSlider addTarget : self action : @ selector ( bitrateSliderMoved ) forControlEvents : UIControlEventValueChanged ] ;
2015-02-24 00:02:37 -05:00
[ self updateBitrateText ] ;
2022-02-05 17:55:32 +01:00
[ self updateCustomResolutionText ] ;
2014-10-29 00:47:51 -04:00
}
2020-11-01 11:50:00 -06:00
- ( void ) touchModeChanged {
// Disable on - screen controls in absolute touch mode
[ self . onscreenControlSelector setEnabled : [ self . touchModeSelector selectedSegmentIndex ] = = 0 ] ;
}
2022-02-05 17:55:32 +01:00
- ( void ) updateBitrate {
2018-10-22 20:24:51 -07:00
NSInteger fps = [ self getChosenFrameRate ] ;
NSInteger width = [ self getChosenStreamWidth ] ;
NSInteger height = [ self getChosenStreamHeight ] ;
2015-11-11 18:11:33 -08:00
NSInteger defaultBitrate ;
2018-10-22 20:24:51 -07:00
// This table prefers 16 : 10 resolutions because they are
// only slightly more pixels than the 16 : 9 equivalents , so
// we don ' t want to bump those 16 : 10 resolutions up to the
// next 16 : 9 slot .
//
// This logic is shamelessly stolen from Moonlight Qt :
// https : // github . com / moonlight - stream / moonlight - qt / blob / master / app / settings / streamingpreferences . cpp
if ( width * height <= 640 * 360 ) {
defaultBitrate = 1000 * ( fps / 30.0 ) ;
2015-11-11 18:11:33 -08:00
}
2018-10-22 20:24:51 -07:00
// This covers 1280 x720 and 1280 x800 too
else if ( width * height <= 1366 * 768 ) {
defaultBitrate = 5000 * ( fps / 30.0 ) ;
2015-11-11 18:11:33 -08:00
}
2018-10-22 20:24:51 -07:00
else if ( width * height <= 1920 * 1200 ) {
defaultBitrate = 10000 * ( fps / 30.0 ) ;
2015-11-11 18:11:33 -08:00
}
2018-10-22 20:24:51 -07:00
else if ( width * height <= 2560 * 1600 ) {
defaultBitrate = 20000 * ( fps / 30.0 ) ;
2018-05-28 13:50:20 -07:00
}
2018-10-22 20:24:51 -07:00
else / * if ( width * height <= 3840 * 2160 ) * / {
defaultBitrate = 40000 * ( fps / 30.0 ) ;
2018-05-28 13:50:20 -07:00
}
2015-11-11 18:11:33 -08:00
2018-06-02 19:09:38 -07:00
// We should always be exactly on a slider position with default bitrates
2019-05-31 15:04:20 +09:30
_bitrate = MIN ( defaultBitrate , 100000 ) ;
2018-06-02 19:09:38 -07:00
assert ( bitrateTable [ [ self getSliderValueForBitrate : _bitrate ] ] = = _bitrate ) ;
[ self . bitrateSlider setValue : [ self getSliderValueForBitrate : _bitrate ] animated : YES ] ;
2015-11-11 18:11:33 -08:00
[ self updateBitrateText ] ;
}
2022-02-05 17:55:32 +01:00
- ( void ) newResolutionChosen {
BOOL lastSegmentSelected = [ self . resolutionSelector selectedSegmentIndex ] + 1 = = [ self . resolutionSelector numberOfSegments ] ;
if ( lastSegmentSelected ) {
[ self promptCustomResolutionDialog ] ;
}
[ self updateBitrate ] ;
}
- ( void ) promptCustomResolutionDialog {
UIAlertController * alertController = [ UIAlertController alertControllerWithTitle : @ "Custom resolution" message : @ "Choose a custom width and height" preferredStyle : UIAlertControllerStyleAlert ] ;
[ alertController addTextFieldWithConfigurationHandler : ^ ( UITextField * textField ) {
textField . placeholder = @ "width ex: 1920" ;
textField . clearButtonMode = UITextFieldViewModeAlways ;
textField . borderStyle = UITextBorderStyleRoundedRect ;
textField . keyboardType = UIKeyboardTypeNumberPad ;
textField . text = [ NSString stringWithFormat : @ "%d" , ( int ) resolutionTable [ RESOLUTION_TABLE _CUSTOM _INDEX ] . width ] ;
} ] ;
[ alertController addTextFieldWithConfigurationHandler : ^ ( UITextField * textField ) {
textField . placeholder = @ "height ex: 1080" ;
textField . clearButtonMode = UITextFieldViewModeAlways ;
textField . borderStyle = UITextBorderStyleRoundedRect ;
textField . keyboardType = UIKeyboardTypeNumberPad ;
textField . text = [ NSString stringWithFormat : @ "%d" , ( int ) resolutionTable [ RESOLUTION_TABLE _CUSTOM _INDEX ] . height ] ;
} ] ;
[ alertController addAction : [ UIAlertAction actionWithTitle : @ "OK" style : UIAlertActionStyleDefault handler : ^ ( UIAlertAction * action ) {
NSArray * textfields = alertController . textFields ;
UITextField * widthField = textfields [ 0 ] ;
UITextField * heightField = textfields [ 1 ] ;
resolutionTable [ RESOLUTION_TABLE _CUSTOM _INDEX ] = CGSizeMake (
[ widthField . text integerValue ] ,
[ heightField . text integerValue ]
) ;
[ self updateBitrate ] ;
[ self updateCustomResolutionText ] ;
} ] ] ;
[ alertController addAction : [ UIAlertAction actionWithTitle : @ "Cancel" style : UIAlertActionStyleCancel handler : ^ ( UIAlertAction * action ) {
[ self . resolutionSelector setSelectedSegmentIndex : RESOLUTION_TABLE _DEFAULT _INDEX ] ;
[ self updateBitrate ] ;
} ] ] ;
[ self presentViewController : alertController animated : YES completion : nil ] ;
}
- ( void ) updateCustomResolutionText {
BOOL customResolutionEqualsDefaultResolution = resolutionTable [ RESOLUTION_TABLE _DEFAULT _INDEX ] . width = = resolutionTable [ RESOLUTION_TABLE _CUSTOM _INDEX ] . width && resolutionTable [ RESOLUTION_TABLE _DEFAULT _INDEX ] . height = = resolutionTable [ RESOLUTION_TABLE _CUSTOM _INDEX ] . height ;
if ( ! customResolutionEqualsDefaultResolution ) {
NSString * newTitle = [ NSString stringWithFormat : @ "Custom %dx%d" , ( int ) resolutionTable [ RESOLUTION_TABLE _CUSTOM _INDEX ] . width , ( int ) resolutionTable [ RESOLUTION_TABLE _CUSTOM _INDEX ] . height ] ;
[ self . resolutionSelector setTitle : newTitle forSegmentAtIndex : [ self . resolutionSelector numberOfSegments ] - 1 ] ;
2022-02-05 18:09:43 +01:00
self . resolutionSelector . apportionsSegmentWidthsByContent = YES ; // to update the width
2022-02-05 17:55:32 +01:00
}
}
2014-10-29 00:47:51 -04:00
- ( void ) bitrateSliderMoved {
2018-06-02 19:09:38 -07:00
assert ( self . bitrateSlider . value < ( sizeof ( bitrateTable ) / sizeof ( * bitrateTable ) ) ) ;
_bitrate = bitrateTable [ ( int ) self . bitrateSlider . value ] ;
2015-02-24 00:02:37 -05:00
[ self updateBitrateText ] ;
2014-10-29 00:47:51 -04:00
}
2015-02-24 00:02:37 -05:00
- ( void ) updateBitrateText {
// Display bitrate in Mbps
[ self . bitrateLabel setText : [ NSString stringWithFormat : bitrateFormat , _bitrate / 1000. ] ] ;
}
2014-10-29 00:47:51 -04:00
2015-11-11 18:11:33 -08:00
- ( NSInteger ) getChosenFrameRate {
2018-10-22 20:24:51 -07:00
switch ( [ self . framerateSelector selectedSegmentIndex ] ) {
case 0 :
return 30 ;
case 1 :
return 60 ;
case 2 :
return 120 ;
default :
abort ( ) ;
}
2015-11-11 18:11:33 -08:00
}
- ( NSInteger ) getChosenStreamHeight {
2022-02-05 17:55:32 +01:00
// because the 4 k resolution can be removed
BOOL lastSegmentSelected = [ self . resolutionSelector selectedSegmentIndex ] + 1 = = [ self . resolutionSelector numberOfSegments ] ;
if ( lastSegmentSelected ) {
return resolutionTable [ RESOLUTION_TABLE _CUSTOM _INDEX ] . height ;
}
2022-02-06 19:04:18 -06:00
2022-02-05 17:55:32 +01:00
return resolutionTable [ [ self . resolutionSelector selectedSegmentIndex ] ] . height ;
2015-11-11 18:11:33 -08:00
}
- ( NSInteger ) getChosenStreamWidth {
2022-02-05 17:55:32 +01:00
// because the 4 k resolution can be removed
BOOL lastSegmentSelected = [ self . resolutionSelector selectedSegmentIndex ] + 1 = = [ self . resolutionSelector numberOfSegments ] ;
if ( lastSegmentSelected ) {
return resolutionTable [ RESOLUTION_TABLE _CUSTOM _INDEX ] . width ;
2022-02-06 19:04:18 -06:00
}
2022-02-05 17:55:32 +01:00
return resolutionTable [ [ self . resolutionSelector selectedSegmentIndex ] ] . width ;
2015-11-11 18:11:33 -08:00
}
2014-10-29 00:47:51 -04:00
- ( void ) saveSettings {
DataManager * dataMan = [ [ DataManager alloc ] init ] ;
2015-11-11 18:11:33 -08:00
NSInteger framerate = [ self getChosenFrameRate ] ;
NSInteger height = [ self getChosenStreamHeight ] ;
NSInteger width = [ self getChosenStreamWidth ] ;
2014-12-31 19:13:54 -05:00
NSInteger onscreenControls = [ self . onscreenControlSelector selectedSegmentIndex ] ;
2018-06-02 18:13:32 -07:00
BOOL optimizeGames = [ self . optimizeSettingsSelector selectedSegmentIndex ] = = 1 ;
BOOL multiController = [ self . multiControllerSelector selectedSegmentIndex ] = = 1 ;
BOOL audioOnPC = [ self . audioOnPCSelector selectedSegmentIndex ] = = 1 ;
BOOL useHevc = [ self . hevcSelector selectedSegmentIndex ] = = 1 ;
2020-03-27 20:26:01 -07:00
BOOL btMouseSupport = [ self . btMouseSelector selectedSegmentIndex ] = = 1 ;
2022-02-02 13:37:07 -03:00
BOOL useFramePacing = [ self . framePacingSelector selectedSegmentIndex ] = = 1 ;
2020-11-01 11:50:00 -06:00
BOOL absoluteTouchMode = [ self . touchModeSelector selectedSegmentIndex ] = = 1 ;
BOOL statsOverlay = [ self . statsOverlaySelector selectedSegmentIndex ] = = 1 ;
2018-06-02 18:13:32 -07:00
[ dataMan saveSettingsWithBitrate : _bitrate
framerate : framerate
height : height
width : width
2022-02-06 17:59:32 -06:00
audioConfig : 2 // Stereo
2018-06-02 18:13:32 -07:00
onscreenControls : onscreenControls
optimizeGames : optimizeGames
multiController : multiController
audioOnPC : audioOnPC
useHevc : useHevc
2022-02-02 13:37:07 -03:00
useFramePacing : useFramePacing
2020-03-27 20:26:01 -07:00
enableHdr : NO
2020-11-01 11:50:00 -06:00
btMouseSupport : btMouseSupport
absoluteTouchMode : absoluteTouchMode
statsOverlay : statsOverlay ] ;
2014-10-29 00:47:51 -04:00
}
- ( void ) didReceiveMemoryWarning {
[ super didReceiveMemoryWarning ] ;
// Dispose of any resources that can be recreated .
}
# pragma mark - Navigation
- ( void ) prepareForSegue : ( UIStoryboardSegue * ) segue sender : ( id ) sender {
}
@ end