Skip to content

Commit 5e6628a

Browse files
committed
Stabilize codec switching and add low latency mode
1 parent 4ef03d4 commit 5e6628a

31 files changed

Lines changed: 427 additions & 88 deletions

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,13 @@ simdeck daemon stop
8383

8484
`simdeck daemon` manages the normal per-project warm process.
8585

86+
Use software H.264's low-latency profile on slower runners where freshness is
87+
more important than full-resolution smoothness:
88+
89+
```sh
90+
simdeck daemon start --video-codec h264-software --low-latency
91+
```
92+
8693
Restart the CoreSimulator service layer when `simctl` reports a stale service
8794
version or the live display gets stuck before the first frame:
8895

cli/XCWH264Encoder.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ typedef void (^XCWH264EncoderOutputHandler)(NSData *sampleData,
1919

2020
- (void)encodePixelBuffer:(CVPixelBufferRef)pixelBuffer;
2121
- (void)requestKeyFrame;
22+
- (void)reconfigureWithCurrentEnvironment;
2223
- (NSDictionary *)statsRepresentation;
2324
- (void)invalidate;
2425

cli/XCWH264Encoder.m

Lines changed: 114 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,27 @@
88

99
static const int32_t XCWMaximumEncodedDimension = 1920;
1010
static const int32_t XCWMaximumSoftwareEncodedDimension = 1600;
11+
static const int32_t XCWMaximumLowLatencySoftwareEncodedDimension = 1472;
1112
static const int32_t XCWTargetRealTimeFrameRate = 60;
1213
static const int32_t XCWTargetSoftwareFrameRate = 60;
14+
static const int32_t XCWTargetLowLatencySoftwareFrameRate = 45;
1315
static const NSUInteger XCWMaximumInFlightFrames = 2;
1416
static const int32_t XCWMinimumAverageBitRate = 18000000;
1517
static const int32_t XCWMinimumSoftwareAverageBitRate = 3000000;
18+
static const int32_t XCWMinimumLowLatencySoftwareAverageBitRate = 2500000;
1619
static const int64_t XCWBitsPerPixelBudget = 10;
1720
static const int64_t XCWSoftwareBitsPerPixelBudget = 6;
21+
static const int64_t XCWLowLatencySoftwareBitsPerPixelBudget = 4;
1822
static const uint64_t XCWSoftwareMinimumFrameIntervalUs = 16667;
1923
static const uint64_t XCWSoftwareInitialFrameIntervalUs = 16667;
2024
static const uint64_t XCWSoftwareMaximumFrameIntervalUs = 50000;
2125
static const uint64_t XCWSoftwareFrameIntervalStepUs = 5556;
2226
static const NSUInteger XCWSoftwareHealthyFrameWindow = 4;
27+
static const uint64_t XCWLowLatencySoftwareMinimumFrameIntervalUs = 22222;
28+
static const uint64_t XCWLowLatencySoftwareInitialFrameIntervalUs = 22222;
29+
static const uint64_t XCWLowLatencySoftwareMaximumFrameIntervalUs = 100000;
30+
static const uint64_t XCWLowLatencySoftwareFrameIntervalStepUs = 11111;
31+
static const NSUInteger XCWLowLatencySoftwareHealthyFrameWindow = 8;
2332

2433
typedef NS_ENUM(NSUInteger, XCWVideoEncoderMode) {
2534
XCWVideoEncoderModeHEVCHardware,
@@ -39,6 +48,15 @@ static XCWVideoEncoderMode XCWVideoEncoderModeFromEnvironment(void) {
3948
return XCWVideoEncoderModeHEVCHardware;
4049
}
4150

51+
static BOOL XCWLowLatencyModeFromEnvironment(void) {
52+
const char *rawValue = getenv("SIMDECK_LOW_LATENCY");
53+
NSString *value = rawValue != NULL ? [[[NSString alloc] initWithUTF8String:rawValue] lowercaseString] : @"";
54+
return [value isEqualToString:@"1"] ||
55+
[value isEqualToString:@"true"] ||
56+
[value isEqualToString:@"yes"] ||
57+
[value isEqualToString:@"on"];
58+
}
59+
4260
static CMVideoCodecType XCWVideoCodecTypeForMode(XCWVideoEncoderMode mode) {
4361
switch (mode) {
4462
case XCWVideoEncoderModeH264Hardware:
@@ -242,14 +260,17 @@ static int32_t XCWRoundToEvenDimension(double value) {
242260
return rounded;
243261
}
244262

245-
static CGSize XCWScaledDimensionsForSourceSize(int32_t width, int32_t height, XCWVideoEncoderMode mode) {
263+
static CGSize XCWScaledDimensionsForSourceSize(int32_t width, int32_t height, XCWVideoEncoderMode mode, BOOL lowLatencyMode) {
246264
if (width <= 0 || height <= 0) {
247265
return CGSizeZero;
248266
}
249267

250-
int32_t maximumDimension = mode == XCWVideoEncoderModeH264Software
251-
? XCWMaximumSoftwareEncodedDimension
252-
: XCWMaximumEncodedDimension;
268+
int32_t maximumDimension = XCWMaximumEncodedDimension;
269+
if (mode == XCWVideoEncoderModeH264Software) {
270+
maximumDimension = lowLatencyMode
271+
? XCWMaximumLowLatencySoftwareEncodedDimension
272+
: XCWMaximumSoftwareEncodedDimension;
273+
}
253274
int32_t longestEdge = MAX(width, height);
254275
if (longestEdge <= maximumDimension) {
255276
return CGSizeMake(width, height);
@@ -260,13 +281,17 @@ static CGSize XCWScaledDimensionsForSourceSize(int32_t width, int32_t height, XC
260281
XCWRoundToEvenDimension(height * scale));
261282
}
262283

263-
static int32_t XCWAverageBitRateForDimensions(int32_t width, int32_t height, XCWVideoEncoderMode mode) {
264-
int64_t bitsPerPixelBudget = mode == XCWVideoEncoderModeH264Software
265-
? XCWSoftwareBitsPerPixelBudget
266-
: XCWBitsPerPixelBudget;
267-
int64_t minimumAverageBitRate = mode == XCWVideoEncoderModeH264Software
268-
? XCWMinimumSoftwareAverageBitRate
269-
: XCWMinimumAverageBitRate;
284+
static int32_t XCWAverageBitRateForDimensions(int32_t width, int32_t height, XCWVideoEncoderMode mode, BOOL lowLatencyMode) {
285+
int64_t bitsPerPixelBudget = XCWBitsPerPixelBudget;
286+
int64_t minimumAverageBitRate = XCWMinimumAverageBitRate;
287+
if (mode == XCWVideoEncoderModeH264Software) {
288+
bitsPerPixelBudget = lowLatencyMode
289+
? XCWLowLatencySoftwareBitsPerPixelBudget
290+
: XCWSoftwareBitsPerPixelBudget;
291+
minimumAverageBitRate = lowLatencyMode
292+
? XCWMinimumLowLatencySoftwareAverageBitRate
293+
: XCWMinimumSoftwareAverageBitRate;
294+
}
270295
int64_t computedBitRate = (int64_t)width * (int64_t)height * bitsPerPixelBudget;
271296
if (computedBitRate < minimumAverageBitRate) {
272297
computedBitRate = minimumAverageBitRate;
@@ -364,6 +389,7 @@ @implementation XCWH264Encoder {
364389
CVPixelBufferRef _scaledPixelBuffer;
365390
OSType _scaledPixelFormat;
366391
XCWVideoEncoderMode _encoderMode;
392+
BOOL _lowLatencyMode;
367393
CMVideoCodecType _codecType;
368394
BOOL _hardwareAccelerated;
369395
NSUInteger _inputFrameCount;
@@ -397,8 +423,9 @@ - (instancetype)initWithOutputHandler:(XCWH264EncoderOutputHandler)outputHandler
397423
_pendingLock = OS_UNFAIR_LOCK_INIT;
398424
_needsKeyFrame = YES;
399425
_encoderMode = XCWVideoEncoderModeFromEnvironment();
426+
_lowLatencyMode = XCWLowLatencyModeFromEnvironment();
400427
_codecType = XCWVideoCodecTypeForMode(_encoderMode);
401-
_softwareFrameIntervalUs = XCWSoftwareInitialFrameIntervalUs;
428+
_softwareFrameIntervalUs = [self initialSoftwareFrameIntervalUsLocked];
402429
return self;
403430
}
404431

@@ -441,6 +468,28 @@ - (void)requestKeyFrame {
441468
});
442469
}
443470

471+
- (void)reconfigureWithCurrentEnvironment {
472+
os_unfair_lock_lock(&_pendingLock);
473+
if (_pendingPixelBuffer != NULL) {
474+
CVPixelBufferRelease(_pendingPixelBuffer);
475+
_pendingPixelBuffer = NULL;
476+
}
477+
_drainScheduled = NO;
478+
os_unfair_lock_unlock(&_pendingLock);
479+
480+
dispatch_sync(_queue, ^{
481+
[self invalidateCompressionSessionLocked];
482+
self->_encoderMode = XCWVideoEncoderModeFromEnvironment();
483+
self->_lowLatencyMode = XCWLowLatencyModeFromEnvironment();
484+
self->_codecType = XCWVideoCodecTypeForMode(self->_encoderMode);
485+
self->_softwareFrameIntervalUs = [self initialSoftwareFrameIntervalUsLocked];
486+
self->_lastSoftwareSubmissionUs = 0;
487+
self->_softwarePacedFrameCount = 0;
488+
self->_softwareHealthyFrameCount = 0;
489+
self->_needsKeyFrame = YES;
490+
});
491+
}
492+
444493
- (NSDictionary *)statsRepresentation {
445494
__block NSUInteger inputFrameCount = 0;
446495
__block NSUInteger pendingReplacementCount = 0;
@@ -466,6 +515,7 @@ - (NSDictionary *)statsRepresentation {
466515
@"softwarePacedFrames": @(self->_softwarePacedFrameCount),
467516
@"transportCodec": XCWCodecName(self->_codecType),
468517
@"encoderMode": XCWVideoEncoderModeName(self->_encoderMode),
518+
@"lowLatencyMode": @(self->_lowLatencyMode),
469519
@"encoderId": XCWVideoEncoderIDForMode(self->_encoderMode) ?: @"automatic",
470520
@"hardwareAccelerated": @(self->_hardwareAccelerated),
471521
@"lastSessionStatus": @(self->_lastSessionStatus),
@@ -492,12 +542,43 @@ - (void)invalidate {
492542
os_unfair_lock_unlock(&_pendingLock);
493543
}
494544

545+
- (NSUInteger)maximumInFlightFrameCountLocked {
546+
return (_encoderMode == XCWVideoEncoderModeH264Software && _lowLatencyMode) ? 1 : XCWMaximumInFlightFrames;
547+
}
548+
549+
- (uint64_t)minimumSoftwareFrameIntervalUsLocked {
550+
return _lowLatencyMode ? XCWLowLatencySoftwareMinimumFrameIntervalUs : XCWSoftwareMinimumFrameIntervalUs;
551+
}
552+
553+
- (uint64_t)initialSoftwareFrameIntervalUsLocked {
554+
return _lowLatencyMode ? XCWLowLatencySoftwareInitialFrameIntervalUs : XCWSoftwareInitialFrameIntervalUs;
555+
}
556+
557+
- (uint64_t)maximumSoftwareFrameIntervalUsLocked {
558+
return _lowLatencyMode ? XCWLowLatencySoftwareMaximumFrameIntervalUs : XCWSoftwareMaximumFrameIntervalUs;
559+
}
560+
561+
- (uint64_t)softwareFrameIntervalStepUsLocked {
562+
return _lowLatencyMode ? XCWLowLatencySoftwareFrameIntervalStepUs : XCWSoftwareFrameIntervalStepUs;
563+
}
564+
565+
- (NSUInteger)softwareHealthyFrameWindowLocked {
566+
return _lowLatencyMode ? XCWLowLatencySoftwareHealthyFrameWindow : XCWSoftwareHealthyFrameWindow;
567+
}
568+
569+
- (int32_t)expectedFrameRateLocked {
570+
if (_encoderMode == XCWVideoEncoderModeH264Software) {
571+
return _lowLatencyMode ? XCWTargetLowLatencySoftwareFrameRate : XCWTargetSoftwareFrameRate;
572+
}
573+
return XCWTargetRealTimeFrameRate;
574+
}
575+
495576
- (BOOL)shouldPaceSoftwareFrameAtTimeUs:(uint64_t)nowUs {
496577
if (_encoderMode != XCWVideoEncoderModeH264Software || _needsKeyFrame) {
497578
return NO;
498579
}
499580
if (_softwareFrameIntervalUs == 0) {
500-
_softwareFrameIntervalUs = XCWSoftwareInitialFrameIntervalUs;
581+
_softwareFrameIntervalUs = [self initialSoftwareFrameIntervalUsLocked];
501582
}
502583
if (_lastSoftwareSubmissionUs == 0) {
503584
return NO;
@@ -515,29 +596,32 @@ - (void)adaptSoftwarePacingForLatencyUs:(uint64_t)latencyUs {
515596
return;
516597
}
517598
if (_softwareFrameIntervalUs == 0) {
518-
_softwareFrameIntervalUs = XCWSoftwareInitialFrameIntervalUs;
599+
_softwareFrameIntervalUs = [self initialSoftwareFrameIntervalUsLocked];
519600
}
520601

521-
uint64_t highLatencyBudgetUs = (_softwareFrameIntervalUs * 3) / 2;
602+
uint64_t highLatencyBudgetUs = _lowLatencyMode ? _softwareFrameIntervalUs : ((_softwareFrameIntervalUs * 3) / 2);
522603
if (latencyUs > highLatencyBudgetUs) {
523-
uint64_t nextIntervalUs = _softwareFrameIntervalUs + XCWSoftwareFrameIntervalStepUs;
524-
uint64_t latencyBoundIntervalUs = latencyUs + XCWSoftwareFrameIntervalStepUs;
604+
uint64_t stepUs = [self softwareFrameIntervalStepUsLocked];
605+
uint64_t nextIntervalUs = _softwareFrameIntervalUs + stepUs;
606+
uint64_t latencyBoundIntervalUs = latencyUs + stepUs;
525607
if (nextIntervalUs < latencyBoundIntervalUs) {
526608
nextIntervalUs = latencyBoundIntervalUs;
527609
}
528-
_softwareFrameIntervalUs = MIN(nextIntervalUs, XCWSoftwareMaximumFrameIntervalUs);
610+
_softwareFrameIntervalUs = MIN(nextIntervalUs, [self maximumSoftwareFrameIntervalUsLocked]);
529611
_softwareHealthyFrameCount = 0;
530612
return;
531613
}
532614

533615
if (latencyUs < _softwareFrameIntervalUs &&
534-
_softwareFrameIntervalUs > XCWSoftwareMinimumFrameIntervalUs) {
616+
_softwareFrameIntervalUs > [self minimumSoftwareFrameIntervalUsLocked]) {
535617
_softwareHealthyFrameCount += 1;
536-
if (_softwareHealthyFrameCount >= XCWSoftwareHealthyFrameWindow) {
537-
uint64_t nextIntervalUs = _softwareFrameIntervalUs > XCWSoftwareFrameIntervalStepUs
538-
? _softwareFrameIntervalUs - XCWSoftwareFrameIntervalStepUs
539-
: XCWSoftwareMinimumFrameIntervalUs;
540-
_softwareFrameIntervalUs = MAX(nextIntervalUs, XCWSoftwareMinimumFrameIntervalUs);
618+
if (_softwareHealthyFrameCount >= [self softwareHealthyFrameWindowLocked]) {
619+
uint64_t stepUs = [self softwareFrameIntervalStepUsLocked];
620+
uint64_t minimumIntervalUs = [self minimumSoftwareFrameIntervalUsLocked];
621+
uint64_t nextIntervalUs = _softwareFrameIntervalUs > stepUs
622+
? _softwareFrameIntervalUs - stepUs
623+
: minimumIntervalUs;
624+
_softwareFrameIntervalUs = MAX(nextIntervalUs, minimumIntervalUs);
541625
_softwareHealthyFrameCount = 0;
542626
}
543627
return;
@@ -548,7 +632,7 @@ - (void)adaptSoftwarePacingForLatencyUs:(uint64_t)latencyUs {
548632

549633
- (void)drainPendingFramesLocked {
550634
while (YES) {
551-
if (_inFlightFrameCount >= XCWMaximumInFlightFrames) {
635+
if (_inFlightFrameCount >= [self maximumInFlightFrameCountLocked]) {
552636
_drainScheduled = NO;
553637
return;
554638
}
@@ -576,7 +660,7 @@ - (BOOL)encodePixelBufferLocked:(CVPixelBufferRef)pixelBuffer {
576660
return NO;
577661
}
578662

579-
CGSize targetSize = XCWScaledDimensionsForSourceSize(sourceWidth, sourceHeight, _encoderMode);
663+
CGSize targetSize = XCWScaledDimensionsForSourceSize(sourceWidth, sourceHeight, _encoderMode, _lowLatencyMode);
580664
int32_t targetWidth = (int32_t)targetSize.width;
581665
int32_t targetHeight = (int32_t)targetSize.height;
582666
if (targetWidth <= 0 || targetHeight <= 0) {
@@ -683,10 +767,8 @@ - (BOOL)ensureCompressionSessionWithWidth:(int32_t)width height:(int32_t)height
683767
_timestampOriginUs = 0;
684768
_needsKeyFrame = YES;
685769

686-
int expectedFrameRate = _encoderMode == XCWVideoEncoderModeH264Software
687-
? XCWTargetSoftwareFrameRate
688-
: XCWTargetRealTimeFrameRate;
689-
int averageBitRate = XCWAverageBitRateForDimensions(width, height, _encoderMode);
770+
int expectedFrameRate = [self expectedFrameRateLocked];
771+
int averageBitRate = XCWAverageBitRateForDimensions(width, height, _encoderMode, _lowLatencyMode);
690772

691773
VTSessionSetProperty(session, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue);
692774
if (@available(macOS 10.14, *)) {
@@ -713,8 +795,8 @@ - (BOOL)ensureCompressionSessionWithWidth:(int32_t)width height:(int32_t)height
713795
VTSessionSetProperty(session, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_High_AutoLevel);
714796
}
715797
VTSessionSetProperty(session, kVTCompressionPropertyKey_ExpectedFrameRate, (__bridge CFTypeRef)@(expectedFrameRate));
716-
VTSessionSetProperty(session, kVTCompressionPropertyKey_MaxKeyFrameInterval, (__bridge CFTypeRef)@(expectedFrameRate * 2));
717-
VTSessionSetProperty(session, kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration, (__bridge CFTypeRef)@2.0);
798+
VTSessionSetProperty(session, kVTCompressionPropertyKey_MaxKeyFrameInterval, (__bridge CFTypeRef)@(_lowLatencyMode ? expectedFrameRate : expectedFrameRate * 2));
799+
VTSessionSetProperty(session, kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration, (__bridge CFTypeRef)@(_lowLatencyMode ? 1.0 : 2.0));
718800
VTSessionSetProperty(session, kVTCompressionPropertyKey_AverageBitRate, (__bridge CFTypeRef)@(averageBitRate));
719801
if (@available(macOS 11.0, *)) {
720802
VTSessionSetProperty(session,

cli/XCWPrivateSimulatorSession.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ typedef void (^XCWPrivateSimulatorEncodedFrameHandler)(NSData *sampleData,
2727

2828
- (BOOL)waitUntilReadyWithTimeout:(NSTimeInterval)timeout;
2929
- (BOOL)waitForFirstEncodedFrameWithTimeout:(NSTimeInterval)timeout;
30+
- (void)reconfigureVideoEncoder;
3031
- (void)requestKeyFrameRefresh;
3132
- (void)requestFrameRefresh;
3233
- (id)addEncodedFrameListener:(XCWPrivateSimulatorEncodedFrameHandler)handler;

cli/XCWPrivateSimulatorSession.m

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,19 @@ - (void)requestKeyFrameRefresh {
167167
[_videoEncoder requestKeyFrame];
168168
}
169169

170+
- (void)reconfigureVideoEncoder {
171+
dispatch_sync(_stateQueue, ^{
172+
self->_latestKeyFrameData = nil;
173+
self->_latestKeyFrameTimestampUs = 0;
174+
self->_latestKeyFrameCodec = nil;
175+
self->_latestKeyFrameDecoderConfig = nil;
176+
self->_latestKeyFrameDimensions = CGSizeZero;
177+
self->_latestKeyFrameSequenceValue = 0;
178+
});
179+
[_videoEncoder reconfigureWithCurrentEnvironment];
180+
[self requestKeyFrameRefresh];
181+
}
182+
170183
- (void)requestFrameRefresh {
171184
[self refreshCurrentFrame];
172185
}

cli/native/XCWNativeBridge.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ bool xcw_native_input_send_key_event(void * _Nonnull handle, uint16_t key_code,
6969
void * _Nullable xcw_native_session_create(const char * _Nonnull udid, char * _Nullable * _Nullable error_message);
7070
void xcw_native_session_destroy(void * _Nullable handle);
7171
bool xcw_native_session_start(void * _Nonnull handle, char * _Nullable * _Nullable error_message);
72+
bool xcw_native_session_reconfigure_video_encoder(void * _Nonnull handle, char * _Nullable * _Nullable error_message);
7273
void xcw_native_session_request_refresh(void * _Nonnull handle);
7374
void xcw_native_session_request_keyframe(void * _Nonnull handle);
7475
bool xcw_native_session_send_touch(void * _Nonnull handle, double x, double y, const char * _Nonnull phase, char * _Nullable * _Nullable error_message);

cli/native/XCWNativeBridge.m

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,17 @@ bool xcw_native_session_start(void *handle, char **error_message) {
662662
}
663663
}
664664

665+
bool xcw_native_session_reconfigure_video_encoder(void *handle, char **error_message) {
666+
@autoreleasepool {
667+
NSError *error = nil;
668+
BOOL ok = [XCWNativeSessionFromHandle(handle) reconfigureVideoEncoder:&error];
669+
if (!ok) {
670+
XCWSetErrorMessage(error_message, error);
671+
}
672+
return ok;
673+
}
674+
}
675+
665676
void xcw_native_session_request_refresh(void *handle) {
666677
@autoreleasepool {
667678
[XCWNativeSessionFromHandle(handle) requestRefresh];

cli/native/XCWNativeSession.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ NS_ASSUME_NONNULL_BEGIN
1212
error:(NSError * _Nullable * _Nullable)error NS_DESIGNATED_INITIALIZER;
1313

1414
- (BOOL)start:(NSError * _Nullable * _Nullable)error;
15+
- (BOOL)reconfigureVideoEncoder:(NSError * _Nullable * _Nullable)error;
1516
- (void)requestRefresh;
1617
- (void)requestKeyFrame;
1718
- (BOOL)sendTouchAtX:(double)x

cli/native/XCWNativeSession.m

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,21 @@ - (void)requestRefresh {
9494
[self.session requestFrameRefresh];
9595
}
9696

97+
- (BOOL)reconfigureVideoEncoder:(NSError * _Nullable __autoreleasing *)error {
98+
[self.session reconfigureVideoEncoder];
99+
if (![self.session waitForFirstEncodedFrameWithTimeout:2.0]) {
100+
if (error != NULL) {
101+
*error = [NSError errorWithDomain:@"SimDeck.NativeSession"
102+
code:3
103+
userInfo:@{
104+
NSLocalizedDescriptionKey: @"Timed out waiting for the first encoded simulator frame after codec reconfiguration.",
105+
}];
106+
}
107+
return NO;
108+
}
109+
return YES;
110+
}
111+
97112
- (void)requestKeyFrame {
98113
[self.session requestKeyFrameRefresh];
99114
}

client/src/app/AppShell.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1249,8 +1249,9 @@ export function AppShell() {
12491249
}
12501250
const udid = selectedSimulator.udid;
12511251
setVideoCodec(codec);
1252-
if (codec === "hevc" && streamTransportMode === "webrtc") {
1253-
setStreamTransportMode("auto");
1252+
if (codec === "hevc" && streamTransportMode !== "webtransport") {
1253+
setStreamTransportMode("webtransport");
1254+
setStreamSettingsRevision((current) => current + 1);
12541255
}
12551256
void (async () => {
12561257
codecSwitchInFlightRef.current = true;

0 commit comments

Comments
 (0)