Skip to content

Commit 342bfd7

Browse files
committed
fix studio expose reliability
1 parent fcc497a commit 342bfd7

15 files changed

Lines changed: 430 additions & 78 deletions

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ profile (`1170` longest edge, dynamic up to `60` fps). Use
8484
`--stream-quality quality|balanced|fast|smooth|economy|ci-software` to override it,
8585
or pass `--video-codec hardware` when a dedicated hardware encoder is preferable.
8686
The remote viewer renders live video with the browser's native video element;
87-
the canvas is only used for input geometry.
87+
the canvas is only used for input geometry. Remote viewers can choose 15, 30,
88+
or 60 fps in the browser stream menu.
8889

8990
CLI commands automatically use the same warm daemon:
9091

client/src/app/AppShell.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ const LOCAL_STREAM_DEFAULTS: StreamConfig = {
9898
quality: "quality",
9999
};
100100
const REMOTE_STREAM_DEFAULTS: StreamConfig = {
101-
encoder: "auto",
101+
encoder: "software",
102102
fps: 30,
103103
quality: "balanced",
104104
};
@@ -1462,6 +1462,7 @@ export function AppShell({
14621462
onToggleTouchOverlay={() =>
14631463
setTouchOverlayVisible((current) => !current)
14641464
}
1465+
remoteStream={remoteStream}
14651466
search={search}
14661467
selectedSimulator={selectedSimulator}
14671468
selectedSimulatorIdentifier={selectedSimulatorDetail}

client/src/features/simulators/SimulatorMenu.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ interface SimulatorMenuProps {
2929
onToggleDebug: () => void;
3030
onToggleMenu: () => void;
3131
onToggleTouchOverlay: () => void;
32+
remoteStream?: boolean;
3233
search: string;
3334
selectedSimulator: SimulatorMetadata | null;
3435
setSelectedUDID: (udid: string) => void;
@@ -56,12 +57,17 @@ export function SimulatorMenu({
5657
onToggleDebug,
5758
onToggleMenu,
5859
onToggleTouchOverlay,
60+
remoteStream = false,
5961
search,
6062
selectedSimulator,
6163
setSelectedUDID,
6264
streamConfig,
6365
touchOverlayVisible,
6466
}: SimulatorMenuProps) {
67+
const fpsOptions = remoteStream
68+
? REMOTE_STREAM_FPS_OPTIONS
69+
: LOCAL_STREAM_FPS_OPTIONS;
70+
6571
return (
6672
<div className="menu-wrap" ref={menuRef}>
6773
<button
@@ -124,7 +130,7 @@ export function SimulatorMenu({
124130
))}
125131
</div>
126132
<div aria-label="Frame rate" className="menu-segment">
127-
{STREAM_FPS_OPTIONS.map((option) => (
133+
{fpsOptions.map((option) => (
128134
<button
129135
className={`menu-option ${streamConfig.fps === option.value ? "active" : ""}`}
130136
key={option.value}
@@ -208,12 +214,18 @@ const STREAM_ENCODERS: Array<{ label: string; value: StreamEncoder }> = [
208214
{ label: "Software", value: "software" },
209215
];
210216

211-
const STREAM_FPS_OPTIONS: Array<{ label: string; value: StreamFps }> = [
217+
const LOCAL_STREAM_FPS_OPTIONS: Array<{ label: string; value: StreamFps }> = [
212218
{ label: "30", value: 30 },
213219
{ label: "60", value: 60 },
214220
{ label: "120", value: 120 },
215221
];
216222

223+
const REMOTE_STREAM_FPS_OPTIONS: Array<{ label: string; value: StreamFps }> = [
224+
{ label: "15", value: 15 },
225+
{ label: "30", value: 30 },
226+
{ label: "60", value: 60 },
227+
];
228+
217229
const STREAM_QUALITY_OPTIONS: Array<{
218230
label: string;
219231
value: StreamQualityPreset;

client/src/features/stream/streamTypes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export interface StreamConnectTarget {
88
}
99

1010
export type StreamEncoder = "auto" | "hardware" | "software";
11-
export type StreamFps = 30 | 60 | 120;
11+
export type StreamFps = 15 | 30 | 60 | 120;
1212
export type StreamQualityPreset = "quality" | "balanced" | "fast";
1313

1414
export interface StreamConfig {

client/src/features/stream/streamWorkerClient.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ const WEBRTC_FIRST_FRAME_TIMEOUT_MS = 10000;
1616
const WEBRTC_STALLED_FRAME_TIMEOUT_MS = 3000;
1717
const WEBRTC_LOCAL_RECEIVER_BUFFER_SECONDS = 0.001;
1818
const WEBRTC_REMOTE_RECEIVER_BUFFER_SECONDS = 0.06;
19-
const WEBRTC_DISCONNECTED_GRACE_MS = 1000;
19+
const WEBRTC_LOCAL_DISCONNECTED_GRACE_MS = 1000;
20+
const WEBRTC_REMOTE_DISCONNECTED_GRACE_MS = 10000;
2021
const WEBRTC_RECONNECT_BASE_DELAY_MS = 250;
2122
const WEBRTC_RECONNECT_MAX_DELAY_MS = 1000;
2223

@@ -551,7 +552,7 @@ class WebRtcStreamClient implements StreamClientBackend {
551552
generation,
552553
new Error("WebRTC connection disconnected."),
553554
);
554-
}, WEBRTC_DISCONNECTED_GRACE_MS);
555+
}, disconnectedGraceMs(target));
555556
}
556557

557558
private scheduleReconnect(target: StreamConnectTarget, generation: number) {
@@ -611,11 +612,8 @@ class WebRtcStreamClient implements StreamClientBackend {
611612
return;
612613
}
613614
if (frameAgeMs > WEBRTC_STALLED_FRAME_TIMEOUT_MS) {
614-
this.handleConnectionError(
615-
target,
616-
generation,
617-
new Error("WebRTC video stalled after rendering frames."),
618-
);
615+
this.sendControl({ snapshot: true, type: "streamControl" });
616+
this.scheduleFrameWatchdog(target, generation);
619617
return;
620618
}
621619
this.scheduleFrameWatchdog(target, generation);
@@ -1105,6 +1103,12 @@ function receiverBufferSeconds(target: StreamConnectTarget): number | null {
11051103
: WEBRTC_LOCAL_RECEIVER_BUFFER_SECONDS;
11061104
}
11071105

1106+
function disconnectedGraceMs(target: StreamConnectTarget): number {
1107+
return target.remote
1108+
? WEBRTC_REMOTE_DISCONNECTED_GRACE_MS
1109+
: WEBRTC_LOCAL_DISCONNECTED_GRACE_MS;
1110+
}
1111+
11081112
function configureReceiverCodecPreferences(transceiver: RTCRtpTransceiver) {
11091113
if (!transceiver.setCodecPreferences) {
11101114
return;

client/src/features/stream/useLiveStream.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,8 +309,9 @@ export function useLiveStream({
309309
const latestStatus = latestStatusRef.current;
310310
const now = Date.now();
311311
if (
312+
!remote &&
312313
now - lastVisualArtifactSampleAtRef.current >=
313-
VISUAL_ARTIFACT_TELEMETRY_INTERVAL_MS
314+
VISUAL_ARTIFACT_TELEMETRY_INTERVAL_MS
314315
) {
315316
lastVisualArtifactSampleAtRef.current = now;
316317
const visualSample = await workerClientRef.current

client/src/features/toolbar/Toolbar.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ interface ToolbarProps {
3434
onToggleHierarchy: () => void;
3535
onToggleMenu: () => void;
3636
onToggleTouchOverlay: () => void;
37+
remoteStream?: boolean;
3738
search: string;
3839
selectedSimulator: SimulatorMetadata | null;
3940
selectedSimulatorIdentifier: string;
@@ -75,6 +76,7 @@ export function Toolbar({
7576
onToggleHierarchy,
7677
onToggleMenu,
7778
onToggleTouchOverlay,
79+
remoteStream = false,
7880
search,
7981
selectedSimulator,
8082
selectedSimulatorIdentifier,
@@ -135,6 +137,7 @@ export function Toolbar({
135137
onToggleDebug={onToggleDebug}
136138
onToggleMenu={onToggleMenu}
137139
onToggleTouchOverlay={onToggleTouchOverlay}
140+
remoteStream={remoteStream}
138141
search={search}
139142
selectedSimulator={selectedSimulator}
140143
setSelectedUDID={setSelectedUDID}

docs/guide/video.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ It is CLI-only because it is meant for less capable machines where freshness
3232
matters more than maximum smoothness.
3333

3434
The requested encoder mode is reported to clients in the JSON `videoCodec` field on `GET /api/health`.
35-
The browser UI exposes stream controls for encoder, FPS, and quality. Local browser sessions default to hardware H.264, 120 fps, and `quality`/full resolution; remote browser sessions default to software H.264, 30 fps, and `balanced`.
35+
The browser UI exposes stream controls for encoder, FPS, and quality. Local browser sessions default to hardware H.264, 120 fps, and `quality`/full resolution with FPS choices of 30, 60, and 120. Remote browser sessions default to software H.264, 30 fps, and `balanced` with FPS choices of 15, 30, and 60.
3636

3737
## Remote WebRTC ICE
3838

0 commit comments

Comments
 (0)