Merge branch 'piptest' into Integration
- PiP & its toolbox special entry - host update timer # Conflicts: # VoidLink/ViewControllers/MainFrameViewController.m # VoidLink/ViewControllers/StreamFrameViewController.m
This commit is contained in:
commit
0f8ba42975
36 files changed
+595
-136
No files matched your search
@@ -570,7 +570,7 @@ ICAgICAgICAgICAgIA
|
|||||||
<color key="selectedSegmentTintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="selectedSegmentTintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
</segmentedControl>
|
</segmentedControl>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Local Mouse Pointer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VGn-Oc-mKc">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Local Mouse Pointer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VGn-Oc-mKc">
|
||||||
<rect key="frame" x="22" y="2007" width="165" height="21"/>
|
<rect key="frame" x="20" y="2008" width="165" height="21"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
@@ -635,6 +635,23 @@ ICAgICAgICAgICAgIA
|
|||||||
<action selector="backToStreamingButtonTapped:" destination="rYd-e6-cQU" eventType="touchUpInside" id="9P0-Yw-T4W"/>
|
<action selector="backToStreamingButtonTapped:" destination="rYd-e6-cQU" eventType="touchUpInside" id="9P0-Yw-T4W"/>
|
||||||
</connections>
|
</connections>
|
||||||
</button>
|
</button>
|
||||||
|
<segmentedControl opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="dcP-yB-6Un" userLabel="PiP Selector">
|
||||||
|
<rect key="frame" x="18" y="2102" width="459" height="28"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<segments>
|
||||||
|
<segment title="No"/>
|
||||||
|
<segment title="Yes"/>
|
||||||
|
</segments>
|
||||||
|
<color key="tintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<color key="selectedSegmentTintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
</segmentedControl>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Enable PiP" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ELc-8r-KSB">
|
||||||
|
<rect key="frame" x="20" y="2075" width="86" height="19"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
|
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
</subviews>
|
</subviews>
|
||||||
<color key="backgroundColor" red="0.12156862745098039" green="0.12941176470588237" blue="0.14117647058823529" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="backgroundColor" red="0.12156862745098039" green="0.12941176470588237" blue="0.14117647058823529" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
</view>
|
</view>
|
||||||
@@ -664,6 +681,7 @@ ICAgICAgICAgICAgIA
|
|||||||
<outlet property="optimizeSettingsSelector" destination="Gob-Lu-b1y" id="9Wl-yp-0Ou"/>
|
<outlet property="optimizeSettingsSelector" destination="Gob-Lu-b1y" id="9Wl-yp-0Ou"/>
|
||||||
<outlet property="oscTapExlusionAreaSizeLabel" destination="Dfl-gL-00M" id="Jbg-64-P3M"/>
|
<outlet property="oscTapExlusionAreaSizeLabel" destination="Dfl-gL-00M" id="Jbg-64-P3M"/>
|
||||||
<outlet property="oscTapExlusionAreaSizeSlider" destination="TSt-Hj-uTR" id="h9l-ap-AVd"/>
|
<outlet property="oscTapExlusionAreaSizeSlider" destination="TSt-Hj-uTR" id="h9l-ap-AVd"/>
|
||||||
|
<outlet property="pipSelector" destination="dcP-yB-6Un" id="rrB-dR-2Bq"/>
|
||||||
<outlet property="pointerVelocityModeDividerSlider" destination="xet-C9-l5y" id="GnQ-xU-e0N"/>
|
<outlet property="pointerVelocityModeDividerSlider" destination="xet-C9-l5y" id="GnQ-xU-e0N"/>
|
||||||
<outlet property="pointerVelocityModeDividerUILabel" destination="VlN-ZW-1gm" id="vbe-QK-hZb"/>
|
<outlet property="pointerVelocityModeDividerUILabel" destination="VlN-ZW-1gm" id="vbe-QK-hZb"/>
|
||||||
<outlet property="resolutionDisplayView" destination="Dej-Bj-F6i" id="bi4-N6-snx"/>
|
<outlet property="resolutionDisplayView" destination="Dej-Bj-F6i" id="bi4-N6-snx"/>
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *audioOnPCSelector;
|
@property (strong, nonatomic) IBOutlet UISegmentedControl *audioOnPCSelector;
|
||||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *codecSelector;
|
@property (strong, nonatomic) IBOutlet UISegmentedControl *codecSelector;
|
||||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *yuv444Selector;
|
@property (strong, nonatomic) IBOutlet UISegmentedControl *yuv444Selector;
|
||||||
|
@property (strong, nonatomic) IBOutlet UISegmentedControl *pipSelector;
|
||||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *hdrSelector;
|
@property (strong, nonatomic) IBOutlet UISegmentedControl *hdrSelector;
|
||||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *framePacingSelector;
|
@property (strong, nonatomic) IBOutlet UISegmentedControl *framePacingSelector;
|
||||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *btMouseSelector;
|
@property (strong, nonatomic) IBOutlet UISegmentedControl *btMouseSelector;
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ BOOL isCustomResolution(CGSize res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)updateResolutionTable{
|
- (void)updateResolutionTable{
|
||||||
UIWindow *window = UIApplication.sharedApplication.windows.firstObject;
|
UIWindow *window = self.view.window;
|
||||||
CGFloat screenScale = window.screen.scale;
|
CGFloat screenScale = window.screen.scale;
|
||||||
CGFloat safeAreaWidth = (window.frame.size.width - window.safeAreaInsets.left - window.safeAreaInsets.right) * screenScale;
|
CGFloat safeAreaWidth = (window.frame.size.width - window.safeAreaInsets.left - window.safeAreaInsets.right) * screenScale;
|
||||||
CGFloat appWindowWidth = window.frame.size.width * screenScale;
|
CGFloat appWindowWidth = window.frame.size.width * screenScale;
|
||||||
@@ -465,6 +465,7 @@ BOOL isCustomResolution(CGSize res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[self.yuv444Selector setSelectedSegmentIndex:currentSettings.enableYUV444 ? 1 : 0];
|
[self.yuv444Selector setSelectedSegmentIndex:currentSettings.enableYUV444 ? 1 : 0];
|
||||||
|
[self.pipSelector setSelectedSegmentIndex:currentSettings.enablePIP ? 1 : 0];
|
||||||
[self.statsOverlaySelector setSelectedSegmentIndex:currentSettings.statsOverlayLevel.intValue];
|
[self.statsOverlaySelector setSelectedSegmentIndex:currentSettings.statsOverlayLevel.intValue];
|
||||||
[self.btMouseSelector setSelectedSegmentIndex:currentSettings.btMouseSupport ? 1 : 0];
|
[self.btMouseSelector setSelectedSegmentIndex:currentSettings.btMouseSupport ? 1 : 0];
|
||||||
[self.optimizeSettingsSelector setSelectedSegmentIndex:currentSettings.optimizeGames ? 1 : 0];
|
[self.optimizeSettingsSelector setSelectedSegmentIndex:currentSettings.optimizeGames ? 1 : 0];
|
||||||
@@ -1079,6 +1080,7 @@ BOOL isCustomResolution(CGSize res) {
|
|||||||
BOOL audioOnPC = [self.audioOnPCSelector selectedSegmentIndex] == 1;
|
BOOL audioOnPC = [self.audioOnPCSelector selectedSegmentIndex] == 1;
|
||||||
uint32_t preferredCodec = [self getChosenCodecPreference];
|
uint32_t preferredCodec = [self getChosenCodecPreference];
|
||||||
BOOL enableYUV444 = [self.yuv444Selector selectedSegmentIndex] == 1;
|
BOOL enableYUV444 = [self.yuv444Selector selectedSegmentIndex] == 1;
|
||||||
|
BOOL enablePIP = [self.pipSelector selectedSegmentIndex] == 1;
|
||||||
BOOL btMouseSupport = [self.btMouseSelector selectedSegmentIndex] == 1;
|
BOOL btMouseSupport = [self.btMouseSelector selectedSegmentIndex] == 1;
|
||||||
BOOL useFramePacing = [self.framePacingSelector selectedSegmentIndex] == 1;
|
BOOL useFramePacing = [self.framePacingSelector selectedSegmentIndex] == 1;
|
||||||
// BOOL absoluteTouchMode = [self.touchModeSelector selectedSegmentIndex] == 1;
|
// BOOL absoluteTouchMode = [self.touchModeSelector selectedSegmentIndex] == 1;
|
||||||
@@ -1116,6 +1118,7 @@ BOOL isCustomResolution(CGSize res) {
|
|||||||
audioOnPC:audioOnPC
|
audioOnPC:audioOnPC
|
||||||
preferredCodec:preferredCodec
|
preferredCodec:preferredCodec
|
||||||
enableYUV444:enableYUV444
|
enableYUV444:enableYUV444
|
||||||
|
enablePIP:enablePIP
|
||||||
useFramePacing:useFramePacing
|
useFramePacing:useFramePacing
|
||||||
enableHdr:enableHdr
|
enableHdr:enableHdr
|
||||||
btMouseSupport:btMouseSupport
|
btMouseSupport:btMouseSupport
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
1829CE3F2E2154DF0074A99A /* SceneDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1829CE3E2E2154DF0074A99A /* SceneDelegate.m */; };
|
||||||
18341A042DEB662C0094E5F2 /* FixedTintImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 18341A032DEB662C0094E5F2 /* FixedTintImageView.m */; };
|
18341A042DEB662C0094E5F2 /* FixedTintImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 18341A032DEB662C0094E5F2 /* FixedTintImageView.m */; };
|
||||||
187F4B282E04658A00EECC59 /* WidgetPanelStackView.m in Sources */ = {isa = PBXBuildFile; fileRef = 187F4B272E04658A00EECC59 /* WidgetPanelStackView.m */; };
|
187F4B282E04658A00EECC59 /* WidgetPanelStackView.m in Sources */ = {isa = PBXBuildFile; fileRef = 187F4B272E04658A00EECC59 /* WidgetPanelStackView.m */; };
|
||||||
189AD29A2DE7446000E24B88 /* HostCollectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 189AD2992DE7446000E24B88 /* HostCollectionViewController.m */; };
|
189AD29A2DE7446000E24B88 /* HostCollectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 189AD2992DE7446000E24B88 /* HostCollectionViewController.m */; };
|
||||||
@@ -130,6 +131,8 @@
|
|||||||
/* End PBXContainerItemProxy section */
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
1829CE3D2E2154DF0074A99A /* SceneDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SceneDelegate.h; sourceTree = "<group>"; };
|
||||||
|
1829CE3E2E2154DF0074A99A /* SceneDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SceneDelegate.m; sourceTree = "<group>"; };
|
||||||
18341A022DEB65AB0094E5F2 /* FixedTintImageView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FixedTintImageView.h; sourceTree = "<group>"; };
|
18341A022DEB65AB0094E5F2 /* FixedTintImageView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FixedTintImageView.h; sourceTree = "<group>"; };
|
||||||
18341A032DEB662C0094E5F2 /* FixedTintImageView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FixedTintImageView.m; sourceTree = "<group>"; };
|
18341A032DEB662C0094E5F2 /* FixedTintImageView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FixedTintImageView.m; sourceTree = "<group>"; };
|
||||||
187F4B232E04633100EECC59 /* WidgetPanelStackView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WidgetPanelStackView.h; sourceTree = "<group>"; };
|
187F4B232E04633100EECC59 /* WidgetPanelStackView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WidgetPanelStackView.h; sourceTree = "<group>"; };
|
||||||
@@ -495,6 +498,8 @@
|
|||||||
FBD3495F1A004453002D2A60 /* Database */,
|
FBD3495F1A004453002D2A60 /* Database */,
|
||||||
FB290CFA19B2C406004C83CF /* Supporting Files */,
|
FB290CFA19B2C406004C83CF /* Supporting Files */,
|
||||||
FB290D0219B2C406004C83CF /* AppDelegate.h */,
|
FB290D0219B2C406004C83CF /* AppDelegate.h */,
|
||||||
|
1829CE3D2E2154DF0074A99A /* SceneDelegate.h */,
|
||||||
|
1829CE3E2E2154DF0074A99A /* SceneDelegate.m */,
|
||||||
18341A022DEB65AB0094E5F2 /* FixedTintImageView.h */,
|
18341A022DEB65AB0094E5F2 /* FixedTintImageView.h */,
|
||||||
18341A032DEB662C0094E5F2 /* FixedTintImageView.m */,
|
18341A032DEB662C0094E5F2 /* FixedTintImageView.m */,
|
||||||
FB290D0319B2C406004C83CF /* AppDelegate.m */,
|
FB290D0319B2C406004C83CF /* AppDelegate.m */,
|
||||||
@@ -907,6 +912,7 @@
|
|||||||
84351B102C29ACCB00A77989 /* OSCProfile.m in Sources */,
|
84351B102C29ACCB00A77989 /* OSCProfile.m in Sources */,
|
||||||
84351B2C2C29AD6100A77989 /* OSCProfilesTableViewController.m in Sources */,
|
84351B2C2C29AD6100A77989 /* OSCProfilesTableViewController.m in Sources */,
|
||||||
98D585701C0ED0E800F6CC00 /* TemporarySettings.m in Sources */,
|
98D585701C0ED0E800F6CC00 /* TemporarySettings.m in Sources */,
|
||||||
|
1829CE3F2E2154DF0074A99A /* SceneDelegate.m in Sources */,
|
||||||
FB89462819F646E200339C8A /* CryptoManager.m in Sources */,
|
FB89462819F646E200339C8A /* CryptoManager.m in Sources */,
|
||||||
DC1F5A07206436B20037755F /* ConnectionHelper.m in Sources */,
|
DC1F5A07206436B20037755F /* ConnectionHelper.m in Sources */,
|
||||||
FB89462E19F646E200339C8A /* PairManager.m in Sources */,
|
FB89462E19F646E200339C8A /* PairManager.m in Sources */,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"originHash" : "f17bf6f1131c7255a75ffd6a989b69573cb49c332e612dffb429905d8d7afe72",
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
{
|
||||||
"identity" : "openssl-package",
|
"identity" : "openssl-package",
|
||||||
@@ -10,5 +11,5 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"version" : 2
|
"version" : 3
|
||||||
}
|
}
|
||||||
@@ -10,7 +10,6 @@
|
|||||||
|
|
||||||
@interface AppDelegate : UIResponder <UIApplicationDelegate>
|
@interface AppDelegate : UIResponder <UIApplicationDelegate>
|
||||||
|
|
||||||
@property (strong, nonatomic) UIWindow *window;
|
|
||||||
@property (strong, nonatomic) NSString *pcUuidToLoad;
|
@property (strong, nonatomic) NSString *pcUuidToLoad;
|
||||||
@property (strong, nonatomic) void (^shortcutCompletionHandler)(BOOL);
|
@property (strong, nonatomic) void (^shortcutCompletionHandler)(BOOL);
|
||||||
|
|
||||||
@@ -21,5 +20,6 @@
|
|||||||
- (void)saveContext;
|
- (void)saveContext;
|
||||||
- (NSURL *)applicationDocumentsDirectory;
|
- (NSURL *)applicationDocumentsDirectory;
|
||||||
- (NSURL*) getStoreURL;
|
- (NSURL*) getStoreURL;
|
||||||
|
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options API_AVAILABLE(ios(13.0));
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -27,6 +27,15 @@ static NSString* DB_NAME = @"Moonlight_tvOS.bin";
|
|||||||
static NSString* DB_NAME = @"Limelight_iOS.sqlite";
|
static NSString* DB_NAME = @"Limelight_iOS.sqlite";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#pragma mark - UISceneSession lifecycle
|
||||||
|
|
||||||
|
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options API_AVAILABLE(ios(13.0)){
|
||||||
|
return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions API_AVAILABLE(ios(13.0)){
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#if !TARGET_OS_TV
|
#if !TARGET_OS_TV
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch.iPad" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES" initialViewController="EVd-wq-ego">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch.iPad" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES" initialViewController="EVd-wq-ego">
|
||||||
<device id="ipad11_0rounded" orientation="portrait" layout="fullscreen" appearance="light"/>
|
<device id="ipad11_0rounded" orientation="portrait" layout="fullscreen" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
|
||||||
<capability name="Image references" minToolsVersion="12.0"/>
|
<capability name="Image references" minToolsVersion="12.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
@@ -870,12 +870,33 @@
|
|||||||
<constraint firstAttribute="width" constant="380" id="pkz-ek-bOk"/>
|
<constraint firstAttribute="width" constant="380" id="pkz-ek-bOk"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</stackView>
|
</stackView>
|
||||||
|
<stackView opaque="NO" contentMode="scaleToFill" spacing="20" id="12a-Wi-LlL" userLabel="pipStack">
|
||||||
|
<rect key="frame" x="293" y="1899" width="280" height="31"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Enable PiP" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3rP-dh-TJi" userLabel="pipLabel">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="130" height="31"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="width" constant="130" id="ir2-ut-mD3"/>
|
||||||
|
</constraints>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
|
<nil key="textColor"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="VTC-Z9-IYM" userLabel="pipSwitch">
|
||||||
|
<rect key="frame" x="150" y="0.0" width="132" height="31"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="width" constant="130" id="ADb-7C-hgx"/>
|
||||||
|
</constraints>
|
||||||
|
</switch>
|
||||||
|
</subviews>
|
||||||
|
</stackView>
|
||||||
</subviews>
|
</subviews>
|
||||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
</view>
|
</view>
|
||||||
<size key="freeformSize" width="682" height="2500"/>
|
<size key="freeformSize" width="682" height="2500"/>
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="HdrStack" destination="dH7-Sy-EdJ" id="lVV-l0-VBW"/>
|
<outlet property="HdrStack" destination="12a-Wi-LlL" id="bbr-VY-n5y"/>
|
||||||
<outlet property="audioConfigSelector" destination="iZL-dA-jk9" id="Yja-os-o6g"/>
|
<outlet property="audioConfigSelector" destination="iZL-dA-jk9" id="Yja-os-o6g"/>
|
||||||
<outlet property="audioConfigStack" destination="qnG-5F-2YY" id="kFX-VK-vuK"/>
|
<outlet property="audioConfigStack" destination="qnG-5F-2YY" id="kFX-VK-vuK"/>
|
||||||
<outlet property="audioOnPcStack" destination="Bbr-Wj-Z75" id="c2K-3R-whg"/>
|
<outlet property="audioOnPcStack" destination="Bbr-Wj-Z75" id="c2K-3R-whg"/>
|
||||||
@@ -916,6 +937,8 @@
|
|||||||
<outlet property="onscreenControllerLabel" destination="LGV-5y-piG" id="GsV-fS-lX9"/>
|
<outlet property="onscreenControllerLabel" destination="LGV-5y-piG" id="GsV-fS-lX9"/>
|
||||||
<outlet property="optimizeGamesStack" destination="TvS-69-vcF" id="pm1-0c-dCR"/>
|
<outlet property="optimizeGamesStack" destination="TvS-69-vcF" id="pm1-0c-dCR"/>
|
||||||
<outlet property="optimizeGamesSwitch" destination="Sui-Ig-YxN" id="Lxu-si-ugy"/>
|
<outlet property="optimizeGamesSwitch" destination="Sui-Ig-YxN" id="Lxu-si-ugy"/>
|
||||||
|
<outlet property="pipStack" destination="12a-Wi-LlL" id="eTJ-0h-hRB"/>
|
||||||
|
<outlet property="pipSwitch" destination="VTC-Z9-IYM" id="gzC-rU-DoP"/>
|
||||||
<outlet property="pointerVelocityDividerStack" destination="b1U-nD-gYE" id="ep1-K3-rKj"/>
|
<outlet property="pointerVelocityDividerStack" destination="b1U-nD-gYE" id="ep1-K3-rKj"/>
|
||||||
<outlet property="pointerVelocityFactorStack" destination="vdk-mK-ztu" id="Cik-Rh-pGS"/>
|
<outlet property="pointerVelocityFactorStack" destination="vdk-mK-ztu" id="Cik-Rh-pGS"/>
|
||||||
<outlet property="pointerVelocityModeDividerSlider" destination="AcZ-2i-GI4" id="dGf-ZU-w3S"/>
|
<outlet property="pointerVelocityModeDividerSlider" destination="AcZ-2i-GI4" id="dGf-ZU-w3S"/>
|
||||||
|
|||||||
@@ -1520,6 +1520,27 @@
|
|||||||
<constraint firstAttribute="width" constant="380" id="zIc-GX-mTA"/>
|
<constraint firstAttribute="width" constant="380" id="zIc-GX-mTA"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</stackView>
|
</stackView>
|
||||||
|
<stackView opaque="NO" contentMode="scaleToFill" spacing="20" id="hvN-TZ-ziV" userLabel="pipStack">
|
||||||
|
<rect key="frame" x="437" y="1618" width="280" height="31"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Enable PiP" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4Ul-P6-eUk" userLabel="pipLabel">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="130" height="31"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="width" constant="130" id="BBk-4S-vkO"/>
|
||||||
|
</constraints>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
|
<nil key="textColor"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="mgR-dj-4gu" userLabel="pipSwitch">
|
||||||
|
<rect key="frame" x="150" y="0.0" width="132" height="31"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="width" constant="130" id="ke0-cJ-jZt"/>
|
||||||
|
</constraints>
|
||||||
|
</switch>
|
||||||
|
</subviews>
|
||||||
|
</stackView>
|
||||||
</subviews>
|
</subviews>
|
||||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
</view>
|
</view>
|
||||||
@@ -1565,6 +1586,8 @@
|
|||||||
<outlet property="onscreenControllerLabel" destination="5uF-E5-hpX" id="Ntz-sT-O6S"/>
|
<outlet property="onscreenControllerLabel" destination="5uF-E5-hpX" id="Ntz-sT-O6S"/>
|
||||||
<outlet property="optimizeGamesStack" destination="Col-Dm-lwT" id="ng3-CV-HLd"/>
|
<outlet property="optimizeGamesStack" destination="Col-Dm-lwT" id="ng3-CV-HLd"/>
|
||||||
<outlet property="optimizeGamesSwitch" destination="zGD-yR-gUS" id="Gji-vK-txl"/>
|
<outlet property="optimizeGamesSwitch" destination="zGD-yR-gUS" id="Gji-vK-txl"/>
|
||||||
|
<outlet property="pipStack" destination="hvN-TZ-ziV" id="6oL-0b-Bto"/>
|
||||||
|
<outlet property="pipSwitch" destination="mgR-dj-4gu" id="b8b-7E-wrq"/>
|
||||||
<outlet property="pointerVelocityDividerStack" destination="Jp2-5v-Zr0" id="0mB-RI-df8"/>
|
<outlet property="pointerVelocityDividerStack" destination="Jp2-5v-Zr0" id="0mB-RI-df8"/>
|
||||||
<outlet property="pointerVelocityFactorStack" destination="PAv-Ep-ZJo" id="fxb-uz-e1a"/>
|
<outlet property="pointerVelocityFactorStack" destination="PAv-Ep-ZJo" id="fxb-uz-e1a"/>
|
||||||
<outlet property="pointerVelocityModeDividerSlider" destination="OMw-Ew-fBX" id="Yki-z6-7bh"/>
|
<outlet property="pointerVelocityModeDividerSlider" destination="OMw-Ew-fBX" id="Yki-z6-7bh"/>
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ typedef NS_ENUM(NSInteger, SettingsMenuMode) {
|
|||||||
audioOnPC:(BOOL)audioOnPC
|
audioOnPC:(BOOL)audioOnPC
|
||||||
preferredCodec:(uint32_t)preferredCodec
|
preferredCodec:(uint32_t)preferredCodec
|
||||||
enableYUV444:(BOOL)enableYUV444
|
enableYUV444:(BOOL)enableYUV444
|
||||||
|
enablePIP:(BOOL)enablePIP
|
||||||
useFramePacing:(BOOL)useFramePacing
|
useFramePacing:(BOOL)useFramePacing
|
||||||
enableHdr:(BOOL)enableHdr
|
enableHdr:(BOOL)enableHdr
|
||||||
btMouseSupport:(BOOL)btMouseSupport
|
btMouseSupport:(BOOL)btMouseSupport
|
||||||
|
|||||||
@@ -81,6 +81,7 @@
|
|||||||
audioOnPC:(BOOL)audioOnPC
|
audioOnPC:(BOOL)audioOnPC
|
||||||
preferredCodec:(uint32_t)preferredCodec
|
preferredCodec:(uint32_t)preferredCodec
|
||||||
enableYUV444:(BOOL)enableYUV444
|
enableYUV444:(BOOL)enableYUV444
|
||||||
|
enablePIP:(BOOL)enablePIP
|
||||||
useFramePacing:(BOOL)useFramePacing
|
useFramePacing:(BOOL)useFramePacing
|
||||||
enableHdr:(BOOL)enableHdr
|
enableHdr:(BOOL)enableHdr
|
||||||
btMouseSupport:(BOOL)btMouseSupport
|
btMouseSupport:(BOOL)btMouseSupport
|
||||||
@@ -121,6 +122,7 @@
|
|||||||
settingsToSave.playAudioOnPC = audioOnPC;
|
settingsToSave.playAudioOnPC = audioOnPC;
|
||||||
settingsToSave.preferredCodec = preferredCodec;
|
settingsToSave.preferredCodec = preferredCodec;
|
||||||
settingsToSave.enableYUV444 = enableYUV444;
|
settingsToSave.enableYUV444 = enableYUV444;
|
||||||
|
settingsToSave.enablePIP = enablePIP;
|
||||||
settingsToSave.useFramePacing = useFramePacing;
|
settingsToSave.useFramePacing = useFramePacing;
|
||||||
settingsToSave.enableHdr = enableHdr;
|
settingsToSave.enableHdr = enableHdr;
|
||||||
settingsToSave.btMouseSupport = btMouseSupport;
|
settingsToSave.btMouseSupport = btMouseSupport;
|
||||||
|
|||||||
@@ -46,6 +46,7 @@
|
|||||||
CODEC_PREF_AV1,
|
CODEC_PREF_AV1,
|
||||||
} preferredCodec;
|
} preferredCodec;
|
||||||
@property (nonatomic) BOOL enableYUV444;
|
@property (nonatomic) BOOL enableYUV444;
|
||||||
|
@property (nonatomic) BOOL enablePIP;
|
||||||
@property (nonatomic) BOOL reverseMouseWheelDirection;
|
@property (nonatomic) BOOL reverseMouseWheelDirection;
|
||||||
@property (nonatomic, retain) NSNumber * asyncNativeTouchPriority;
|
@property (nonatomic, retain) NSNumber * asyncNativeTouchPriority;
|
||||||
@property (nonatomic) BOOL useFramePacing;
|
@property (nonatomic) BOOL useFramePacing;
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
assert([self.audioConfig intValue] != 0);
|
assert([self.audioConfig intValue] != 0);
|
||||||
self.preferredCodec = (typeof(self.preferredCodec))[[NSUserDefaults standardUserDefaults] integerForKey:@"preferredCodec"];
|
self.preferredCodec = (typeof(self.preferredCodec))[[NSUserDefaults standardUserDefaults] integerForKey:@"preferredCodec"];
|
||||||
self.enableYUV444 = [[NSUserDefaults standardUserDefaults] boolForKey:@"enableYUV444"];
|
self.enableYUV444 = [[NSUserDefaults standardUserDefaults] boolForKey:@"enableYUV444"];
|
||||||
|
self.enablePIP = [[NSUserDefaults standardUserDefaults] boolForKey:@"enablePIP"];
|
||||||
self.useFramePacing = [[NSUserDefaults standardUserDefaults] integerForKey:@"useFramePacing"] != 0;
|
self.useFramePacing = [[NSUserDefaults standardUserDefaults] integerForKey:@"useFramePacing"] != 0;
|
||||||
self.playAudioOnPC = [[NSUserDefaults standardUserDefaults] boolForKey:@"audioOnPC"];
|
self.playAudioOnPC = [[NSUserDefaults standardUserDefaults] boolForKey:@"audioOnPC"];
|
||||||
self.enableHdr = [[NSUserDefaults standardUserDefaults] boolForKey:@"enableHdr"];
|
self.enableHdr = [[NSUserDefaults standardUserDefaults] boolForKey:@"enableHdr"];
|
||||||
@@ -82,6 +83,7 @@
|
|||||||
self.audioConfig = settings.audioConfig;
|
self.audioConfig = settings.audioConfig;
|
||||||
self.preferredCodec = settings.preferredCodec;
|
self.preferredCodec = settings.preferredCodec;
|
||||||
self.enableYUV444 = settings.enableYUV444;
|
self.enableYUV444 = settings.enableYUV444;
|
||||||
|
self.enablePIP = settings.enablePIP;
|
||||||
self.useFramePacing = settings.useFramePacing;
|
self.useFramePacing = settings.useFramePacing;
|
||||||
self.playAudioOnPC = settings.playAudioOnPC;
|
self.playAudioOnPC = settings.playAudioOnPC;
|
||||||
self.enableHdr = settings.enableHdr;
|
self.enableHdr = settings.enableHdr;
|
||||||
|
|||||||
@@ -63,7 +63,6 @@
|
|||||||
- (void)keyboardWillShow:(NSNotification *)notification;
|
- (void)keyboardWillShow:(NSNotification *)notification;
|
||||||
- (void)keyboardWillHide;
|
- (void)keyboardWillHide;
|
||||||
|
|
||||||
|
|
||||||
#if !TARGET_OS_TV
|
#if !TARGET_OS_TV
|
||||||
- (void) updateCursorLocation:(CGPoint)location isMouse:(BOOL)isMouse;
|
- (void) updateCursorLocation:(CGPoint)location isMouse:(BOOL)isMouse;
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -364,6 +364,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// we'll enable on screen buttons, and disable on screen controllers for absolute touch
|
// we'll enable on screen buttons, and disable on screen controllers for absolute touch
|
||||||
- (bool) isOscEnabled{
|
- (bool) isOscEnabled{
|
||||||
return (settings.touchMode.intValue == RelativeTouch || settings.touchMode.intValue == NativeTouch) && settings.onscreenControls.intValue != OnScreenControlsLevelOff;
|
return (settings.touchMode.intValue == RelativeTouch || settings.touchMode.intValue == NativeTouch) && settings.onscreenControls.intValue != OnScreenControlsLevelOff;
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>GCSupportsGameMode</key>
|
||||||
|
<true/>
|
||||||
<key>CADisableMinimumFrameDuration</key>
|
<key>CADisableMinimumFrameDuration</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
@@ -66,11 +68,36 @@
|
|||||||
<string>Moonlight uses the device Gyro and Acceleration data to emulate motion controls when streaming.</string>
|
<string>Moonlight uses the device Gyro and Acceleration data to emulate motion controls when streaming.</string>
|
||||||
<key>UIApplicationSceneManifest</key>
|
<key>UIApplicationSceneManifest</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>UISceneConfigurations</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIWindowSceneSessionRoleExternalDisplay</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>UISceneDelegateClassName</key>
|
||||||
|
<string>SceneDelegate</string>
|
||||||
|
<key>UISceneConfigurationName</key>
|
||||||
|
<string>External Display Configuration</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>UIWindowSceneSessionRoleApplication</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>UISceneConfigurationName</key>
|
||||||
|
<string>Default Configuration</string>
|
||||||
|
<key>UISceneDelegateClassName</key>
|
||||||
|
<string>SceneDelegate</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
<key>UIApplicationSupportsMultipleScenes</key>
|
<key>UIApplicationSupportsMultipleScenes</key>
|
||||||
<false/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>UIBackgroundModes</key>
|
||||||
|
<array>
|
||||||
|
<string>audio</string>
|
||||||
|
</array>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>Launch Screen</string>
|
<string>Launch Screen</string>
|
||||||
<key>UIMainStoryboardFile</key>
|
<key>UIMainStoryboardFile</key>
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" usesScalarValueType="NO" syncable="YES"/>
|
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" usesScalarValueType="NO" syncable="YES"/>
|
||||||
<attribute name="btMouseSupport" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
<attribute name="btMouseSupport" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||||
<attribute name="enableHdr" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
<attribute name="enableHdr" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||||
|
<attribute name="enablePIP" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||||
<attribute name="enableYUV444" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
<attribute name="enableYUV444" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||||
<attribute name="externalDisplayMode" optional="YES" attributeType="Integer 16" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
|
<attribute name="externalDisplayMode" optional="YES" attributeType="Integer 16" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
|
||||||
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" usesScalarValueType="NO" syncable="YES"/>
|
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" usesScalarValueType="NO" syncable="YES"/>
|
||||||
|
|||||||
@@ -214,6 +214,29 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"[ Enter picture-in-picture mode ]" : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "[ 进入画中画模式 ]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hant" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "[ 进入画中画模式 ]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-HK" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "[ 进入画中画模式 ]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"[ Open on-screen widget tool ]" : {
|
"[ Open on-screen widget tool ]" : {
|
||||||
"extractionState" : "manual",
|
"extractionState" : "manual",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
@interface SceneDelegate : UIResponder <UIWindowSceneDelegate>
|
||||||
|
|
||||||
|
@property (strong, nonatomic) UIWindow * window;
|
||||||
|
|
||||||
|
+ (void)setExternalDisplayRenderView:(UIView *)renderView;
|
||||||
|
+ (void)clearExternalDisplayRenderView;
|
||||||
|
- (void)updatePreferredDisplayMode:(BOOL)streamActive withRenderView:(UIView *)renderView;
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
#import "SceneDelegate.h"
|
||||||
|
#import "StreamFrameViewController.h"
|
||||||
|
|
||||||
|
@implementation SceneDelegate
|
||||||
|
|
||||||
|
static UIView *_sharedStreamVideoRenderView = nil;
|
||||||
|
static UIWindow *_externalSceneWindow = nil;
|
||||||
|
|
||||||
|
- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions {
|
||||||
|
if (![scene isKindOfClass:[UIWindowScene class]]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UIWindowScene *windowScene = (UIWindowScene *)scene;
|
||||||
|
if ([session.role isEqualToString:UIWindowSceneSessionRoleApplication]) {
|
||||||
|
self.window = [[UIWindow alloc] initWithWindowScene:windowScene];
|
||||||
|
NSString *storyboardName;
|
||||||
|
if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) {
|
||||||
|
storyboardName = @"iPad";
|
||||||
|
} else {
|
||||||
|
storyboardName = @"iPhone";
|
||||||
|
}
|
||||||
|
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:storyboardName bundle:nil];
|
||||||
|
UIViewController *initialViewController = [storyboard instantiateInitialViewController];
|
||||||
|
self.window.rootViewController = initialViewController;
|
||||||
|
[self.window makeKeyAndVisible];
|
||||||
|
Log(LOG_I, @"SceneDelegate: Main app scene connected.");
|
||||||
|
|
||||||
|
} else if ([session.role isEqualToString:UIWindowSceneSessionRoleExternalDisplay]) {
|
||||||
|
Log(LOG_I, @"SceneDelegate: External display scene connecting for screen: %@", ((UIWindowScene *)scene).screen.description);
|
||||||
|
UIWindowScene *windowScene = (UIWindowScene *)scene;
|
||||||
|
_externalSceneWindow = [[UIWindow alloc] initWithWindowScene:windowScene];
|
||||||
|
UIViewController *externalVC = [[UIViewController alloc] init];
|
||||||
|
externalVC.view.backgroundColor = [UIColor blackColor]; // Set a default background
|
||||||
|
_externalSceneWindow.rootViewController = externalVC;
|
||||||
|
|
||||||
|
if (_sharedStreamVideoRenderView) {
|
||||||
|
_sharedStreamVideoRenderView.frame = _externalSceneWindow.bounds;
|
||||||
|
[_externalSceneWindow.rootViewController.view addSubview:_sharedStreamVideoRenderView];
|
||||||
|
Log(LOG_I, @"SceneDelegate: External display scene connected.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Method for StreamFrameViewController to provide its render view
|
||||||
|
+ (void)setExternalDisplayRenderView:(UIView *)renderView {
|
||||||
|
_sharedStreamVideoRenderView = renderView;
|
||||||
|
if (_externalSceneWindow && _externalSceneWindow.rootViewController && _sharedStreamVideoRenderView) {
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
// Ensure it's removed from any previous parent (should have been done by StreamFrameVC)
|
||||||
|
[_sharedStreamVideoRenderView removeFromSuperview];
|
||||||
|
_sharedStreamVideoRenderView.frame = _externalSceneWindow.bounds; // Set frame for external window
|
||||||
|
[_externalSceneWindow.rootViewController.view addSubview:_sharedStreamVideoRenderView];
|
||||||
|
_externalSceneWindow.hidden = NO;
|
||||||
|
Log(LOG_I, @"SceneDelegate: Added render view to external window's root view.");
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Log(LOG_E, @"SceneDelegate: External display window or root view controller not available.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (void)clearExternalDisplayRenderView {
|
||||||
|
if (_sharedStreamVideoRenderView) {
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
[_sharedStreamVideoRenderView removeFromSuperview];
|
||||||
|
Log(LOG_I, @"SceneDelegate: Removed render view from external display.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_sharedStreamVideoRenderView = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)sceneDidDisconnect:(UIScene *)scene {
|
||||||
|
Log(LOG_I, @"SceneDelegate: Scene disconnected: %@, role: %@", scene.title, scene.session.role);
|
||||||
|
|
||||||
|
if ([scene.session.role isEqualToString:UIWindowSceneSessionRoleExternalDisplay]) {
|
||||||
|
if ([scene isKindOfClass:[UIWindowScene class]]) {
|
||||||
|
UIWindowScene *windowScene = (UIWindowScene *)scene;
|
||||||
|
if (_externalSceneWindow == windowScene.windows.firstObject) { // Compare with the window from the disconnecting scene
|
||||||
|
[SceneDelegate clearExternalDisplayRenderView]; // Clears the shared view
|
||||||
|
_externalSceneWindow = nil;
|
||||||
|
Log(LOG_I, @"SceneDelegate: External display scene fully disconnected and cleaned up.");
|
||||||
|
} else {
|
||||||
|
Log(LOG_W, @"SceneDelegate: Disconnecting scene is not the one holding our _externalSceneWindow.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log(LOG_W, @"SceneDelegate: Disconnecting scene is not a UIWindowScene.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -255,8 +255,10 @@ int ArInit(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig, v
|
|||||||
SDL_PauseAudioDevice(audioDevice, 0);
|
SDL_PauseAudioDevice(audioDevice, 0);
|
||||||
|
|
||||||
// Disable lowering volume of other audio streams (SDL sets AVAudioSessionCategoryOptionDuckOthers by default)
|
// Disable lowering volume of other audio streams (SDL sets AVAudioSessionCategoryOptionDuckOthers by default)
|
||||||
|
|
||||||
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil];
|
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil];
|
||||||
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
@property NSString* rtspSessionUrl;
|
@property NSString* rtspSessionUrl;
|
||||||
@property int serverCodecModeSupport;
|
@property int serverCodecModeSupport;
|
||||||
@property BOOL enableYUV444;
|
@property BOOL enableYUV444;
|
||||||
|
@property BOOL enablePIP;
|
||||||
@property int width;
|
@property int width;
|
||||||
@property int height;
|
@property int height;
|
||||||
@property int frameRate;
|
@property int frameRate;
|
||||||
|
|||||||
@@ -9,5 +9,5 @@
|
|||||||
#import "StreamConfiguration.h"
|
#import "StreamConfiguration.h"
|
||||||
|
|
||||||
@implementation StreamConfiguration
|
@implementation StreamConfiguration
|
||||||
@synthesize host, httpsPort, appID, width, height, frameRate, bitRate, riKeyId, riKey, gamepadMask, appName, optimizeGameSettings, playAudioOnPC, swapABXYButtons, gyroMode, audioConfiguration, supportedVideoFormats, multiController, serverCert, rtspSessionUrl, serverCodecModeSupport, enableYUV444;
|
@synthesize host, httpsPort, appID, width, height, frameRate, bitRate, riKeyId, riKey, gamepadMask, appName, optimizeGameSettings, playAudioOnPC, swapABXYButtons, gyroMode, audioConfiguration, supportedVideoFormats, multiController, serverCert, rtspSessionUrl, serverCodecModeSupport, enableYUV444, enablePIP;
|
||||||
@end
|
@end
|
||||||
@@ -14,6 +14,8 @@
|
|||||||
|
|
||||||
@interface StreamManager : NSOperation
|
@interface StreamManager : NSOperation
|
||||||
|
|
||||||
|
@property (nonatomic, strong, readonly) VideoDecoderRenderer *videoRenderer;
|
||||||
|
|
||||||
- (id) initWithConfig:(StreamConfiguration*)config renderView:(UIView*)view connectionCallbacks:(id<ConnectionCallbacks>)callback;
|
- (id) initWithConfig:(StreamConfiguration*)config renderView:(UIView*)view connectionCallbacks:(id<ConnectionCallbacks>)callback;
|
||||||
|
|
||||||
- (void) stopStream;
|
- (void) stopStream;
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
UIView* _renderView;
|
UIView* _renderView;
|
||||||
id<ConnectionCallbacks> _callbacks;
|
id<ConnectionCallbacks> _callbacks;
|
||||||
Connection* _connection;
|
Connection* _connection;
|
||||||
|
VideoDecoderRenderer* _videoRenderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id) initWithConfig:(StreamConfiguration*)config renderView:(UIView*)view connectionCallbacks:(id<ConnectionCallbacks>)callbacks {
|
- (id) initWithConfig:(StreamConfiguration*)config renderView:(UIView*)view connectionCallbacks:(id<ConnectionCallbacks>)callbacks {
|
||||||
@@ -103,8 +104,12 @@
|
|||||||
|
|
||||||
// Initializing the renderer must be done on the main thread
|
// Initializing the renderer must be done on the main thread
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
VideoDecoderRenderer* renderer = [[VideoDecoderRenderer alloc] initWithView:self->_renderView callbacks:self->_callbacks streamAspectRatio:(float)self->_config.width / (float)self->_config.height useFramePacing:self->_config.useFramePacing];
|
self->_videoRenderer = [[VideoDecoderRenderer alloc] initWithView:self->_renderView
|
||||||
self->_connection = [[Connection alloc] initWithConfig:self->_config renderer:renderer connectionCallbacks:self->_callbacks];
|
callbacks:self->_callbacks
|
||||||
|
streamAspectRatio:(float)self->_config.width / (float)self->_config.height
|
||||||
|
useFramePacing:self->_config.useFramePacing];
|
||||||
|
|
||||||
|
self->_connection = [[Connection alloc] initWithConfig:self->_config renderer:self->_videoRenderer connectionCallbacks:self->_callbacks];
|
||||||
NSOperationQueue* opQueue = [[NSOperationQueue alloc] init];
|
NSOperationQueue* opQueue = [[NSOperationQueue alloc] init];
|
||||||
[opQueue addOperation:self->_connection];
|
[opQueue addOperation:self->_connection];
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
|
|
||||||
@interface VideoDecoderRenderer : NSObject
|
@interface VideoDecoderRenderer : NSObject
|
||||||
|
|
||||||
|
@property (nonatomic, strong, readonly) AVSampleBufferDisplayLayer *displayLayer;
|
||||||
|
|
||||||
- (id)initWithView:(UIView*)view callbacks:(id<ConnectionCallbacks>)callbacks streamAspectRatio:(float)aspectRatio useFramePacing:(BOOL)useFramePacing;
|
- (id)initWithView:(UIView*)view callbacks:(id<ConnectionCallbacks>)callbacks streamAspectRatio:(float)aspectRatio useFramePacing:(BOOL)useFramePacing;
|
||||||
|
|
||||||
- (void)setupWithVideoFormat:(int)videoFormat width:(int)videoWidth height:(int)videoHeight frameRate:(int)frameRate;
|
- (void)setupWithVideoFormat:(int)videoFormat width:(int)videoWidth height:(int)videoHeight frameRate:(int)frameRate;
|
||||||
|
|||||||
@@ -20,11 +20,11 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size,
|
|||||||
int write_seq_header);
|
int write_seq_header);
|
||||||
|
|
||||||
@implementation VideoDecoderRenderer {
|
@implementation VideoDecoderRenderer {
|
||||||
StreamView* _view;
|
UIView* _view;
|
||||||
id<ConnectionCallbacks> _callbacks;
|
id<ConnectionCallbacks> _callbacks;
|
||||||
float _streamAspectRatio;
|
float _streamAspectRatio;
|
||||||
|
|
||||||
AVSampleBufferDisplayLayer* displayLayer;
|
AVSampleBufferDisplayLayer* _displayLayer;
|
||||||
int videoFormat;
|
int videoFormat;
|
||||||
int frameRate;
|
int frameRate;
|
||||||
|
|
||||||
@@ -39,10 +39,12 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size,
|
|||||||
|
|
||||||
- (void)reinitializeDisplayLayer
|
- (void)reinitializeDisplayLayer
|
||||||
{
|
{
|
||||||
CALayer *oldLayer = displayLayer;
|
if (_displayLayer == nil) {
|
||||||
|
_displayLayer = [[AVSampleBufferDisplayLayer alloc] init];
|
||||||
displayLayer = [[AVSampleBufferDisplayLayer alloc] init];
|
_displayLayer.backgroundColor = [UIColor blackColor].CGColor;
|
||||||
displayLayer.backgroundColor = [UIColor blackColor].CGColor;
|
_displayLayer.videoGravity = AVLayerVideoGravityResize;
|
||||||
|
[_view.layer addSublayer:_displayLayer];
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure the AVSampleBufferDisplayLayer is sized to preserve the aspect ratio
|
// Ensure the AVSampleBufferDisplayLayer is sized to preserve the aspect ratio
|
||||||
// of the video stream. We used to use AVLayerVideoGravityResizeAspect, but that
|
// of the video stream. We used to use AVLayerVideoGravityResizeAspect, but that
|
||||||
@@ -55,21 +57,16 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size,
|
|||||||
} else {
|
} else {
|
||||||
videoSize = CGSizeMake(_view.bounds.size.width, _view.bounds.size.width / _streamAspectRatio);
|
videoSize = CGSizeMake(_view.bounds.size.width, _view.bounds.size.width / _streamAspectRatio);
|
||||||
}
|
}
|
||||||
displayLayer.position = CGPointMake(CGRectGetMidX(_view.bounds), CGRectGetMidY(_view.bounds));
|
|
||||||
displayLayer.bounds = CGRectMake(0, 0, videoSize.width, videoSize.height);
|
[CATransaction begin];
|
||||||
displayLayer.videoGravity = AVLayerVideoGravityResize;
|
[CATransaction setDisableActions:YES];
|
||||||
|
_displayLayer.position = CGPointMake(CGRectGetMidX(_view.bounds), CGRectGetMidY(_view.bounds));
|
||||||
|
_displayLayer.bounds = CGRectMake(0, 0, videoSize.width, videoSize.height);
|
||||||
|
[CATransaction commit];
|
||||||
|
|
||||||
// Hide the layer until we get an IDR frame. This ensures we
|
// Hide the layer until we get an IDR frame. This ensures we
|
||||||
// can see the loading progress label as the stream is starting.
|
// can see the loading progress label as the stream is starting.
|
||||||
displayLayer.hidden = YES;
|
_displayLayer.hidden = YES;
|
||||||
|
|
||||||
if (oldLayer != nil) {
|
|
||||||
// Switch out the old display layer with the new one
|
|
||||||
[_view.layer replaceSublayer:oldLayer with:displayLayer];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
[_view.layer addSublayer:displayLayer];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (formatDesc != nil) {
|
if (formatDesc != nil) {
|
||||||
CFRelease(formatDesc);
|
CFRelease(formatDesc);
|
||||||
@@ -77,7 +74,7 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id)initWithView:(StreamView*)view callbacks:(id<ConnectionCallbacks>)callbacks streamAspectRatio:(float)aspectRatio useFramePacing:(BOOL)useFramePacing
|
- (id)initWithView:(UIView* )view callbacks:(id<ConnectionCallbacks>)callbacks streamAspectRatio:(float)aspectRatio useFramePacing:(BOOL)useFramePacing
|
||||||
{
|
{
|
||||||
NSLog(@"initializing video decoder %f", CACurrentMediaTime());
|
NSLog(@"initializing video decoder %f", CACurrentMediaTime());
|
||||||
self = [super init];
|
self = [super init];
|
||||||
@@ -522,8 +519,10 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check for previous decoder errors before doing anything
|
// Check for previous decoder errors before doing anything
|
||||||
if (displayLayer.status == AVQueuedSampleBufferRenderingStatusFailed) {
|
if (_displayLayer.status == AVQueuedSampleBufferRenderingStatusFailed) {
|
||||||
Log(LOG_E, @"Display layer rendering failed: %@", displayLayer.error);
|
Log(LOG_E, @"Display layer rendering failed: %@", _displayLayer.error);
|
||||||
|
|
||||||
|
[self->_displayLayer flushAndRemoveImage];
|
||||||
|
|
||||||
// Recreate the display layer. We are already on the main thread,
|
// Recreate the display layer. We are already on the main thread,
|
||||||
// so this is safe to do right here.
|
// so this is safe to do right here.
|
||||||
@@ -601,11 +600,11 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Enqueue the next frame
|
// Enqueue the next frame
|
||||||
[self->displayLayer enqueueSampleBuffer:sampleBuffer];
|
[self->_displayLayer enqueueSampleBuffer:sampleBuffer];
|
||||||
|
|
||||||
if (du->frameType == FRAME_TYPE_IDR) {
|
if (du->frameType == FRAME_TYPE_IDR) {
|
||||||
// Ensure the layer is visible now
|
// Ensure the layer is visible now
|
||||||
self->displayLayer.hidden = NO;
|
self->_displayLayer.hidden = NO;
|
||||||
|
|
||||||
// Tell our parent VC to hide the progress indicator
|
// Tell our parent VC to hide the progress indicator
|
||||||
[self->_callbacks videoContentShown];
|
[self->_callbacks videoContentShown];
|
||||||
|
|||||||
@@ -1500,20 +1500,6 @@ static NSMutableSet* hostList;
|
|||||||
|
|
||||||
_boxArtCache = [[NSCache alloc] init];
|
_boxArtCache = [[NSCache alloc] init];
|
||||||
|
|
||||||
//recordedScreenWidth = CGRectGetWidth([[UIScreen mainScreen] bounds]);
|
|
||||||
CGFloat screenWidthInPoints = CGRectGetWidth([[UIScreen mainScreen] bounds]);
|
|
||||||
CGFloat screenHeightInPoints = CGRectGetHeight([[UIScreen mainScreen] bounds]);
|
|
||||||
|
|
||||||
// deal with scroll host view reload after device orientation change here:
|
|
||||||
bool isLandscape = screenWidthInPoints > screenHeightInPoints;
|
|
||||||
CGFloat realViewFrameWidth = self.view.frame.size.width > self.view.frame.size.height ? self.view.frame.size.width : self.view.frame.size.height;
|
|
||||||
CGFloat realViewFrameHeight = self.view.frame.size.width < self.view.frame.size.height ? self.view.frame.size.width : self.view.frame.size.height;
|
|
||||||
if(!isLandscape) {
|
|
||||||
CGFloat tmpLength = realViewFrameWidth;
|
|
||||||
realViewFrameWidth = realViewFrameHeight;
|
|
||||||
realViewFrameHeight = tmpLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
self.collectionView.delaysContentTouches = NO;
|
self.collectionView.delaysContentTouches = NO;
|
||||||
self.collectionView.allowsMultipleSelection = NO;
|
self.collectionView.allowsMultipleSelection = NO;
|
||||||
@@ -1880,6 +1866,7 @@ static NSMutableSet* hostList;
|
|||||||
// Sort the host list in alphabetical order
|
// Sort the host list in alphabetical order
|
||||||
NSArray* sortedHostList = [[hostList allObjects] sortedArrayUsingSelector:@selector(compareName:)];
|
NSArray* sortedHostList = [[hostList allObjects] sortedArrayUsingSelector:@selector(compareName:)];
|
||||||
for (TemporaryHost* comp in sortedHostList) {
|
for (TemporaryHost* comp in sortedHostList) {
|
||||||
|
|
||||||
if(comp.state == StateOnline || comp.pairState == PairStatePaired || comp.pairState == PairStateUnknown) [self.hostCollectionVC addHost:comp];
|
if(comp.state == StateOnline || comp.pairState == PairStatePaired || comp.pairState == PairStateUnknown) [self.hostCollectionVC addHost:comp];
|
||||||
|
|
||||||
// new host card test
|
// new host card test
|
||||||
|
|||||||
@@ -800,8 +800,8 @@ const int FrontViewPositionNone = 0xff;
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (UIInterfaceOrientationMask)getCurrentOrientation{
|
- (UIInterfaceOrientationMask)getCurrentOrientation{
|
||||||
CGFloat screenHeightInPoints = CGRectGetHeight([[UIScreen mainScreen] bounds]);
|
CGFloat screenHeightInPoints = CGRectGetHeight(self.view.bounds);
|
||||||
CGFloat screenWidthInPoints = CGRectGetWidth([[UIScreen mainScreen] bounds]);
|
CGFloat screenWidthInPoints = CGRectGetWidth(self.view.bounds);
|
||||||
//lock the orientation accordingly after streaming is started
|
//lock the orientation accordingly after streaming is started
|
||||||
if(screenWidthInPoints > screenHeightInPoints) return UIInterfaceOrientationMaskLandscape;
|
if(screenWidthInPoints > screenHeightInPoints) return UIInterfaceOrientationMaskLandscape;
|
||||||
else return UIInterfaceOrientationMaskPortrait|UIInterfaceOrientationMaskPortraitUpsideDown;
|
else return UIInterfaceOrientationMaskPortrait|UIInterfaceOrientationMaskPortraitUpsideDown;
|
||||||
|
|||||||
@@ -47,6 +47,7 @@
|
|||||||
@property (strong, nonatomic) IBOutlet UISwitch *audioOnPcSwitch;
|
@property (strong, nonatomic) IBOutlet UISwitch *audioOnPcSwitch;
|
||||||
@property (strong, nonatomic) IBOutlet UIStackView *codecStack;
|
@property (strong, nonatomic) IBOutlet UIStackView *codecStack;
|
||||||
@property (strong, nonatomic) IBOutlet UIStackView *yuv444Stack;
|
@property (strong, nonatomic) IBOutlet UIStackView *yuv444Stack;
|
||||||
|
@property (strong, nonatomic) IBOutlet UIStackView *pipStack;
|
||||||
@property (strong, nonatomic) IBOutlet UIStackView *HdrStack;
|
@property (strong, nonatomic) IBOutlet UIStackView *HdrStack;
|
||||||
@property (strong, nonatomic) IBOutlet UIStackView *framepacingStack;
|
@property (strong, nonatomic) IBOutlet UIStackView *framepacingStack;
|
||||||
@property (strong, nonatomic) IBOutlet UIStackView *reverseMouseWheelDirectionStack;
|
@property (strong, nonatomic) IBOutlet UIStackView *reverseMouseWheelDirectionStack;
|
||||||
@@ -72,6 +73,7 @@
|
|||||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *codecSelector;
|
@property (strong, nonatomic) IBOutlet UISegmentedControl *codecSelector;
|
||||||
@property (strong, nonatomic) IBOutlet UISwitch *hdrSwitch;
|
@property (strong, nonatomic) IBOutlet UISwitch *hdrSwitch;
|
||||||
@property (strong, nonatomic) IBOutlet UISwitch *yuv444Switch;
|
@property (strong, nonatomic) IBOutlet UISwitch *yuv444Switch;
|
||||||
|
@property (strong, nonatomic) IBOutlet UISwitch *pipSwitch;
|
||||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *framePacingSelector;
|
@property (strong, nonatomic) IBOutlet UISegmentedControl *framePacingSelector;
|
||||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *reverseMouseWheelDirectionSelector;
|
@property (strong, nonatomic) IBOutlet UISegmentedControl *reverseMouseWheelDirectionSelector;
|
||||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *statsOverlaySelector;
|
@property (strong, nonatomic) IBOutlet UISegmentedControl *statsOverlaySelector;
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ BOOL isCustomResolution(CGSize res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)updateResolutionTable{
|
- (void)updateResolutionTable{
|
||||||
UIWindow *window = UIApplication.sharedApplication.windows.firstObject;
|
UIWindow *window = self.view.window;
|
||||||
CGFloat screenScale = window.screen.scale;
|
CGFloat screenScale = window.screen.scale;
|
||||||
CGFloat safeAreaWidth = (window.frame.size.width - window.safeAreaInsets.left - window.safeAreaInsets.right) * screenScale;
|
CGFloat safeAreaWidth = (window.frame.size.width - window.safeAreaInsets.left - window.safeAreaInsets.right) * screenScale;
|
||||||
CGFloat appWindowWidth = window.frame.size.width * screenScale;
|
CGFloat appWindowWidth = window.frame.size.width * screenScale;
|
||||||
@@ -482,6 +482,7 @@ BOOL isCustomResolution(CGSize res) {
|
|||||||
[self addSetting:self.codecStack ofId:@"codecStack" withInfoTag:NO withDynamicLabel:NO to:videoSection];
|
[self addSetting:self.codecStack ofId:@"codecStack" withInfoTag:NO withDynamicLabel:NO to:videoSection];
|
||||||
[self addSetting:self.HdrStack ofId:@"HdrStack" withInfoTag:NO withDynamicLabel:NO to:videoSection];
|
[self addSetting:self.HdrStack ofId:@"HdrStack" withInfoTag:NO withDynamicLabel:NO to:videoSection];
|
||||||
[self addSetting:self.yuv444Stack ofId:@"yuv444Stack" withInfoTag:YES withDynamicLabel:NO to:videoSection];
|
[self addSetting:self.yuv444Stack ofId:@"yuv444Stack" withInfoTag:YES withDynamicLabel:NO to:videoSection];
|
||||||
|
[self addSetting:self.pipStack ofId:@"pipStack" withInfoTag:YES withDynamicLabel:NO to:videoSection];
|
||||||
[videoSection addToParentStack:_parentStack];
|
[videoSection addToParentStack:_parentStack];
|
||||||
[videoSection setExpanded:YES];
|
[videoSection setExpanded:YES];
|
||||||
|
|
||||||
@@ -1245,6 +1246,7 @@ BOOL isCustomResolution(CGSize res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[self.yuv444Switch setOn:currentSettings.enableYUV444];
|
[self.yuv444Switch setOn:currentSettings.enableYUV444];
|
||||||
|
[self.pipSwitch setOn:currentSettings.enablePIP];
|
||||||
[self.statsOverlaySelector setSelectedSegmentIndex:currentSettings.statsOverlayLevel.intValue];
|
[self.statsOverlaySelector setSelectedSegmentIndex:currentSettings.statsOverlayLevel.intValue];
|
||||||
[self.citrixX1MouseSwitch setOn:currentSettings.btMouseSupport];
|
[self.citrixX1MouseSwitch setOn:currentSettings.btMouseSupport];
|
||||||
[self.optimizeGamesSwitch setOn: currentSettings.optimizeGames];
|
[self.optimizeGamesSwitch setOn: currentSettings.optimizeGames];
|
||||||
@@ -1957,6 +1959,7 @@ BOOL isCustomResolution(CGSize res) {
|
|||||||
BOOL audioOnPC = self.audioOnPcSwitch.isOn;
|
BOOL audioOnPC = self.audioOnPcSwitch.isOn;
|
||||||
uint32_t preferredCodec = [self getChosenCodecPreference];
|
uint32_t preferredCodec = [self getChosenCodecPreference];
|
||||||
BOOL enableYUV444 = self.yuv444Switch.isOn;
|
BOOL enableYUV444 = self.yuv444Switch.isOn;
|
||||||
|
BOOL enablePIP = self.pipSwitch.isOn;
|
||||||
BOOL btMouseSupport = self.citrixX1MouseSwitch.isOn;
|
BOOL btMouseSupport = self.citrixX1MouseSwitch.isOn;
|
||||||
BOOL useFramePacing = [self.framePacingSelector selectedSegmentIndex] == 1;
|
BOOL useFramePacing = [self.framePacingSelector selectedSegmentIndex] == 1;
|
||||||
NSInteger touchMode = [self isNotNativeTouchOnly] ? self.touchModeSelector.selectedSegmentIndex : NativeTouchOnly;
|
NSInteger touchMode = [self isNotNativeTouchOnly] ? self.touchModeSelector.selectedSegmentIndex : NativeTouchOnly;
|
||||||
@@ -1993,6 +1996,7 @@ BOOL isCustomResolution(CGSize res) {
|
|||||||
audioOnPC:audioOnPC
|
audioOnPC:audioOnPC
|
||||||
preferredCodec:preferredCodec
|
preferredCodec:preferredCodec
|
||||||
enableYUV444:enableYUV444
|
enableYUV444:enableYUV444
|
||||||
|
enablePIP:enablePIP
|
||||||
useFramePacing:useFramePacing
|
useFramePacing:useFramePacing
|
||||||
enableHdr:enableHdr
|
enableHdr:enableHdr
|
||||||
btMouseSupport:btMouseSupport
|
btMouseSupport:btMouseSupport
|
||||||
|
|||||||
@@ -14,18 +14,23 @@
|
|||||||
#import "StreamView.h"
|
#import "StreamView.h"
|
||||||
#import "LayoutOnScreenControlsViewController.h"
|
#import "LayoutOnScreenControlsViewController.h"
|
||||||
#import "MainFrameViewController.h"
|
#import "MainFrameViewController.h"
|
||||||
|
#import "StreamManager.h"
|
||||||
|
|
||||||
|
#import <AVKit/AVKit.h>
|
||||||
#import <UIKit/UIKit.h>
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
#if TARGET_OS_TV
|
#if TARGET_OS_TV
|
||||||
@import GameController;
|
@import GameController;
|
||||||
|
|
||||||
@interface StreamFrameViewController : GCEventViewController <ConnectionCallbacks, ControllerSupportDelegate, UserInteractionDelegate, UIScrollViewDelegate>
|
@interface StreamFrameViewController : GCEventViewController <ConnectionCallbacks, ControllerSupportDelegate, UserInteractionDelegate, UIScrollViewDelegate, AVPictureInPictureControllerDelegate>
|
||||||
#else
|
#else
|
||||||
@interface StreamFrameViewController : UIViewController <ConnectionCallbacks, ControllerSupportDelegate, UserInteractionDelegate, UIScrollViewDelegate, ToolboxSpecialEntryDelegate>
|
@interface StreamFrameViewController : UIViewController <ConnectionCallbacks, ControllerSupportDelegate, UserInteractionDelegate, UIScrollViewDelegate, ToolboxSpecialEntryDelegate, AVPictureInPictureControllerDelegate>
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@property (nonatomic) StreamManager* streamMan;
|
||||||
@property (nonatomic) StreamConfiguration* streamConfig;
|
@property (nonatomic) StreamConfiguration* streamConfig;
|
||||||
|
@property (nonatomic, strong) AVPictureInPictureController *pipController API_AVAILABLE(ios(9.0));
|
||||||
|
@property (nonatomic, strong) AVPictureInPictureControllerContentSource *pipContentSource API_AVAILABLE(ios(15.0)); // Needed for iOS 15+ layer-based PiP
|
||||||
@property (nonatomic, assign) MainFrameViewController *mainFrameViewcontroller;
|
@property (nonatomic, assign) MainFrameViewController *mainFrameViewcontroller;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,12 @@
|
|||||||
// Copyright © 2024 True砖家 @ Bilibili. All rights reserved.
|
// Copyright © 2024 True砖家 @ Bilibili. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#import <AVKit/AVKit.h>
|
||||||
#import "StreamFrameViewController.h"
|
#import "StreamFrameViewController.h"
|
||||||
#import "MainFrameViewController.h"
|
#import "MainFrameViewController.h"
|
||||||
#import "VideoDecoderRenderer.h"
|
#import "VideoDecoderRenderer.h"
|
||||||
#import "StreamManager.h"
|
#import "StreamManager.h"
|
||||||
|
#import "SceneDelegate.h"
|
||||||
#import "ControllerSupport.h"
|
#import "ControllerSupport.h"
|
||||||
#import "DataManager.h"
|
#import "DataManager.h"
|
||||||
#import "CustomEdgeSlideGestureRecognizer.h"
|
#import "CustomEdgeSlideGestureRecognizer.h"
|
||||||
@@ -69,6 +71,8 @@
|
|||||||
*/
|
*/
|
||||||
UIWindow *_deviceWindow;
|
UIWindow *_deviceWindow;
|
||||||
dispatch_block_t _delayedRemoveExtScreen;
|
dispatch_block_t _delayedRemoveExtScreen;
|
||||||
|
VideoDecoderRenderer *_videoRenderer;
|
||||||
|
BOOL _isRestoringFromPiP;
|
||||||
#if !TARGET_OS_TV
|
#if !TARGET_OS_TV
|
||||||
CustomEdgeSlideGestureRecognizer *_slideToSettingsRecognizer;
|
CustomEdgeSlideGestureRecognizer *_slideToSettingsRecognizer;
|
||||||
CustomEdgeSlideGestureRecognizer *_slideToCmdToolRecognizer;
|
CustomEdgeSlideGestureRecognizer *_slideToCmdToolRecognizer;
|
||||||
@@ -76,6 +80,61 @@
|
|||||||
LayoutOnScreenControlsViewController *_layoutOnScreenControlsVC;
|
LayoutOnScreenControlsViewController *_layoutOnScreenControlsVC;
|
||||||
ToolboxViewController* toolBoxViewController;
|
ToolboxViewController* toolBoxViewController;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)pictureInPictureControllerWillStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
|
||||||
|
_streamView.hidden = YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)pictureInPictureControllerDidStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController failedToStartPictureInPictureWithError:(NSError *)error {
|
||||||
|
Log(LOG_E, @"PiP Failed to Start: %@", error);
|
||||||
|
_streamView.hidden = NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)pictureInPictureControllerWillStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)pictureInPictureControllerDidStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
|
||||||
|
_streamView.hidden = NO;
|
||||||
|
if (!_isRestoringFromPiP) {
|
||||||
|
[self returnToMainFrame];
|
||||||
|
}
|
||||||
|
_isRestoringFromPiP = NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController restoreUserInterfaceForPictureInPictureStopWithCompletionHandler:(void (^)(BOOL restored))completionHandler {
|
||||||
|
_isRestoringFromPiP = YES;
|
||||||
|
_streamView.hidden = NO;
|
||||||
|
completionHandler(YES);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController
|
||||||
|
didTransitionToRenderSize:(CMVideoDimensions)newRenderSize API_AVAILABLE(ios(15.0)){
|
||||||
|
Log(LOG_I, @"PiP transitioned to size: %d x %d", newRenderSize.width, newRenderSize.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indicate that playback is never paused for a live stream
|
||||||
|
- (BOOL)pictureInPictureControllerIsPlaybackPaused:(AVPictureInPictureController *)pictureInPictureController API_AVAILABLE(ios(14.0)) {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return an indefinite time range for a live stream
|
||||||
|
- (CMTimeRange)pictureInPictureControllerTimeRangeForPlayback:(AVPictureInPictureController *)pictureInPictureController API_AVAILABLE(ios(14.0)) {
|
||||||
|
return CMTimeRangeMake(kCMTimeZero, kCMTimePositiveInfinity);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController setPlaying:(BOOL)playing API_AVAILABLE(ios(14.0)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Live streams typically can't skip, so just call the completion handler.
|
||||||
|
- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController skipByInterval:(CMTime)skipInterval completionHandler:(void (^)(void))completionHandler API_AVAILABLE(ios(14.0)) {
|
||||||
|
if (completionHandler) {
|
||||||
|
completionHandler();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)isFirstLaunch {
|
- (BOOL)isFirstLaunch {
|
||||||
@@ -95,10 +154,71 @@
|
|||||||
return (_settings.touchMode.intValue == RelativeTouch || _settings.touchMode.intValue == NativeTouch || _settings.touchMode.intValue == AbsoluteTouch) && _settings.onscreenControls.intValue == OnScreenControlsLevelCustom;
|
return (_settings.touchMode.intValue == RelativeTouch || _settings.touchMode.intValue == NativeTouch || _settings.touchMode.intValue == AbsoluteTouch) && _settings.onscreenControls.intValue == OnScreenControlsLevelCustom;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)configOscLayoutTool{
|
- (void)setupPiPControllerWithRenderer:(VideoDecoderRenderer *)videoRenderer { // Ensure we have the renderer and its layer
|
||||||
|
if (self.pipController) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Log(LOG_I, @"Setting up PiP controller...");
|
||||||
|
|
||||||
|
if (!videoRenderer || !videoRenderer.displayLayer) {
|
||||||
|
Log(LOG_E, @"PiP setup failed: Video renderer or display layer not ready.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AVSampleBufferDisplayLayer *streamLayer = videoRenderer.displayLayer;
|
||||||
|
|
||||||
|
if ([AVPictureInPictureController isPictureInPictureSupported]) {
|
||||||
|
if (@available(iOS 15.0, *)) {
|
||||||
|
self.pipContentSource = [[AVPictureInPictureControllerContentSource alloc] initWithSampleBufferDisplayLayer:streamLayer playbackDelegate:(id<AVPictureInPictureSampleBufferPlaybackDelegate>)self];
|
||||||
|
self.pipController = [[AVPictureInPictureController alloc] initWithContentSource:self.pipContentSource];
|
||||||
|
self.pipController.canStartPictureInPictureAutomaticallyFromInline = YES;
|
||||||
|
} else {
|
||||||
|
Log(LOG_E, @"PiP not fully supported on this device.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.pipController) {
|
||||||
|
self.pipController.delegate = self;
|
||||||
|
Log(LOG_I, @"PiP controller created successfully.");
|
||||||
|
} else {
|
||||||
|
Log(LOG_E, @"Failed to create PiP controller.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log(LOG_E, @"PiP not supported on this device.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)cleanupPiPController {
|
||||||
|
if (self.pipController) {
|
||||||
|
self.pipController.delegate = nil;
|
||||||
|
self.pipController = nil;
|
||||||
|
if (@available(iOS 15.0, *)) {
|
||||||
|
self.pipContentSource = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log(LOG_I, @"PiP controller cleaned up.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)updateToolboxSpecialEntries{
|
||||||
if([self isOscLayoutToolEnabled]){
|
if([self isOscLayoutToolEnabled]){
|
||||||
if(![toolBoxViewController.specialEntries containsObject:@"widgetLayoutTool"]) [toolBoxViewController.specialEntries insertObject:@"widgetLayoutTool" atIndex:0];
|
if(![toolBoxViewController.specialEntries containsObject:@"widgetLayoutTool"]) [toolBoxViewController.specialEntries insertObject:@"widgetLayoutTool" atIndex:0];
|
||||||
if(![toolBoxViewController.specialEntries containsObject:@"widgetSwitchTool"]) [toolBoxViewController.specialEntries insertObject:@"widgetSwitchTool" atIndex:1];
|
if(![toolBoxViewController.specialEntries containsObject:@"widgetSwitchTool"]) [toolBoxViewController.specialEntries insertObject:@"widgetSwitchTool" atIndex:1];
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
[toolBoxViewController.specialEntries removeObject:@"widgetLayoutTool"];
|
||||||
|
[toolBoxViewController.specialEntries removeObject:@"widgetSwitchTool"];
|
||||||
|
}
|
||||||
|
if(_settings.enablePIP){
|
||||||
|
if(![toolBoxViewController.specialEntries containsObject:@"enterPip"]) [toolBoxViewController.specialEntries addObject:@"enterPip"];
|
||||||
|
}
|
||||||
|
else [toolBoxViewController.specialEntries removeObject:@"enterPip"];
|
||||||
|
|
||||||
|
NSLog(@"toolBoxViewController.specialEntries %@", toolBoxViewController.specialEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)configOscLayoutTool{
|
||||||
|
if([self isOscLayoutToolEnabled]){
|
||||||
_oscLayoutTapRecoginizer = [[CustomTapGestureRecognizer alloc] initWithTarget:self action:@selector(openWidgetLayoutTool)];
|
_oscLayoutTapRecoginizer = [[CustomTapGestureRecognizer alloc] initWithTarget:self action:@selector(openWidgetLayoutTool)];
|
||||||
_oscLayoutTapRecoginizer.numberOfTouchesRequired = _settings.oscLayoutToolFingers.intValue; //tap a predefined number of fingers to open osc layout tool
|
_oscLayoutTapRecoginizer.numberOfTouchesRequired = _settings.oscLayoutToolFingers.intValue; //tap a predefined number of fingers to open osc layout tool
|
||||||
_oscLayoutTapRecoginizer.tapDownTimeThreshold = 0.2;
|
_oscLayoutTapRecoginizer.tapDownTimeThreshold = 0.2;
|
||||||
@@ -122,15 +242,13 @@
|
|||||||
_layoutOnScreenControlsVC.view.backgroundColor = UIColor.clearColor;
|
_layoutOnScreenControlsVC.view.backgroundColor = UIColor.clearColor;
|
||||||
_layoutOnScreenControlsVC.modalPresentationStyle = UIModalPresentationOverCurrentContext;
|
_layoutOnScreenControlsVC.modalPresentationStyle = UIModalPresentationOverCurrentContext;
|
||||||
}
|
}
|
||||||
else{
|
|
||||||
[toolBoxViewController.specialEntries removeObject:@"widgetLayoutTool"];
|
|
||||||
[toolBoxViewController.specialEntries removeObject:@"widgetSwitchTool"];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)presentCommandManagerViewController{
|
- (void)presentCommandManagerViewController{
|
||||||
|
ToolboxViewController* oldToolboxVC = toolBoxViewController;
|
||||||
toolBoxViewController = [[ToolboxViewController alloc] init];
|
toolBoxViewController = [[ToolboxViewController alloc] init];
|
||||||
toolBoxViewController.specialEntryDelegate = self;
|
toolBoxViewController.specialEntryDelegate = self;
|
||||||
|
toolBoxViewController.specialEntries = oldToolboxVC.specialEntries;
|
||||||
[self configOscLayoutTool];
|
[self configOscLayoutTool];
|
||||||
[self presentViewController:toolBoxViewController animated:YES completion:^{
|
[self presentViewController:toolBoxViewController animated:YES completion:^{
|
||||||
//[self->toolBoxViewController setupConstraints];
|
//[self->toolBoxViewController setupConstraints];
|
||||||
@@ -191,6 +309,7 @@
|
|||||||
_settings = [[[DataManager alloc] init] getSettings]; //StreamFrameViewController retrieve the settings here.
|
_settings = [[[DataManager alloc] init] getSettings]; //StreamFrameViewController retrieve the settings here.
|
||||||
overlayLevel = _settings.statsOverlayLevel.intValue;
|
overlayLevel = _settings.statsOverlayLevel.intValue;
|
||||||
[self configOscLayoutTool];
|
[self configOscLayoutTool];
|
||||||
|
[self updateToolboxSpecialEntries];
|
||||||
[self configSwipeGestures];
|
[self configSwipeGestures];
|
||||||
[self configZoomGestureAndAddStreamView];
|
[self configZoomGestureAndAddStreamView];
|
||||||
[self->_streamView disableOnScreenControls]; //don't know why but this must be called outside the streamview class, just put it here. execute in streamview class cause hang
|
[self->_streamView disableOnScreenControls]; //don't know why but this must be called outside the streamview class, just put it here. execute in streamview class cause hang
|
||||||
@@ -224,20 +343,24 @@
|
|||||||
[super viewDidAppear:animated];
|
[super viewDidAppear:animated];
|
||||||
|
|
||||||
_deviceWindow = self.view.window;
|
_deviceWindow = self.view.window;
|
||||||
|
if (@available(iOS 13.0, *)) {
|
||||||
if (UIScreen.screens.count > 1 && [self isAirPlayEnabled]) {
|
UIScreen *currentScreen = self.view.window.windowScene.screen;
|
||||||
[self prepExtScreen:UIScreen.screens.lastObject];
|
if (UIScreen.screens.count > 1 && [self isAirPlayEnabled] && currentScreen == UIScreen.mainScreen) {
|
||||||
}
|
[SceneDelegate setExternalDisplayRenderView:self->_streamVideoRenderView];
|
||||||
else {
|
}
|
||||||
/*
|
else {
|
||||||
_settings.externalDisplayMode.intValue:
|
/*
|
||||||
0 - stage manager
|
_settings.externalDisplayMode.intValue:
|
||||||
1 - airplay
|
0 - stage manager
|
||||||
2 - disabled
|
1 - airplay
|
||||||
*/
|
2 - disabled
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
*/
|
||||||
if(self->_settings.externalDisplayMode.intValue != 2) [self->_streamView insertSubview:self->_streamVideoRenderView atIndex:0];
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
});
|
[self->_streamView insertSubview:self->_streamVideoRenderView atIndex:0];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback on earlier versions
|
||||||
}
|
}
|
||||||
|
|
||||||
self->_streamView.originalFrame = self->_streamView.frame;
|
self->_streamView.originalFrame = self->_streamView.frame;
|
||||||
@@ -382,19 +505,18 @@
|
|||||||
toolBoxViewController = [[ToolboxViewController alloc] init];
|
toolBoxViewController = [[ToolboxViewController alloc] init];
|
||||||
toolBoxViewController.specialEntryDelegate = self;
|
toolBoxViewController.specialEntryDelegate = self;
|
||||||
|
|
||||||
|
_isRestoringFromPiP = NO;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
_settings.externalDisplayMode.intValue:
|
_settings.externalDisplayMode.intValue:
|
||||||
0 - stage manager
|
0 - stage manager
|
||||||
1 - airplay
|
1 - airplay
|
||||||
2 - disabled
|
|
||||||
*/
|
*/
|
||||||
if(_settings.externalDisplayMode.intValue == 2) _streamVideoRenderView = _streamView;
|
// A separate render view is always created to support external displays.
|
||||||
else {
|
_streamVideoRenderView = (StreamView*)[[UIView alloc] initWithFrame:self.view.frame];
|
||||||
//NSLog(@"renderView separated from streamView");
|
_streamVideoRenderView.bounds = _streamView.bounds;
|
||||||
_streamVideoRenderView = (StreamView*)[[UIView alloc] initWithFrame:self.view.frame];
|
_streamVideoRenderView.userInteractionEnabled = false;
|
||||||
_streamVideoRenderView.bounds = _streamView.bounds;
|
|
||||||
_streamVideoRenderView.userInteractionEnabled = false; // this will prevent renderView from intrecepting touchEvents which shall be interacting with streamView
|
|
||||||
}
|
|
||||||
//[_streamView setupStreamView:_controllerSupport interactionDelegate:self config:self.streamConfig];
|
//[_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.
|
[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.
|
||||||
|
|
||||||
@@ -516,6 +638,10 @@
|
|||||||
[self->_streamView readyToBringUpSoftKeyboardByToolbox];
|
[self->_streamView readyToBringUpSoftKeyboardByToolbox];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)enterPip{
|
||||||
|
[self.pipController startPictureInPicture];
|
||||||
|
}
|
||||||
|
|
||||||
- (void)oscLayoutClosed{
|
- (void)oscLayoutClosed{
|
||||||
// Handle the callback
|
// Handle the callback
|
||||||
[self->_streamView disableOnScreenControls]; // add this to get realtime back menu working.
|
[self->_streamView disableOnScreenControls]; // add this to get realtime back menu working.
|
||||||
@@ -642,6 +768,11 @@
|
|||||||
- (void) returnToMainFrame {
|
- (void) returnToMainFrame {
|
||||||
// Reset display mode back to default
|
// Reset display mode back to default
|
||||||
[self updatePreferredDisplayMode:NO];
|
[self updatePreferredDisplayMode:NO];
|
||||||
|
[SceneDelegate clearExternalDisplayRenderView];
|
||||||
|
|
||||||
|
if (_settings.enablePIP) {
|
||||||
|
[self cleanupPiPController];
|
||||||
|
}
|
||||||
|
|
||||||
[_statsUpdateTimer invalidate];
|
[_statsUpdateTimer invalidate];
|
||||||
_statsUpdateTimer = nil;
|
_statsUpdateTimer = nil;
|
||||||
@@ -654,19 +785,34 @@
|
|||||||
// External Screen connected
|
// External Screen connected
|
||||||
- (void)extScreenDidConnect:(NSNotification *)notification {
|
- (void)extScreenDidConnect:(NSNotification *)notification {
|
||||||
Log(LOG_I, @"External Screen Connected");
|
Log(LOG_I, @"External Screen Connected");
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
if ([self isAirPlayEnabled] && [notification.object isKindOfClass:[UIScreen class]]) {
|
||||||
[self prepExtScreen:notification.object];
|
UIScreen *extScreen = (UIScreen *)notification.object;
|
||||||
});
|
if (_streamVideoRenderView) {
|
||||||
|
// Remove from current superview before passing it
|
||||||
|
[_streamVideoRenderView removeFromSuperview];
|
||||||
|
[SceneDelegate setExternalDisplayRenderView:_streamVideoRenderView];
|
||||||
|
NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
|
||||||
|
[nc postNotificationName:@"ScreenChanged" object:self];
|
||||||
|
} else {
|
||||||
|
Log(LOG_W, @"_streamVideoRenderView is nil when external screen connected.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// External Screen disconnected
|
// External Screen disconnected
|
||||||
- (void)extScreenDidDisconnect:(NSNotification *)notification {
|
- (void)extScreenDidDisconnect:(NSNotification *)notification {
|
||||||
Log(LOG_I, @"External Screen Disconnected");
|
Log(LOG_I, @"External Screen Disconnected");
|
||||||
if(UIScreen.screens.count < 2)
|
if(UIScreen.screens.count < 2) {
|
||||||
{
|
[SceneDelegate clearExternalDisplayRenderView];
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
// Add the render view back to the local StreamView if AirPlay was active
|
||||||
[self removeExtScreen];
|
if ([self isAirPlayEnabled]) {
|
||||||
});
|
if (_streamVideoRenderView && _streamView) {
|
||||||
|
[_streamView insertSubview:_streamVideoRenderView atIndex:0];
|
||||||
|
[self handleViewResize]; // Adjust frames as needed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
|
||||||
|
[nc postNotificationName:@"ScreenChanged" object:self]; // Your existing notification
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -675,7 +821,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL) isAirPlaying{
|
- (BOOL) isAirPlaying{
|
||||||
return _extWindow != nil && _extWindow.hidden == NO;
|
if (_settings.externalDisplayMode.intValue == 1 && _streamVideoRenderView) {
|
||||||
|
return _streamVideoRenderView.hidden;
|
||||||
|
}
|
||||||
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL) isAirPlayEnabled{
|
- (BOOL) isAirPlayEnabled{
|
||||||
@@ -685,45 +834,12 @@
|
|||||||
- (void) reloadAirPlayConfig{
|
- (void) reloadAirPlayConfig{
|
||||||
if (UIScreen.screens.count == 1){return;}
|
if (UIScreen.screens.count == 1){return;}
|
||||||
if (![self isAirPlaying] && [self isAirPlayEnabled]){
|
if (![self isAirPlaying] && [self isAirPlayEnabled]){
|
||||||
[self prepExtScreen:UIScreen.screens.lastObject];
|
[SceneDelegate setExternalDisplayRenderView:_streamVideoRenderView];
|
||||||
}else if ([self isAirPlaying] && ![self isAirPlayEnabled]){
|
}else if ([self isAirPlaying] && ![self isAirPlayEnabled]){
|
||||||
[self removeExtScreen];
|
[SceneDelegate clearExternalDisplayRenderView];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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;
|
|
||||||
_streamVideoRenderView.bounds = frame;
|
|
||||||
_streamVideoRenderView.frame = frame;
|
|
||||||
NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
|
|
||||||
[nc postNotificationName:@"ScreenChanged" object:self];
|
|
||||||
[_extWindow addSubview:_streamVideoRenderView];
|
|
||||||
_extWindow.hidden = NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)removeExtScreen {
|
|
||||||
Log(LOG_I, @"Removing External Screen");
|
|
||||||
_extWindow.hidden = YES;
|
|
||||||
[self handleViewResize];
|
|
||||||
/*
|
|
||||||
_settings.externalDisplayMode.intValue:
|
|
||||||
0 - stage manager
|
|
||||||
1 - airplay
|
|
||||||
2 - disabled
|
|
||||||
*/
|
|
||||||
if(_settings.externalDisplayMode.intValue != 2) [_streamView insertSubview:_streamVideoRenderView atIndex:0];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void) handleViewResize{
|
- (void) handleViewResize{
|
||||||
_streamView.bounds = _deviceWindow.bounds;
|
_streamView.bounds = _deviceWindow.bounds;
|
||||||
_streamView.frame = _deviceWindow.frame;
|
_streamView.frame = _deviceWindow.frame;
|
||||||
@@ -739,10 +855,16 @@
|
|||||||
|
|
||||||
// This will fire if the user opens control center or gets a low battery message
|
// This will fire if the user opens control center or gets a low battery message
|
||||||
- (void)applicationWillResignActive:(NSNotification *)notification {
|
- (void)applicationWillResignActive:(NSNotification *)notification {
|
||||||
|
|
||||||
|
//[self.pipController startPictureInPicture];
|
||||||
|
//sleep(1);
|
||||||
|
|
||||||
if (_inactivityTimer != nil) {
|
if (_inactivityTimer != nil) {
|
||||||
[_inactivityTimer invalidate];
|
[_inactivityTimer invalidate];
|
||||||
|
_inactivityTimer = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#if !TARGET_OS_TV
|
#if !TARGET_OS_TV
|
||||||
// Terminate the stream if the app is inactive for 60 seconds
|
// Terminate the stream if the app is inactive for 60 seconds
|
||||||
Log(LOG_I, @"Starting inactivity termination timer");
|
Log(LOG_I, @"Starting inactivity termination timer");
|
||||||
@@ -769,19 +891,27 @@
|
|||||||
[_inactivityTimer invalidate];
|
[_inactivityTimer invalidate];
|
||||||
_inactivityTimer = nil;
|
_inactivityTimer = nil;
|
||||||
}
|
}
|
||||||
|
if (self.pipController && self.pipController.isPictureInPictureActive) {
|
||||||
|
[self.pipController stopPictureInPicture];
|
||||||
|
}
|
||||||
|
|
||||||
|
//self.pipController = nil;
|
||||||
|
//[self setupPiPControllerWithRenderer:self->_streamMan.videoRenderer];
|
||||||
|
//self->_streamMan.videoRenderer.
|
||||||
}
|
}
|
||||||
|
|
||||||
// This fires when the home button is pressed
|
// This fires when the home button is pressed
|
||||||
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
||||||
/*
|
|
||||||
Log(LOG_I, @"Terminating stream immediately for backgrounding");
|
|
||||||
|
|
||||||
if (_inactivityTimer != nil) {
|
NSLog(@"did enter background, %d, %@, %d", _settings.enablePIP, self.pipController, self.pipController.isPictureInPictureActive);
|
||||||
[_inactivityTimer invalidate];
|
if (_settings.enablePIP && self.pipController && self.pipController.isPictureInPictureActive) {
|
||||||
_inactivityTimer = nil;
|
Log(LOG_I, @"PIP is active, not terminating stream");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
// [self returnToMainFrame];
|
#if !TARGET_OS_TV
|
||||||
|
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)expandSettingsView{
|
- (void)expandSettingsView{
|
||||||
@@ -1059,8 +1189,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void) videoContentShown {
|
- (void) videoContentShown {
|
||||||
[_spinner stopAnimating];
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
[self.view setBackgroundColor:[UIColor blackColor]];
|
[self->_spinner stopAnimating];
|
||||||
|
[self.view setBackgroundColor:[UIColor blackColor]];
|
||||||
|
|
||||||
|
if (@available(iOS 15.0, *)) {
|
||||||
|
if (self->_settings.enablePIP) {
|
||||||
|
if (self->_streamMan && self->_streamMan.videoRenderer) {
|
||||||
|
Log(LOG_I, @"Setting up PiP with renderer: %p", self->_streamMan.videoRenderer);
|
||||||
|
[self setupPiPControllerWithRenderer:self->_streamMan.videoRenderer];
|
||||||
|
} else {
|
||||||
|
Log(LOG_I, @"No renderer available for PiP setup");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)didReceiveMemoryWarning
|
- (void)didReceiveMemoryWarning
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import UIKit
|
|||||||
@objc optional func openWidgetLayoutTool()
|
@objc optional func openWidgetLayoutTool()
|
||||||
@objc optional func switchWidgetProfile()
|
@objc optional func switchWidgetProfile()
|
||||||
@objc optional func bringUpSoftKeyboard()
|
@objc optional func bringUpSoftKeyboard()
|
||||||
|
@objc optional func enterPip()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -27,11 +28,12 @@ import UIKit
|
|||||||
private let viewBackgroundColor = UIColor(white: 0.2, alpha: 0.8);
|
private let viewBackgroundColor = UIColor(white: 0.2, alpha: 0.8);
|
||||||
private let highlightColor = UIColor(white: 0.39, alpha: 0.8);
|
private let highlightColor = UIColor(white: 0.39, alpha: 0.8);
|
||||||
private let titleLabel = UILabel()
|
private let titleLabel = UILabel()
|
||||||
@objc public var specialEntries : NSMutableArray = ["widgetSwitchTool", "widgetLayoutTool", "bringUpSoftKeyboard"]
|
@objc public var specialEntries : NSMutableArray = ["widgetSwitchTool", "widgetLayoutTool", "bringUpSoftKeyboard", "enterPip"]
|
||||||
private let specialEntryAliasDic : [String:String] = [
|
private let specialEntryAliasDic : [String:String] = [
|
||||||
"widgetSwitchTool":SwiftLocalizationHelper.localizedString(forKey: "[ Switch among on-screen widget profiles ]"),
|
"widgetSwitchTool":SwiftLocalizationHelper.localizedString(forKey: "[ Switch among on-screen widget profiles ]"),
|
||||||
"widgetLayoutTool":SwiftLocalizationHelper.localizedString(forKey: "[ Open on-screen widget tool ]"),
|
"widgetLayoutTool":SwiftLocalizationHelper.localizedString(forKey: "[ Open on-screen widget tool ]"),
|
||||||
"bringUpSoftKeyboard":SwiftLocalizationHelper.localizedString(forKey: "[ Bring up soft keyboard ]")
|
"bringUpSoftKeyboard":SwiftLocalizationHelper.localizedString(forKey: "[ Bring up soft keyboard ]"),
|
||||||
|
"enterPip":SwiftLocalizationHelper.localizedString(forKey: "[ Enter picture-in-picture mode ]")
|
||||||
]
|
]
|
||||||
|
|
||||||
private var viewPinned: Bool = false
|
private var viewPinned: Bool = false
|
||||||
@@ -374,6 +376,8 @@ import UIKit
|
|||||||
specialEntryDelegate?.switchWidgetProfile?()
|
specialEntryDelegate?.switchWidgetProfile?()
|
||||||
case "bringUpSoftKeyboard":
|
case "bringUpSoftKeyboard":
|
||||||
specialEntryDelegate?.bringUpSoftKeyboard?()
|
specialEntryDelegate?.bringUpSoftKeyboard?()
|
||||||
|
case "enterPip":
|
||||||
|
specialEntryDelegate?.enterPip?()
|
||||||
default: break
|
default: break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,36 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"3rP-dh-TJi.text" : {
|
||||||
|
"comment" : "Class = \"UILabel\"; text = \"Enable PiP\"; ObjectID = \"3rP-dh-TJi\";",
|
||||||
|
"extractionState" : "extracted_with_value",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "new",
|
||||||
|
"value" : "Enable PiP"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "画中画"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hant" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "啟用子母畫面"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-HK" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "啟用子母畫面"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"4FU-iP-c6T.segmentTitles[0]" : {
|
"4FU-iP-c6T.segmentTitles[0]" : {
|
||||||
"comment" : "Class = \"UISegmentedControl\"; 4FU-iP-c6T.segmentTitles[0] = \"Captured\"; ObjectID = \"4FU-iP-c6T\";",
|
"comment" : "Class = \"UISegmentedControl\"; 4FU-iP-c6T.segmentTitles[0] = \"Captured\"; ObjectID = \"4FU-iP-c6T\";",
|
||||||
"extractionState" : "extracted_with_value",
|
"extractionState" : "extracted_with_value",
|
||||||
|
|||||||
@@ -211,6 +211,36 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"4Ul-P6-eUk.text" : {
|
||||||
|
"comment" : "Class = \"UILabel\"; text = \"Enable PiP\"; ObjectID = \"4Ul-P6-eUk\";",
|
||||||
|
"extractionState" : "extracted_with_value",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "new",
|
||||||
|
"value" : "Enable PiP"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "画中画"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hant" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "啟用子母畫面"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-HK" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "啟用子母畫面"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"5JB-m0-nUF.text" : {
|
"5JB-m0-nUF.text" : {
|
||||||
"comment" : "Class = \"UILabel\"; text = \"YUV 4:4:4\"; ObjectID = \"5JB-m0-nUF\";",
|
"comment" : "Class = \"UILabel\"; text = \"YUV 4:4:4\"; ObjectID = \"5JB-m0-nUF\";",
|
||||||
"extractionState" : "extracted_with_value",
|
"extractionState" : "extracted_with_value",
|
||||||
|
|||||||
Reference in new issue
Block a user