Merge branch 'Integration' into feat/frame_pacing_metal

# Conflicts:
#	VoidLink.xcodeproj/project.pbxproj
#	VoidLink/Base.lproj/iPad.storyboard
#	VoidLink/Database/DataManager.m
#	VoidLink/Database/TemporarySettings.m
#	VoidLink/SceneDelegate.h
#	VoidLink/Stream/StreamManager.m
#	VoidLink/Stream/VideoDecoderRenderer.h
#	VoidLink/Stream/VideoDecoderRenderer.m
#	VoidLink/ViewControllers/MainFrameViewController.m
#	VoidLink/ViewControllers/SettingsViewController.m
This commit is contained in:
Acaki committed 2025-07-12 20:12:20 +08:00
commit 47a590e92a
34 files changed
+542 -168

No files matched your search

+19 -1
View File
@@ -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 UILabel *frameQueueSizeLabel; @property (strong, nonatomic) IBOutlet UILabel *frameQueueSizeLabel;
@property (strong, nonatomic) IBOutlet UISlider *frameQueueSizeSlider; @property (strong, nonatomic) IBOutlet UISlider *frameQueueSizeSlider;
@@ -230,7 +230,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;
@@ -467,6 +467,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];
NSInteger renderingBackend = [currentSettings.renderingBackend integerValue]; NSInteger renderingBackend = [currentSettings.renderingBackend integerValue];
[self.renderingBackendSelector setSelectedSegmentIndex:renderingBackend]; [self.renderingBackendSelector setSelectedSegmentIndex:renderingBackend];
@@ -1122,6 +1123,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;
@@ -1160,6 +1162,7 @@ BOOL isCustomResolution(CGSize res) {
audioOnPC:audioOnPC audioOnPC:audioOnPC
preferredCodec:preferredCodec preferredCodec:preferredCodec
enableYUV444:enableYUV444 enableYUV444:enableYUV444
enablePIP:enablePIP
useFramePacing:useFramePacing useFramePacing:useFramePacing
frameQueueSize:_frameQueueSize frameQueueSize:_frameQueueSize
enableHdr:enableHdr enableHdr:enableHdr
+6 -1
View File
@@ -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 */; };
@@ -149,6 +150,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>"; };
@@ -599,6 +602,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 */,
@@ -1041,7 +1046,7 @@
97E20DD22E21647A000D5DCA /* MetalVideoRenderer.m in Sources */, 97E20DD22E21647A000D5DCA /* MetalVideoRenderer.m in Sources */,
84351B2C2C29AD6100A77989 /* OSCProfilesTableViewController.m in Sources */, 84351B2C2C29AD6100A77989 /* OSCProfilesTableViewController.m in Sources */,
98D585701C0ED0E800F6CC00 /* TemporarySettings.m in Sources */, 98D585701C0ED0E800F6CC00 /* TemporarySettings.m in Sources */,
97E20DBF2E21637B000D5DCA /* SceneDelegate.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
} }
+1 -1
View File
@@ -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
+9
View File
@@ -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
+26 -3
View File
@@ -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="23504" 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="23727" 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="23506"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
<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,6 +870,27 @@
<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="382" 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>
<stackView opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="6hG-dw-YSr" userLabel="renderingBackend Stack"> <stackView opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="6hG-dw-YSr" userLabel="renderingBackend Stack">
<rect key="frame" x="20" y="1166" width="380" height="56.5"/> <rect key="frame" x="20" y="1166" width="380" height="56.5"/>
<subviews> <subviews>
@@ -953,7 +974,7 @@
</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="dH7-Sy-EdJ" id="cJm-bj-Aqo"/>
<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"/>
@@ -1000,6 +1021,8 @@
<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="performanceGraphStack" destination="H1O-Hi-8rG" id="AcS-Va-yZs"/> <outlet property="performanceGraphStack" destination="H1O-Hi-8rG" id="AcS-Va-yZs"/>
<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"/>
+23
View File
@@ -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"/>
+1
View File
@@ -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
+3
View File
@@ -81,9 +81,11 @@
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
// absoluteTouchMode:(BOOL)absoluteTouchMode
touchMode:(NSInteger)touchMode touchMode:(NSInteger)touchMode
statsOverlayLevel:(NSInteger)statsOverlayLevel statsOverlayLevel:(NSInteger)statsOverlayLevel
statsOverlayEnabled:(BOOL)statsOverlayEnabled statsOverlayEnabled:(BOOL)statsOverlayEnabled
@@ -124,6 +126,7 @@
settingsToSave.playAudioOnPC = audioOnPC; settingsToSave.playAudioOnPC = audioOnPC;
settingsToSave.preferredCodec = preferredCodec; settingsToSave.preferredCodec = preferredCodec;
settingsToSave.enableYUV444 = enableYUV444; settingsToSave.enableYUV444 = enableYUV444;
settingsToSave.enablePIP = enablePIP;
settingsToSave.enableHdr = enableHdr; settingsToSave.enableHdr = enableHdr;
settingsToSave.btMouseSupport = btMouseSupport; settingsToSave.btMouseSupport = btMouseSupport;
// settingsToSave.absoluteTouchMode = absoluteTouchMode; // settingsToSave.absoluteTouchMode = absoluteTouchMode;
+1
View File
@@ -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;
+2
View File
@@ -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.frameQueueSize = [NSNumber numberWithInteger:[[NSUserDefaults standardUserDefaults] integerForKey:@"frameQueueSize"]]; self.frameQueueSize = [NSNumber numberWithInteger:[[NSUserDefaults standardUserDefaults] integerForKey:@"frameQueueSize"]];
self.playAudioOnPC = [[NSUserDefaults standardUserDefaults] boolForKey:@"audioOnPC"]; self.playAudioOnPC = [[NSUserDefaults standardUserDefaults] boolForKey:@"audioOnPC"];
self.enableHdr = [[NSUserDefaults standardUserDefaults] boolForKey:@"enableHdr"]; self.enableHdr = [[NSUserDefaults standardUserDefaults] boolForKey:@"enableHdr"];
@@ -85,6 +86,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.frameQueueSize = settings.frameQueueSize; self.frameQueueSize = settings.frameQueueSize;
self.playAudioOnPC = settings.playAudioOnPC; self.playAudioOnPC = settings.playAudioOnPC;
self.enableHdr = settings.enableHdr; self.enableHdr = settings.enableHdr;
-1
View File
@@ -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
+1
View File
@@ -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;
+28 -1
View File
@@ -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>
@@ -28,6 +28,7 @@
<attribute name="deviceGyroMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/> <attribute name="deviceGyroMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="enableGraphs" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/> <attribute name="enableGraphs" optional="YES" 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="frameQueueSize" optional="YES" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="NO" syncable="YES"/> <attribute name="frameQueueSize" optional="YES" attributeType="Integer 32" defaultValueString="2" 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" : {
-1
View File
@@ -9,4 +9,3 @@
- (void)updatePreferredDisplayMode:(BOOL)streamActive withRenderView:(UIView *)renderView; - (void)updatePreferredDisplayMode:(BOOL)streamActive withRenderView:(UIView *)renderView;
@end @end
+2
View File
@@ -273,7 +273,9 @@ 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;
} }
+1
View File
@@ -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;
+1 -1
View File
@@ -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
+2
View File
@@ -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;
+7 -5
View File
@@ -30,6 +30,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 {
@@ -104,8 +105,9 @@
// 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]; self->_videoRenderer = [[VideoDecoderRenderer alloc] initWithView:self->_renderView callbacks:self->_callbacks streamAspectRatio:(float)self->_config.width / (float)self->_config.height];
self->_connection = [[Connection alloc] initWithConfig:self->_config renderer:renderer connectionCallbacks:self->_callbacks];
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];
}); });
@@ -154,7 +156,7 @@
- (NSString*) getStatsOverlayText { - (NSString*) getStatsOverlayText {
video_stats_t stats; video_stats_t stats;
if (!_connection) { if (!_connection) {
return nil; return nil;
} }
@@ -187,10 +189,10 @@
float interval = stats.endTime - stats.startTime; float interval = stats.endTime - stats.startTime;
float scalePlotMetrics = stats.frameDropMetrics.nsamples > 0 ? ((float)stats.frameDropMetrics.nsamples / stats.totalFrames) : 1.0f; float scalePlotMetrics = stats.frameDropMetrics.nsamples > 0 ? ((float)stats.frameDropMetrics.nsamples / stats.totalFrames) : 1.0f;
float fps = (stats.totalFrames - stats.networkDroppedFrames - (stats.frameDropMetrics.total / scalePlotMetrics)) / interval; float fps = (stats.totalFrames - stats.networkDroppedFrames - (stats.frameDropMetrics.total / scalePlotMetrics)) / interval;
double avgVideoMbps = [_connection getBwTracker].averageMbps; double avgVideoMbps = [_connection getBwTracker].averageMbps;
double peakVideoMbps = [_connection getBwTracker].peakMbps; double peakVideoMbps = [_connection getBwTracker].peakMbps;
return [NSString stringWithFormat:@"Video stream: %dx%d %.2f FPS (Codec: %@)\n" return [NSString stringWithFormat:@"Video stream: %dx%d %.2f FPS (Codec: %@)\n"
"Bitrate: %.1f Mbps, Peak: %.1f, Renderer: %@\n" "Bitrate: %.1f Mbps, Peak: %.1f, Renderer: %@\n"
"%@" "%@"
+29 -18
View File
@@ -58,10 +58,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
@@ -74,26 +76,20 @@ 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);
} }
[CATransaction begin];
[CATransaction setDisableActions:YES];
_displayLayer.position = CGPointMake(CGRectGetMidX(_view.bounds), CGRectGetMidY(_view.bounds)); _displayLayer.position = CGPointMake(CGRectGetMidX(_view.bounds), CGRectGetMidY(_view.bounds));
_displayLayer.bounds = CGRectMake(0, 0, videoSize.width, videoSize.height); _displayLayer.bounds = CGRectMake(0, 0, videoSize.width, videoSize.height);
_displayLayer.videoGravity = AVLayerVideoGravityResize; [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) { if (formatDesc != nil) {
// Switch out the old display layer with the new one CFRelease(formatDesc);
[_view.layer replaceSublayer:oldLayer with:_displayLayer]; formatDesc = nil;
}
else {
[_view.layer addSublayer:_displayLayer];
}
if (_formatDesc != nil) {
CFRelease(_formatDesc);
_formatDesc = nil;
}
if (_formatDescImageBuffer != nil) { if (_formatDescImageBuffer != nil) {
CFRelease(_formatDescImageBuffer); CFRelease(_formatDescImageBuffer);
@@ -729,7 +725,22 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
free(data); free(data);
return DR_NEED_IDR; return DR_NEED_IDR;
} }
// Check for previous decoder errors before doing anything
if (_displayLayer.status == AVQueuedSampleBufferRenderingStatusFailed) {
Log(LOG_E, @"Display layer rendering failed: %@", _displayLayer.error);
[self->_displayLayer flushAndRemoveImage];
// Recreate the display layer. We are already on the main thread,
// so this is safe to do right here.
[self reinitializeDisplayLayer];
// Request an IDR frame to initialize the new decoder
free(data);
return DR_NEED_IDR;
}
// Now we're decoding actual frame data here // Now we're decoding actual frame data here
CMBlockBufferRef frameBlockBuffer; CMBlockBufferRef frameBlockBuffer;
CMBlockBufferRef dataBlockBuffer; CMBlockBufferRef dataBlockBuffer;
@@ -75,6 +75,8 @@
StreamFrameViewController* streamFrameViewController; StreamFrameViewController* streamFrameViewController;
id navBarAppearanceStandard; id navBarAppearanceStandard;
NSTimer *_foregroundHostUpdateTimer;
#if TARGET_OS_TV #if TARGET_OS_TV
UITapGestureRecognizer* _menuRecognizer; UITapGestureRecognizer* _menuRecognizer;
#endif #endif
@@ -1497,20 +1499,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;
@@ -1528,7 +1516,6 @@ static NSMutableSet* hostList;
// if ([hostList count] == 1) [self hostClicked:[hostList anyObject] view:nil]; // auto click for single host // if ([hostList count] == 1) [self hostClicked:[hostList anyObject] view:nil]; // auto click for single host
//if([SettingsViewController isLandscapeNow] != _streamConfig.width > _streamConfig.height) //if([SettingsViewController isLandscapeNow] != _streamConfig.width > _streamConfig.height)
//[self simulateSettingsButtonPress]; //force expand setting view if orientation changed since last quit from app. //[self simulateSettingsButtonPress]; //force expand setting view if orientation changed since last quit from app.
//[self simulateSettingsButtonPress]; //force expand setting view if orientation changed since last quit from app. //[self simulateSettingsButtonPress]; //force expand setting view if orientation changed since last quit from app.
@@ -1638,10 +1625,13 @@ static NSMutableSet* hostList;
{ {
if (!_background) { if (!_background) {
// This will kick off box art caching // This will kick off box art caching
[self updateHosts];
_foregroundHostUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:5 repeats:YES block:^(NSTimer *timer) {
[self updateHosts];
}];
// Reset state first so we can rediscover hosts that were deleted before
[_discMan resetDiscoveryState];
[_discMan startDiscovery]; [_discMan startDiscovery];
// This will refresh the applist when a paired host is selected // This will refresh the applist when a paired host is selected
@@ -1743,7 +1733,12 @@ static NSMutableSet* hostList;
} }
- (void)viewWillDisappear:(BOOL)animated{
NSLog(@"willDisappear");
[super viewWillDisappear:animated];
[_foregroundHostUpdateTimer invalidate];
_foregroundHostUpdateTimer = nil;
}
- (void)viewWillAppear:(BOOL)animated - (void)viewWillAppear:(BOOL)animated
{ {
@@ -1862,13 +1857,13 @@ static NSMutableSet* hostList;
} }
- (void)updateHosts { - (void)updateHosts {
Log(LOG_I, @"Updating hosts..."); Log(LOG_I, @"Updating hosts %f", CACurrentMediaTime());
@synchronized (hostList) { @synchronized (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) [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
// if([comp.name isEqualToString: @"ASRockPC"]) { // if([comp.name isEqualToString: @"ASRockPC"]) {
@@ -1903,6 +1898,9 @@ static NSMutableSet* hostList;
// Update the title in case we now have a PC // Update the title in case we now have a PC
[self updateTitle]; [self updateTitle];
// Reset state first so we can rediscover hosts that were deleted before
[_discMan resetDiscoveryState];
} }
// This function forces immediate decoding of the UIImage, rather // This function forces immediate decoding of the UIImage, rather
@@ -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;
@@ -75,6 +76,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;
@@ -255,7 +255,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;
@@ -484,9 +484,10 @@ 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];
[self addSetting:self.frameQueueSizeStack ofId:@"frameQueueSizeStack" withInfoTag:NO withDynamicLabel:NO to:videoSection]; [self addSetting:self.frameQueueSizeStack ofId:@"frameQueueSizeStack" withInfoTag:NO withDynamicLabel:NO to:videoSection];
[self addSetting:self.renderingBackendStack ofId:@"renderingBackendStack" withInfoTag:YES withDynamicLabel:NO to:videoSection]; [self addSetting:self.renderingBackendStack ofId:@"renderingBackendStack" withInfoTag:YES withDynamicLabel:NO to:videoSection];
[videoSection addToParentStack:_parentStack]; [videoSection addToParentStack:_parentStack];
[videoSection setExpanded:YES]; [videoSection setExpanded:YES];
@@ -1254,8 +1255,9 @@ 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];
NSInteger renderingBackend = [currentSettings.renderingBackend integerValue]; NSInteger renderingBackend = [currentSettings.renderingBackend integerValue];
[self.renderingBackendSelector setSelectedSegmentIndex:renderingBackend]; [self.renderingBackendSelector setSelectedSegmentIndex:renderingBackend];
@@ -1287,7 +1289,7 @@ BOOL isCustomResolution(CGSize res) {
[self.bitrateSlider addTarget:self action:@selector(bitrateSliderMoved) forControlEvents:UIControlEventValueChanged]; [self.bitrateSlider addTarget:self action:@selector(bitrateSliderMoved) forControlEvents:UIControlEventValueChanged];
[self updateBitrateText]; [self updateBitrateText];
[self updateResolutionDisplayLabel]; [self updateResolutionDisplayLabel];
[self.frameQueueSizeSlider setMinimumValue:1]; [self.frameQueueSizeSlider setMinimumValue:1];
[self.frameQueueSizeSlider setMaximumValue:5]; [self.frameQueueSizeSlider setMaximumValue:5];
[self.frameQueueSizeSlider setValue:_frameQueueSize]; [self.frameQueueSizeSlider setValue:_frameQueueSize];
@@ -1988,16 +1990,16 @@ BOOL isCustomResolution(CGSize res) {
NSInteger onscreenControls = [self.onScreenWidgetSelector selectedSegmentIndex]; NSInteger onscreenControls = [self.onScreenWidgetSelector selectedSegmentIndex];
NSInteger keyboardToggleFingers = self.softKeyboardGestureSelector.selectedSegmentIndex == 3 ? 20 : self.softKeyboardGestureSelector.selectedSegmentIndex+3; NSInteger keyboardToggleFingers = self.softKeyboardGestureSelector.selectedSegmentIndex == 3 ? 20 : self.softKeyboardGestureSelector.selectedSegmentIndex+3;
NSInteger oscLayoutToolFingers = (uint16_t)self->oswLayoutFingers; NSInteger oscLayoutToolFingers = (uint16_t)self->oswLayoutFingers;
CGFloat slideToSettingsDistance = self.slideToMenuDistanceSlider.value; CGFloat slideToSettingsDistance = self.slideToMenuDistanceSlider.value;
uint32_t slideToSettingsScreenEdge = [self getScreenEdgeFromSelector]; uint32_t slideToSettingsScreenEdge = [self getScreenEdgeFromSelector];
CGFloat pointerVelocityModeDivider = (CGFloat)(uint8_t)self.pointerVelocityModeDividerSlider.value/100; CGFloat pointerVelocityModeDivider = (CGFloat)(uint8_t)self.pointerVelocityModeDividerSlider.value/100;
CGFloat touchPointerVelocityFactor = (CGFloat)(uint16_t)[self map_velocFactorDisplay_fromSliderValue:self.touchPointerVelocityFactorSlider.value]/100; CGFloat touchPointerVelocityFactor = (CGFloat)(uint16_t)[self map_velocFactorDisplay_fromSliderValue:self.touchPointerVelocityFactorSlider.value]/100;
CGFloat mousePointerVelocityFactor = (CGFloat)(uint16_t)[self map_velocFactorDisplay_fromSliderValue:self.mousePointerVelocityFactorSlider.value]/100; CGFloat mousePointerVelocityFactor = (CGFloat)(uint16_t)[self map_velocFactorDisplay_fromSliderValue:self.mousePointerVelocityFactorSlider.value]/100;
CGFloat gyroSensitivity = (CGFloat)(uint16_t)self.gyroSensitivitySlider.value/100; CGFloat gyroSensitivity = (CGFloat)(uint16_t)self.gyroSensitivitySlider.value/100;
uint16_t touchMoveEventInterval = (uint16_t)self.touchMoveEventIntervalSlider.value; uint16_t touchMoveEventInterval = (uint16_t)self.touchMoveEventIntervalSlider.value;
BOOL reverseMouseWheelDirection = [self.reverseMouseWheelDirectionSelector selectedSegmentIndex] == 1; BOOL reverseMouseWheelDirection = [self.reverseMouseWheelDirectionSelector selectedSegmentIndex] == 1;
NSInteger asyncNativeTouchPriority = 1; NSInteger asyncNativeTouchPriority = 1;
//BOOL liftStreamViewForKeyboard = [self.liftStreamViewForKeyboardSelector selectedSegmentIndex] == 1; //BOOL liftStreamViewForKeyboard = [self.liftStreamViewForKeyboardSelector selectedSegmentIndex] == 1;
@@ -2010,6 +2012,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;
@@ -2047,6 +2050,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
@@ -17,18 +17,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;
@property (nonatomic, strong) MetalViewController *metalViewController; @property (nonatomic, strong) MetalViewController *metalViewController;
@@ -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 "PaddedLabel.h" #import "PaddedLabel.h"
@@ -75,6 +77,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;
@@ -86,12 +90,67 @@
UITapGestureRecognizer *_menuDoubleTapGestureRecognizer; UITapGestureRecognizer *_menuDoubleTapGestureRecognizer;
UITapGestureRecognizer *_playPauseTapGestureRecognizer; UITapGestureRecognizer *_playPauseTapGestureRecognizer;
#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 {
NSString *key = @"hasLaunchedBefore"; NSString *key = @"hasLaunchedBefore";
BOOL launchedBefore = [[NSUserDefaults standardUserDefaults] boolForKey:key]; BOOL launchedBefore = [[NSUserDefaults standardUserDefaults] boolForKey:key];
if (!launchedBefore) { if (!launchedBefore) {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:key]; [[NSUserDefaults standardUserDefaults] setBool:YES forKey:key];
[[NSUserDefaults standardUserDefaults] synchronize]; // iOS 12+ 可省略 [[NSUserDefaults standardUserDefaults] synchronize]; // iOS 12+ 可省略
@@ -105,10 +164,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;
@@ -132,15 +252,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];
@@ -200,6 +318,7 @@
_settings = [[[DataManager alloc] init] getSettings]; //StreamFrameViewController retrieve the settings here. _settings = [[[DataManager alloc] init] getSettings]; //StreamFrameViewController retrieve the settings here.
[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
@@ -233,22 +352,26 @@
[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 {
/*
_settings.externalDisplayMode.intValue:
0 - stage manager
1 - airplay
2 - disabled
*/
dispatch_async(dispatch_get_main_queue(), ^{
[self->_streamView insertSubview:self->_streamVideoRenderView atIndex:0];
});
}
} else {
// Fallback on earlier versions
} }
else {
/*
_settings.externalDisplayMode.intValue:
0 - stage manager
1 - airplay
2 - disabled
*/
dispatch_async(dispatch_get_main_queue(), ^{
if(self->_settings.externalDisplayMode.intValue != 2) [self->_streamView insertSubview:self->_streamVideoRenderView atIndex:0];
});
}
self->_streamView.originalFrame = self->_streamView.frame; self->_streamView.originalFrame = self->_streamView.frame;
// check to see if external screen is connected/disconnected // check to see if external screen is connected/disconnected
@@ -279,17 +402,17 @@
selector:@selector(expandSettingsView) // //force expand settings view to update resolution table, and all setting includes current fullscreen resolution will be updated. selector:@selector(expandSettingsView) // //force expand settings view to update resolution table, and all setting includes current fullscreen resolution will be updated.
name:@"SettingsOverlayButtonPressedNotification" name:@"SettingsOverlayButtonPressedNotification"
object:nil]; object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self [[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillShow:) selector:@selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification name:UIKeyboardWillShowNotification
object:nil]; object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self [[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillHide) selector:@selector(keyboardWillHide)
name:UIKeyboardWillHideNotification name:UIKeyboardWillHideNotification
object:nil]; object:nil];
#endif #endif
} }
@@ -309,7 +432,7 @@
return; // temp disable return; // temp disable
// 初始化倒计时秒数 // 初始化倒计时秒数
__block NSInteger remainingSeconds = 16; __block NSInteger remainingSeconds = 16;
NSString* settingsEdgeSide = _settings.slideToSettingsScreenEdge.intValue == UIRectEdgeLeft ? [LocalizationHelper localizedStringForKey:@"left"] : [LocalizationHelper localizedStringForKey:@"right"]; NSString* settingsEdgeSide = _settings.slideToSettingsScreenEdge.intValue == UIRectEdgeLeft ? [LocalizationHelper localizedStringForKey:@"left"] : [LocalizationHelper localizedStringForKey:@"right"];
NSString* cmdToolEdgeSide = _settings.slideToSettingsScreenEdge.intValue == UIRectEdgeLeft ? [LocalizationHelper localizedStringForKey:@"right"] : [LocalizationHelper localizedStringForKey:@"left"]; NSString* cmdToolEdgeSide = _settings.slideToSettingsScreenEdge.intValue == UIRectEdgeLeft ? [LocalizationHelper localizedStringForKey:@"right"] : [LocalizationHelper localizedStringForKey:@"left"];
uint8_t slideDist = (uint8_t)(_settings.slideToSettingsDistance.floatValue * 100); uint8_t slideDist = (uint8_t)(_settings.slideToSettingsDistance.floatValue * 100);
@@ -317,7 +440,7 @@
UIAlertController *alert = [UIAlertController alertControllerWithTitle:[LocalizationHelper localizedStringForKey:@"First Launch Tips"] UIAlertController *alert = [UIAlertController alertControllerWithTitle:[LocalizationHelper localizedStringForKey:@"First Launch Tips"]
message:[LocalizationHelper localizedStringForKey:@"\n1. From %@ edge, slide %d%% screen width to open settings menu\n\n2. From %@ edge, slide %d%% screen width to open command tool\n\n3. Back to Streaming from settings menu: slide from left edge\n\n4. Slide from upper 40%% of screen edge to avoid sending touch events to PC side", settingsEdgeSide, slideDist, cmdToolEdgeSide, slideDist] message:[LocalizationHelper localizedStringForKey:@"\n1. From %@ edge, slide %d%% screen width to open settings menu\n\n2. From %@ edge, slide %d%% screen width to open command tool\n\n3. Back to Streaming from settings menu: slide from left edge\n\n4. Slide from upper 40%% of screen edge to avoid sending touch events to PC side", settingsEdgeSide, slideDist, cmdToolEdgeSide, slideDist]
preferredStyle:UIAlertControllerStyleAlert]; preferredStyle:UIAlertControllerStyleAlert];
// 添加确认按钮(初始禁用) // 添加确认按钮(初始禁用)
UIAlertAction *confirmAction = [UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Got it! (15)"] UIAlertAction *confirmAction = [UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Got it! (15)"]
style:UIAlertActionStyleDefault style:UIAlertActionStyleDefault
@@ -325,17 +448,17 @@
}]; }];
confirmAction.enabled = NO; confirmAction.enabled = NO;
[alert addAction:confirmAction]; [alert addAction:confirmAction];
// 显示弹窗 // 显示弹窗
[self presentViewController:alert animated:YES completion:nil]; [self presentViewController:alert animated:YES completion:nil];
// 使用dispatch_source_t实现精确倒计时 // 使用dispatch_source_t实现精确倒计时
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue()); dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC); dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{ dispatch_source_set_event_handler(timer, ^{
remainingSeconds--; remainingSeconds--;
if (remainingSeconds <= 0) { if (remainingSeconds <= 0) {
// 倒计时结束 // 倒计时结束
dispatch_source_cancel(timer); dispatch_source_cancel(timer);
@@ -347,9 +470,9 @@
[confirmAction setValue:[NSString stringWithFormat:[LocalizationHelper localizedStringForKey:@"Got it! (%ld)", remainingSeconds], (long)remainingSeconds] forKey:@"title"]; [confirmAction setValue:[NSString stringWithFormat:[LocalizationHelper localizedStringForKey:@"Got it! (%ld)", remainingSeconds], (long)remainingSeconds] forKey:@"title"];
} }
}); });
dispatch_resume(timer); dispatch_resume(timer);
} }
@@ -357,7 +480,7 @@
{ {
viewJustLoaded = true; viewJustLoaded = true;
[super viewDidLoad]; [super viewDidLoad];
[self.navigationController setNavigationBarHidden:YES animated:YES]; [self.navigationController setNavigationBarHidden:YES animated:YES];
[UIApplication sharedApplication].idleTimerDisabled = YES; [UIApplication sharedApplication].idleTimerDisabled = YES;
@@ -390,25 +513,24 @@
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.
if([self isFirstLaunch]) [self popFirstLaunchTip]; if([self isFirstLaunch]) [self popFirstLaunchTip];
#if TARGET_OS_TV #if TARGET_OS_TV
if (!_menuTapGestureRecognizer || !_menuDoubleTapGestureRecognizer || !_playPauseTapGestureRecognizer) { if (!_menuTapGestureRecognizer || !_menuDoubleTapGestureRecognizer || !_playPauseTapGestureRecognizer) {
_menuTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(controllerPauseButtonPressed:)]; _menuTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(controllerPauseButtonPressed:)];
@@ -545,6 +667,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.
@@ -570,7 +696,7 @@
if (parent == nil) { if (parent == nil) {
//NSLog(@"gyro cleanup, count: %ld", _controller.count); //NSLog(@"gyro cleanup, count: %ld", _controller.count);
[_controllerSupport cleanup]; [_controllerSupport cleanup];
[UIApplication sharedApplication].idleTimerDisabled = NO; [UIApplication sharedApplication].idleTimerDisabled = NO;
[_streamMan stopStream]; [_streamMan stopStream];
if (_inactivityTimer != nil) { if (_inactivityTimer != nil) {
@@ -666,7 +792,12 @@
- (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;
@@ -678,19 +809,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
} }
} }
@@ -699,7 +845,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{
@@ -709,45 +858,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;
@@ -763,14 +879,20 @@
// 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");
_inactivityTimer = [NSTimer scheduledTimerWithTimeInterval:1800 _inactivityTimer = [NSTimer scheduledTimerWithTimeInterval:18000
target:self target:self
selector:@selector(inactiveTimerExpired:) selector:@selector(inactiveTimerExpired:)
userInfo:nil userInfo:nil
@@ -793,19 +915,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{
@@ -1083,8 +1213,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) observeFloat:(int)plotId value:(CFTimeInterval)value { - (void) observeFloat:(int)plotId value:(CFTimeInterval)value {
@@ -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
} }
} }
+30
View File
@@ -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",
+30
View File
@@ -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",