feat: add back legacy pacing

This commit is contained in:
TrueZhuanjia
2025-08-25 18:05:53 +08:00
committed by Acaki
parent 34a115706e
commit 59b1dcfd13
15 changed files with 689 additions and 318 deletions
+26 -1
View File
@@ -986,6 +986,29 @@
</switch>
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="oZG-5Y-pw5" userLabel="framePacing Stack">
<rect key="frame" x="420" y="1189" width="380" height="56.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Frame Pacing Mode" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="MaN-f7-I3g" userLabel="Frame Pacing Mode">
<rect key="frame" x="0.0" y="0.0" width="380" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="1" translatesAutoresizingMaskIntoConstraints="NO" id="2Dp-Qg-chi" userLabel="Frame Pacing Mode Selector">
<rect key="frame" x="0.0" y="25.5" width="380" height="32"/>
<segments>
<segment title="Legacy"/>
<segment title="Queue"/>
</segments>
<color key="tintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="selectedSegmentTintColor" systemColor="linkColor"/>
</segmentedControl>
</subviews>
<constraints>
<constraint firstAttribute="width" constant="380" id="7VX-PK-miO"/>
</constraints>
</stackView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
@@ -1014,9 +1037,11 @@
<outlet property="externalDisplayModeSelector" destination="VWB-YB-Ptj" id="0ak-pc-evb"/>
<outlet property="externalDisplayModeStack" destination="YBu-VX-wpN" id="72y-R6-fF7"/>
<outlet property="fpsStack" destination="TuR-H8-e2H" id="zGQ-gR-P1i"/>
<outlet property="framePacingModeSelector" destination="2Dp-Qg-chi" id="h8A-YF-SZP"/>
<outlet property="framePacingStack" destination="oZG-5Y-pw5" id="vGd-hD-zTr"/>
<outlet property="frameQueueSizeLabel" destination="Ce3-FM-vIh" id="90D-Tq-OGd"/>
<outlet property="frameQueueSizeSlider" destination="oZa-8Y-icz" id="Jo1-Fx-UMw"/>
<outlet property="frameQueueSizeStack" destination="MML-c1-D5j" id="uFG-Ch-nsK"/>
<outlet property="frameQueueSizeStack" destination="MML-c1-D5j" id="82B-r7-Zbh"/>
<outlet property="framerateSelector" destination="8hF-kb-2uV" id="dfT-hb-csE"/>
<outlet property="graphOpacityStepper" destination="9VJ-Gj-nWG" id="c4x-HW-KXU"/>
<outlet property="gyroModeSelector" destination="aa5-3y-I4d" id="FsX-kH-h7w"/>
+29 -4
View File
@@ -1685,6 +1685,29 @@
<constraint firstAttribute="width" constant="380" id="A4I-UV-heD"/>
</constraints>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="U0H-kh-QLS" userLabel="framePacing Stack">
<rect key="frame" x="437" y="2081" width="380" height="56.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Frame Pacing Mode" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Cvx-kQ-L3H" userLabel="Frame Pacing Mode">
<rect key="frame" x="0.0" y="0.0" width="380" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="1" translatesAutoresizingMaskIntoConstraints="NO" id="CC6-pu-Uoh" userLabel="Frame Pacing Mode Selector">
<rect key="frame" x="0.0" y="25.5" width="380" height="32"/>
<segments>
<segment title="Legacy"/>
<segment title="Queue"/>
</segments>
<color key="tintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="selectedSegmentTintColor" systemColor="linkColor"/>
</segmentedControl>
</subviews>
<constraints>
<constraint firstAttribute="width" constant="380" id="AUi-t1-5ej"/>
</constraints>
</stackView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
@@ -1713,6 +1736,8 @@
<outlet property="externalDisplayModeSelector" destination="Tue-Qh-8zP" id="YCO-TO-Dda"/>
<outlet property="externalDisplayModeStack" destination="1Q7-WA-wrj" id="KLk-cJ-fa3"/>
<outlet property="fpsStack" destination="aMv-7Q-yYg" id="JxU-wq-2Za"/>
<outlet property="framePacingModeSelector" destination="CC6-pu-Uoh" id="HTB-6I-QgB"/>
<outlet property="framePacingStack" destination="U0H-kh-QLS" id="M8w-wy-HI8"/>
<outlet property="frameQueueSizeLabel" destination="r8X-jt-JfH" id="Tr9-eb-bIx"/>
<outlet property="frameQueueSizeSlider" destination="thv-JE-onX" id="gKD-6c-5tw"/>
<outlet property="frameQueueSizeStack" destination="IHx-FO-ctr" id="gob-Ea-Avh"/>
@@ -1798,19 +1823,19 @@
<image name="trash.fill" catalog="system" width="117" height="128"/>
<image name="tray.full.fill" catalog="system" width="128" height="88"/>
<systemColor name="linkColor">
<color red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.0" green="0.47843137254901963" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="secondarySystemGroupedBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
<systemColor name="systemBrownColor">
<color red="0.63529411759999999" green="0.51764705879999995" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.63529411764705879" green="0.51764705882352946" blue="0.36862745098039218" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemGray2Color">
<color red="0.68235294120000001" green="0.68235294120000001" blue="0.69803921570000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.68235294117647061" green="0.68235294117647061" blue="0.69803921568627447" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemTealColor">
<color red="0.18823529410000001" green="0.69019607839999997" blue="0.78039215689999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.18823529411764706" green="0.69019607843137254" blue="0.7803921568627451" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
</resources>
</document>
+5
View File
@@ -42,6 +42,10 @@ typedef NS_ENUM(NSInteger, GyroMode) {
AlwaysController
};
typedef NS_ENUM(NSInteger, FramePacingMode) {
FramePacingModeLegacy,
FramePacingModeQueue
};
typedef NS_ENUM(NSInteger, SettingsMenuMode) {
AllSettings,
@@ -91,6 +95,7 @@ typedef NS_ENUM(NSInteger, SettingsMenuMode) {
enableGraphs:(BOOL)enableGraphs
graphOpacity:(NSInteger)graphOpacity
renderingBackend:(NSInteger)renderingBackend
framePacingMode:(NSInteger)framePacingMode
backgroundSessionTimer:(NSInteger)backgroundSessionTimer;
- (NSArray*) getHosts;
+2
View File
@@ -97,6 +97,7 @@
enableGraphs:(BOOL)enableGraphs
graphOpacity:(NSInteger)graphOpacity
renderingBackend:(NSInteger)renderingBackend
framePacingMode:(NSInteger)framePacingMode
backgroundSessionTimer:(NSInteger)backgroundSessionTimer{
[_managedObjectContext performBlockAndWait:^{
@@ -145,6 +146,7 @@
settingsToSave.enableGraphs = enableGraphs;
settingsToSave.graphOpacity = [NSNumber numberWithInteger:graphOpacity];
settingsToSave.renderingBackend = [NSNumber numberWithInteger:renderingBackend];
settingsToSave.framePacingMode = [NSNumber numberWithInteger:framePacingMode];
[self saveData];
}];
}
+1
View File
@@ -68,6 +68,7 @@
@property (nonatomic, retain) NSNumber * frameQueueSize;
@property (nonatomic, retain) NSNumber * graphOpacity;
@property (nonatomic, retain) NSNumber * renderingBackend;
@property (nonatomic, retain) NSNumber * framePacingMode;
- (id) initFromSettings:(Settings*)settings;
+2
View File
@@ -61,6 +61,7 @@
// iOS < 17 must use Balanced mode (AVSB renderer)
self.renderingBackend = [NSNumber numberWithInteger:RENDER_AVSB];
}
self.framePacingMode = [NSNumber numberWithInteger:[[NSUserDefaults standardUserDefaults] integerForKey:@"framePacingMode"]];
NSInteger _screenSize = [[NSUserDefaults standardUserDefaults] integerForKey:@"streamResolution"];
switch (_screenSize) {
@@ -129,6 +130,7 @@
self.enableGraphs = settings.enableGraphs;
self.graphOpacity = settings.graphOpacity;
self.renderingBackend = settings.renderingBackend;
self.framePacingMode = settings.framePacingMode;
self.backgroundSessionTimer = settings.backroundSessionTimer;
#endif
self.uniqueId = settings.uniqueId;
@@ -34,6 +34,7 @@
<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="frameQueueSize" optional="YES" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="NO" syncable="YES"/>
<attribute name="framePacingMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" usesScalarValueType="NO" syncable="YES"/>
<attribute name="gyroMode" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="gyroSensitivity" optional="YES" attributeType="Float" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
+253 -253
View File
@@ -306,29 +306,6 @@
}
}
},
"[ Toggle stats overlay & graphs ]" : {
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "[ 切换统计覆盖层和图表 ]"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "[ 切換統計覆蓋層和圖表 ]"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "[ 切換統計覆蓋層和圖表 ]"
}
}
}
},
"[ On-screen widget tool ]" : {
"extractionState" : "manual",
"localizations" : {
@@ -375,6 +352,29 @@
}
}
},
"[ Toggle stats overlay & graphs ]" : {
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "[ 切换统计覆盖层和图表 ]"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "[ 切換統計覆蓋層和圖表 ]"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "[ 切換統計覆蓋層和圖表 ]"
}
}
}
},
"%@ is currently running" : {
"extractionState" : "manual",
"localizations" : {
@@ -789,6 +789,29 @@
}
}
},
"Balanced" : {
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "平衡"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "平衡"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "平衡"
}
}
}
},
"Ball: assumed touch point. Cross: indicator location" : {
"extractionState" : "manual",
"localizations" : {
@@ -2010,6 +2033,52 @@
}
}
},
"Frames to Buffer" : {
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "缓冲帧数"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "緩衝影格數"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "緩衝影格數"
}
}
}
},
"Frames to buffer: %ld" : {
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "缓冲帧数:%ld"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "緩衝影格數:%ld"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "緩衝影格數:%ld"
}
}
}
},
"From the player community, to the player community." : {
"extractionState" : "manual",
"localizations" : {
@@ -2419,6 +2488,29 @@
}
}
},
"Later" : {
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "稍后"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "稍後"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "稍後"
}
}
}
},
"Launch" : {
"extractionState" : "manual",
"localizations" : {
@@ -3393,6 +3485,52 @@
}
}
},
"Performance" : {
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "性能"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "效能"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "效能"
}
}
}
},
"Performance Graphs - Opacity: %ld%%" : {
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "性能图表 - 不透明度:%ld%%"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "效能圖表 - 不透明度:%ld%%"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "效能圖表 - 不透明度:%ld%%"
}
}
}
},
"Peripherals" : {
"extractionState" : "manual",
"localizations" : {
@@ -3745,6 +3883,29 @@
}
}
},
"Quit Now" : {
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "立即退出"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "立即退出"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "立即退出"
}
}
}
},
"Quit Running App" : {
"extractionState" : "manual",
"localizations" : {
@@ -3837,98 +3998,6 @@
}
}
},
"Quit Now" : {
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "立即退出"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "立即退出"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "立即退出"
}
}
}
},
"Rendering mode change requires app restart" : {
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "更改渲染模式需要重启应用以确保干净启动。您是否要立即退出应用?"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "更改渲染模式需要重新啟動應用程式以確保乾淨啟動。您是否要立即退出應用程式?"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "更改渲染模式需要重新啟動應用程式以確保乾淨啟動。您是否要立即退出應用程式?"
}
}
}
},
"Restart Required" : {
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "需要重启"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "需要重新啟動"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "需要重新啟動"
}
}
}
},
"Later" : {
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "稍后"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "稍後"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "稍後"
}
}
}
},
"Read Widget Instruction" : {
"extractionState" : "manual",
"localizations" : {
@@ -4044,6 +4113,52 @@
}
}
},
"Rendering Mode (Experimental)" : {
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "渲染模式(实验性)"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "渲染模式(實驗性)"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "渲染模式(實驗性)"
}
}
}
},
"Rendering mode change requires app restart" : {
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "更改渲染模式需要重启应用以确保干净启动。您是否要立即退出应用?"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "更改渲染模式需要重新啟動應用程式以確保乾淨啟動。您是否要立即退出應用程式?"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "更改渲染模式需要重新啟動應用程式以確保乾淨啟動。您是否要立即退出應用程式?"
}
}
}
},
"renderingBackendStackTip" : {
"extractionState" : "manual",
"localizations" : {
@@ -4073,6 +4188,29 @@
}
}
},
"Restart Required" : {
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "需要重启"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "需要重新啟動"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "需要重新啟動"
}
}
}
},
"Resume App" : {
"extractionState" : "manual",
"localizations" : {
@@ -5384,144 +5522,6 @@
}
}
},
"Rendering Mode (Experimental)" : {
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "渲染模式(实验性)"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "渲染模式(實驗性)"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "渲染模式(實驗性)"
}
}
}
},
"Frames to Buffer" : {
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "缓冲帧数"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "緩衝影格數"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "緩衝影格數"
}
}
}
},
"Frames to buffer: %ld" : {
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "缓冲帧数:%ld"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "緩衝影格數:%ld"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "緩衝影格數:%ld"
}
}
}
},
"Performance Graphs - Opacity: %ld%%" : {
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "性能图表 - 不透明度:%ld%%"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "效能圖表 - 不透明度:%ld%%"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "效能圖表 - 不透明度:%ld%%"
}
}
}
},
"Balanced" : {
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "平衡"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "平衡"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "平衡"
}
}
}
},
"Performance" : {
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "性能"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "效能"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "效能"
}
}
}
},
"闽ICP备17012590号-3A\n主办单位:福州创图信息技术有限公司" : {
}
+141 -42
View File
@@ -55,6 +55,13 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size,
NSInteger _maxRefreshRate;
RenderingBackend _renderingBackend;
// Frame pacing mode
BOOL _useLegacyPacing;
// Legacy pacing frame counter and queue management
NSInteger _simplePacingFrameCount;
NSInteger _enqueuedSampleBuffers;
}
- (void)reinitializeDisplayLayer
@@ -129,9 +136,15 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size,
DataManager* dataMan = [[DataManager alloc] init];
_frameQueue = [FrameQueue sharedInstance];
[_frameQueue start];
[_frameQueue setHighWaterMark:(int)[[dataMan getSettings].frameQueueSize integerValue]];
// Check if we should use legacy pacing mode
_useLegacyPacing = [[dataMan getSettings].framePacingMode integerValue] == FramePacingModeLegacy;
if (!_useLegacyPacing) {
// Queue pacing mode uses FrameQueue
_frameQueue = [FrameQueue sharedInstance];
[_frameQueue start];
[_frameQueue setHighWaterMark:(int)[[dataMan getSettings].frameQueueSize integerValue]];
}
[self reinitializeDisplayLayer];
@@ -155,11 +168,17 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size,
DataManager* dataMan = [[DataManager alloc] init];
if ([[dataMan getSettings].renderingBackend integerValue] == RENDER_AVSB) {
// PACING_MODE_VSYNC:
// Deliver 1 frame at each vsync interval. Ignores server pts timestamps.
// Drop frames intelligently to maintain chosen queue size.
_renderingBackend = RENDER_AVSB;
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(renderModeAVSB:)];
if (_useLegacyPacing) {
// Legacy pacing mode
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(renderModeLegacy:)];
} else {
// PACING_MODE_VSYNC:
// Deliver 1 frame at each vsync interval. Ignores server pts timestamps.
// Drop frames intelligently to maintain chosen queue size.
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(renderModeAVSB:)];
}
if (@available(iOS 15.0, tvOS 15.0, *)) {
_displayLink.preferredFrameRateRange = CAFrameRateRangeMake(self->_frameRate, self->_frameRate, self->_frameRate);
@@ -176,7 +195,6 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size,
- (void)setupDecompressionSessionWithAttributes:(NSDictionary *)destinationPixelBufferAttributes {
// This method is called from within synchronized block, so no additional sync needed here
if (_decompressionSession != NULL) {
VTDecompressionSessionInvalidate(_decompressionSession);
CFRelease(_decompressionSession);
@@ -242,7 +260,33 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size,
int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
#pragma mark DisplayLink - Frame Pacing - Vsync with FrameQueue
#pragma mark DisplayLink - Legacy Frame Pacing
// Legacy frame pacing method from upstream/Integration - direct polling without FrameQueue
- (void)renderModeLegacy:(CADisplayLink *)link {
VIDEO_FRAME_HANDLE handle;
PDECODE_UNIT du;
while (LiPollNextVideoFrame(&handle, &du)) {
LiCompleteVideoFrame(handle, DrSubmitDecodeUnit(du));
// Calculate the actual display refresh rate
double displayRefreshRate = 1 / (link.targetTimestamp - link.timestamp);
// Only pace frames if the display refresh rate is >= 90% of our stream frame rate.
// Battery saver, accessibility settings, or device thermals can cause the actual
// refresh rate of the display to drop below the physical maximum.
if (displayRefreshRate >= _frameRate * 0.9f) {
// Keep one pending frame to smooth out gaps due to
// network jitter at the cost of 1 frame of latency
if (LiGetPendingVideoFrames() == 1) {
break;
}
}
}
}
#pragma mark DisplayLink - Frame Pacing - Vsync with FrameQueue (Queue Pacing)
// This frame pacing method was inspired by the behavior of moonlight-qt's Pacer class, although it has evolved
// a few additional features. Incoming frames from Sunshine are asynchronously processed into a queue by the VideoRecv thread.
@@ -294,9 +338,11 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
// we missed a callback
// Log(LOG_W, @"*** slow frametime %.3f ms", frametime * 1000.0);
}
if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground) {
[[ImGuiPlots sharedInstance] observeFloat:PLOT_FRAMETIME value:frametime * 1000.0];
}
dispatch_async(dispatch_get_main_queue(), ^{
if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground) {
[[ImGuiPlots sharedInstance] observeFloat:PLOT_FRAMETIME value:frametime * 1000.0];
}
});
}
lastTargetLocal = targetLocal;
@@ -364,7 +410,9 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
}
- (void)cleanup
{
[_frameQueue stop];
if (_frameQueue) {
[_frameQueue stop];
}
if (_renderingBackend == RENDER_AVSB) {
[_displayLink invalidate];
@@ -925,34 +973,82 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
// Dispatch onto our higher priority queue
dispatch_async(self->_vtq, ^{
Frame *frame = nil;
if (self->_renderingBackend == RENDER_AVSB) {
frame = [[Frame alloc] initWithSampleBuffer:sampleBufferOut frameNumber:frameNumber frameType:frameType];
if (self->_useLegacyPacing && self->_renderingBackend == RENDER_AVSB) {
// Legacy pacing: Process all frames normally, but with immediate display timing
// Set presentation time to current time for immediate display
CMTime targetTime = CMTimeMakeWithSeconds(CACurrentMediaTime(), NSEC_PER_SEC);
CMSampleBufferSetOutputPresentationTimeStamp(sampleBufferOut, targetTime);
// Dispatch display layer operations to main thread to avoid CATransaction warnings
dispatch_async(dispatch_get_main_queue(), ^{
if (frameType == FRAME_TYPE_IDR) {
// Ensure the layer is visible now
self->_displayLayer.hidden = NO;
// Tell our parent VC to hide the progress indicator
[self->_callbacks videoContentShown];
}
if ([self->_displayLayer controlTimebase] == NULL) {
// On first frame, set timebase
CMTimebaseRef timebase = NULL;
CMTimebaseCreateWithSourceClock(CFAllocatorGetDefault(), CMClockGetHostTimeClock(), &timebase);
CMTime pts = CMSampleBufferGetOutputPresentationTimeStamp(sampleBufferOut);
CMTimebaseSetTime(timebase, pts);
CMTimebaseSetRate(timebase, 1.0);
[self->_displayLayer setControlTimebase:timebase];
if (timebase) {
CFRelease(timebase);
}
Log(LOG_I, @"Setting timebase for legacy pacing to %d / %d", pts.value, pts.timescale);
}
[self->_displayLayer enqueueSampleBuffer:sampleBufferOut];
CFRelease(sampleBufferOut);
});
} else {
frame = [[Frame alloc] initWithPixelBufffer:pixelBuffer frameNumber:frameNumber frameType:frameType pts:presentationTimestamp];
[frame setFormatDesc:self->_formatDesc];
}
int framesDropped = [self->_frameQueue enqueue:frame withSlackSize:3];
if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground) {
static PlotMetrics frameQueueMetrics = {};
[[ImGuiPlots sharedInstance] observeFloatReturnMetrics:PLOT_QUEUED_FRAMES value:[self->_frameQueue count] plotMetrics:&frameQueueMetrics];
[self safeCopyMetricsTo:&self->_frameQueueMetrics from:&frameQueueMetrics];
[[ImGuiPlots sharedInstance] observeFloat:PLOT_DROPPED value:framesDropped];
// It's important we capture host metrics on the incoming thread, as this frame object
// may have been dropped by the above enqueue
static CFTimeInterval lastHostFrame = 0.0f;
if (lastHostFrame != 0) {
[[ImGuiPlots sharedInstance] observeFloat:PLOT_HOST_FRAMETIME value:(frame.pts - lastHostFrame) * 1000.0];
// Queue pacing: use FrameQueue
Frame *frame = nil;
if (self->_renderingBackend == RENDER_AVSB) {
frame = [[Frame alloc] initWithSampleBuffer:sampleBufferOut frameNumber:frameNumber frameType:frameType];
} else {
frame = [[Frame alloc] initWithPixelBufffer:pixelBuffer frameNumber:frameNumber frameType:frameType pts:presentationTimestamp];
[frame setFormatDesc:self->_formatDesc];
}
lastHostFrame = frame.pts;
int framesDropped = [self->_frameQueue enqueue:frame withSlackSize:3];
// Decode time is not graphed because it is marked as hidden, but we can use the same mechanism for the value used by stats
static PlotMetrics decodeMetrics = {};
[[ImGuiPlots sharedInstance] observeFloatReturnMetrics:PLOT_DECODE value:(CACurrentMediaTime() - decodeStartTime) * 1000.0 plotMetrics:&decodeMetrics];
[self safeCopyMetricsTo:&self->_decodeMetrics from:&decodeMetrics];
// Capture metrics on main thread to avoid UIApplication thread warning
int frameQueueCount = [self->_frameQueue count];
CFTimeInterval capturedDecodeTime = CACurrentMediaTime() - decodeStartTime;
CFTimeInterval hostFramePts = frame.pts;
dispatch_async(dispatch_get_main_queue(), ^{
if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground) {
static PlotMetrics frameQueueMetrics = {};
[[ImGuiPlots sharedInstance] observeFloatReturnMetrics:PLOT_QUEUED_FRAMES value:frameQueueCount plotMetrics:&frameQueueMetrics];
[self safeCopyMetricsTo:&self->_frameQueueMetrics from:&frameQueueMetrics];
[[ImGuiPlots sharedInstance] observeFloat:PLOT_DROPPED value:framesDropped];
// It's important we capture host metrics on the incoming thread, as this frame object
// may have been dropped by the above enqueue
static CFTimeInterval lastHostFrame = 0.0f;
if (lastHostFrame != 0) {
[[ImGuiPlots sharedInstance] observeFloat:PLOT_HOST_FRAMETIME value:(hostFramePts - lastHostFrame) * 1000.0];
}
lastHostFrame = hostFramePts;
// Decode time is not graphed because it is marked as hidden, but we can use the same mechanism for the value used by stats
static PlotMetrics decodeMetrics = {};
[[ImGuiPlots sharedInstance] observeFloatReturnMetrics:PLOT_DECODE
value:capturedDecodeTime * 1000.0
plotMetrics:&decodeMetrics];
[self safeCopyMetricsTo:&self->_decodeMetrics from:&decodeMetrics];
}
});
}
});
});
@@ -1051,13 +1147,16 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
if (_renderingBackend == RENDER_METAL) {
stats->renderingBackendString = [NSString stringWithFormat:@"Metal, colorspace: %@", [MetalVideoRenderer currentColorSpace]];
} else {
stats->renderingBackendString = @"AVSampleBuffer";
NSString *pacingMode = _useLegacyPacing ? @"Legacy" : @"Queue";
stats->renderingBackendString = [NSString stringWithFormat:@"AVSampleBuffer (%@ pacing)", pacingMode];
}
dispatch_sync(_sq, ^{
memcpy(&stats->decodeMetrics, &_decodeMetrics, sizeof(PlotMetrics));
memcpy(&stats->frameQueueMetrics, &_frameQueueMetrics, sizeof(PlotMetrics));
[_frameQueue.frameDropMetrics copyMetrics:&stats->frameDropMetrics];
if (_frameQueue) {
memcpy(&stats->frameQueueMetrics, &_frameQueueMetrics, sizeof(PlotMetrics));
[_frameQueue.frameDropMetrics copyMetrics:&stats->frameDropMetrics];
}
});
}
@@ -1069,7 +1168,7 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
static int lastTargetRate = 0;
int targetRate = (int)_maxRefreshRate;
if (_maxRefreshRate <= 60 || _maxRefreshRate == 90) {
if (_maxRefreshRate <= 60 || _maxRefreshRate == 90 || !_frameQueue) {
return;
}
-5
View File
@@ -1,8 +1,3 @@
typedef enum {
PACING_MODE_VSYNC,
PACING_MODE_PTS
} FramePacingMode;
typedef enum {
PLOT_FRAMETIME = 0,
PLOT_HOST_FRAMETIME,
@@ -28,6 +28,7 @@
#import "IdManager.h"
#import "ConnectionHelper.h"
#import "LocalizationHelper.h"
#import "Plot.h"
#import "CustomEdgeSlideGestureRecognizer.h"
#import "DataManager.h"
#import "ThemeManager.h"
@@ -1180,6 +1181,9 @@ static NSMutableSet* hostList;
[settingsViewController setHidden:_settingsExpandedInStreamView forStack:settingsViewController.audioConfigStack];
[settingsViewController setHidden:_settingsExpandedInStreamView forStack:settingsViewController.pipStack];
[settingsViewController.renderingBackendSelector setEnabled:!_settingsExpandedInStreamView];
// Enable frame pacing mode selector only if not in stream view AND not in performance mode
BOOL shouldEnableFramePacing = !_settingsExpandedInStreamView && (settingsViewController.renderingBackendSelector.selectedSegmentIndex != RENDER_METAL);
[settingsViewController.framePacingModeSelector setEnabled:shouldEnableFramePacing];
}
- (void)revealController:(SWRevealViewController *)revealController didMoveToPosition:(FrontViewPosition)position {
@@ -57,6 +57,7 @@
@property (strong, nonatomic) IBOutlet UIStackView *externalDisplayModeStack;
@property (strong, nonatomic) IBOutlet UIStackView *localMousePointerModeStack;
@property (strong, nonatomic) IBOutlet UIStackView *renderingBackendStack;
@property (strong, nonatomic) IBOutlet UIStackView *framePacingStack;
@property (strong, nonatomic) IBOutlet UIStackView *frameQueueSizeStack;
@property (strong, nonatomic) IBOutlet UIStackView *performanceGraphStack;
@@ -115,6 +116,7 @@
@property (strong, nonatomic) IBOutlet UISwitch *enableGraphsSwitch;
@property (strong, nonatomic) IBOutlet UIStepper *graphOpacityStepper;
@property (strong, nonatomic) IBOutlet UISegmentedControl *renderingBackendSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *framePacingModeSelector;
@property (strong, nonatomic) IBOutlet UIStackView *backgroundSessionTimerStack;
@property (strong, nonatomic) IBOutlet UISlider *backgroundSessionTimerSlider;
@@ -560,6 +560,7 @@ BOOL isCustomResolution(int resolutionSelected) {
[self addSetting:self.pipStack ofId:@"pipStack" 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.framePacingStack ofId:@"framePacingStack" withInfoTag:YES withDynamicLabel:NO to:videoSection];
// Only show Metal renderer option on iOS 17+ where CAMetalDisplayLink is available
if (@available(iOS 17.0, *)) {
@@ -1431,7 +1432,13 @@ BOOL isCustomResolution(int resolutionSelected) {
NSInteger renderingBackend = [currentSettings.renderingBackend integerValue];
[self.renderingBackendSelector setSelectedSegmentIndex:renderingBackend];
[self.renderingBackendSelector addTarget:self action:@selector(renderingBackendChanged:) forControlEvents:UIControlEventValueChanged];
[self renderingBackendChanged:self.renderingBackendSelector]; // Update PiP state based on current selection
NSInteger framePacingMode = [currentSettings.framePacingMode integerValue];
[self.framePacingModeSelector setSelectedSegmentIndex:framePacingMode];
[self.framePacingModeSelector addTarget:self action:@selector(framePacingModeChanged:) forControlEvents:UIControlEventValueChanged];
[self framePacingModeChanged:self.framePacingModeSelector];
[self renderingBackendChanged:self.renderingBackendSelector]; // Update PiP and frame pacing state based on current selection
[self.citrixX1MouseSwitch setOn:currentSettings.btMouseSupport];
[self.optimizeGamesSwitch setOn: currentSettings.optimizeGames];
@@ -1665,6 +1672,24 @@ BOOL isCustomResolution(int resolutionSelected) {
}
- (void)renderingBackendChanged:(UISegmentedControl *)sender {
// Disable PiP toggle when Metal renderer is selected
if (sender.selectedSegmentIndex == RENDER_METAL) {
// Performance mode (Metal renderer) selected - disable PiP
[self.pipSwitch setOn:NO animated:YES];
[self.pipSwitch setEnabled:NO];
// Set pacing method to Queue and disable selector
[self.framePacingModeSelector setSelectedSegmentIndex:FramePacingModeQueue];
[self.framePacingModeSelector setEnabled:NO];
} else {
// Balanced mode (AVSB renderer) - enable PiP toggle if iOS 15+
if (@available(iOS 15.0, *)) {
[self.pipSwitch setEnabled:YES];
} else {
[self.pipSwitch setOn:NO];
[self.pipSwitch setEnabled:NO];
}
[self.framePacingModeSelector setEnabled:YES];
}
// Get the current settings to compare with the new selection
DataManager* dataMan = [[DataManager alloc] init];
@@ -1706,19 +1731,22 @@ BOOL isCustomResolution(int resolutionSelected) {
[alertController addAction:quitAction];
[self presentViewController:alertController animated:YES completion:nil];
}
// Disable PiP toggle when Metal renderer is selected
if (sender.selectedSegmentIndex == RENDER_METAL) {
// Performance mode (Metal renderer) selected - disable PiP
[self.pipSwitch setOn:NO animated:YES];
[self.pipSwitch setEnabled:NO];
}
- (void)framePacingModeChanged:(UISegmentedControl *)sender {
if (sender.selectedSegmentIndex == FramePacingModeLegacy) {
// Legacy mode selected - disable frames to buffer and graph settings
[self.frameQueueSizeSlider setEnabled:NO];
[self.enableGraphsSwitch setOn:NO animated:YES];
[self.enableGraphsSwitch setEnabled:NO];
[self.graphOpacityStepper setEnabled:NO];
} else {
// Balanced mode (AVSB renderer) - enable PiP toggle if iOS 15+
if (@available(iOS 15.0, *)) {
[self.pipSwitch setEnabled:YES];
} else {
[self.pipSwitch setOn:NO];
[self.pipSwitch setEnabled:NO];
// Queue mode selected - enable frames to buffer and graph settings
[self.frameQueueSizeSlider setEnabled:YES];
[self.enableGraphsSwitch setEnabled:YES];
if (self.enableGraphsSwitch.isOn) {
[self.graphOpacityStepper setEnabled:YES];
}
}
}
@@ -2265,6 +2293,7 @@ BOOL isCustomResolution(int resolutionSelected) {
NSInteger audioConfig = [@[@2, @6, @8][[self.audioConfigSelector selectedSegmentIndex]] integerValue];
NSInteger renderingBackend = [self.renderingBackendSelector selectedSegmentIndex];
NSInteger framePacingMode = [self.framePacingModeSelector selectedSegmentIndex];
NSInteger onscreenControls = [self.onScreenWidgetSelector selectedSegmentIndex];
NSInteger keyboardToggleFingers = self.softKeyboardGestureSelector.selectedSegmentIndex == 3 ? 20 : self.softKeyboardGestureSelector.selectedSegmentIndex+3;
NSInteger oscLayoutToolFingers = (uint16_t)self->oswLayoutFingers;
@@ -2348,6 +2377,7 @@ BOOL isCustomResolution(int resolutionSelected) {
enableGraphs:enableGraphs
graphOpacity:_graphOpacity
renderingBackend:renderingBackend
framePacingMode:framePacingMode
backgroundSessionTimer:backgroundSessionTimer];
}
+90
View File
@@ -31,6 +31,66 @@
}
}
},
"2Dp-Qg-chi.segmentTitles[0]" : {
"comment" : "Class = \"UISegmentedControl\"; 2Dp-Qg-chi.segmentTitles[0] = \"Legacy\"; ObjectID = \"2Dp-Qg-chi\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Legacy"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "旧版"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "舊版"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "舊版"
}
}
}
},
"2Dp-Qg-chi.segmentTitles[1]" : {
"comment" : "Class = \"UISegmentedControl\"; 2Dp-Qg-chi.segmentTitles[1] = \"Queue\"; ObjectID = \"2Dp-Qg-chi\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Queue"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "佇列"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "佇列"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "佇列"
}
}
}
},
"2w1-Vw-GVy.text" : {
"comment" : "Class = \"UILabel\"; text = \"HDR\"; ObjectID = \"2w1-Vw-GVy\";",
"extractionState" : "extracted_with_value",
@@ -2311,6 +2371,36 @@
}
}
},
"MaN-f7-I3g.text" : {
"comment" : "Class = \"UILabel\"; text = \"Frame Pacing Mode\"; ObjectID = \"MaN-f7-I3g\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Frame Pacing Mode"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "帧缓冲模式"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "幀緩衝模式"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "幀緩衝模式"
}
}
}
},
"Mm0-xf-9eh.segmentTitles[0]" : {
"comment" : "Class = \"UISegmentedControl\"; Mm0-xf-9eh.segmentTitles[0] = \"Slide From Left Edge\"; ObjectID = \"Mm0-xf-9eh\";",
"extractionState" : "extracted_with_value",
+90
View File
@@ -1087,6 +1087,66 @@
}
}
},
"CC6-pu-Uoh.segmentTitles[0]" : {
"comment" : "Class = \"UISegmentedControl\"; CC6-pu-Uoh.segmentTitles[0] = \"Legacy\"; ObjectID = \"CC6-pu-Uoh\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Legacy"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "旧版"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "舊版"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "舊版"
}
}
}
},
"CC6-pu-Uoh.segmentTitles[1]" : {
"comment" : "Class = \"UISegmentedControl\"; CC6-pu-Uoh.segmentTitles[1] = \"Queue\"; ObjectID = \"CC6-pu-Uoh\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Queue"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "佇列"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "佇列"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "佇列"
}
}
}
},
"ci6-ak-aCa.text" : {
"comment" : "Class = \"UILabel\"; text = \"Custom Resolution\"; ObjectID = \"ci6-ak-aCa\";",
"extractionState" : "extracted_with_value",
@@ -1177,6 +1237,36 @@
}
}
},
"Cvx-kQ-L3H.text" : {
"comment" : "Class = \"UILabel\"; text = \"Frame Pacing Mode\"; ObjectID = \"Cvx-kQ-L3H\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Frame Pacing Mode"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "帧缓冲模式"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "幀緩衝模式"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "幀緩衝模式"
}
}
}
},
"D9X-7p-9oc.title" : {
"comment" : "Class = \"UIBarButtonItem\"; title = \"ExportAll\"; ObjectID = \"D9X-7p-9oc\";",
"extractionState" : "extracted_with_value",