From 7f74f829ee635db420e1f1d5169c48f72d5b78f0 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Sat, 2 May 2026 18:14:39 -0400 Subject: [PATCH 1/5] Improve stream reconnect handling --- client/src/features/stream/streamTypes.ts | 1 + .../src/features/stream/streamWorkerClient.ts | 20 +++++++++++-------- client/src/features/stream/useLiveStream.ts | 7 ++++++- docs/guide/video.md | 2 +- server/src/logging.rs | 2 +- server/src/transport/webrtc.rs | 12 +++++++++++ 6 files changed, 33 insertions(+), 11 deletions(-) diff --git a/client/src/features/stream/streamTypes.ts b/client/src/features/stream/streamTypes.ts index 5ed03b7..ba07a6e 100644 --- a/client/src/features/stream/streamTypes.ts +++ b/client/src/features/stream/streamTypes.ts @@ -1,6 +1,7 @@ import type { Size } from "../viewport/types"; export interface StreamConnectTarget { + clientId?: string; remote?: boolean; udid: string; } diff --git a/client/src/features/stream/streamWorkerClient.ts b/client/src/features/stream/streamWorkerClient.ts index f08fb78..8f93467 100644 --- a/client/src/features/stream/streamWorkerClient.ts +++ b/client/src/features/stream/streamWorkerClient.ts @@ -12,8 +12,9 @@ const HAVE_CURRENT_DATA = 2; const WEBRTC_CONTROL_CHANNEL_LABEL = "simdeck-control"; const WEBRTC_TELEMETRY_CHANNEL_LABEL = "simdeck-telemetry"; const WEBRTC_FIRST_FRAME_TIMEOUT_MS = 10000; -const WEBRTC_STALLED_FRAME_TIMEOUT_MS = 8000; -const WEBRTC_DISCONNECTED_GRACE_MS = 8000; +const WEBRTC_STALLED_FRAME_TIMEOUT_MS = 60000; +const WEBRTC_RECEIVER_BUFFER_SECONDS = 0.06; +const WEBRTC_DISCONNECTED_GRACE_MS = 30000; const WEBRTC_RECONNECT_BASE_DELAY_MS = 3000; const WEBRTC_RECONNECT_MAX_DELAY_MS = 10000; @@ -47,9 +48,9 @@ function sendDataChannelMessage( export function buildStreamTarget( udid: string, - options: { remote?: boolean } = {}, + options: { clientId?: string; remote?: boolean } = {}, ): StreamConnectTarget { - return { remote: options.remote, udid }; + return { clientId: options.clientId, remote: options.remote, udid }; } export function canUseWebRtc(): boolean { @@ -431,7 +432,7 @@ class WebRtcStreamClient implements StreamClientBackend { const hasRenderedFrame = this.stats.renderedFrames > 0; const frameAgeMs = this.lastVideoFrameAt > 0 ? now - this.lastVideoFrameAt : Infinity; - if (!hasRenderedFrame || frameAgeMs > WEBRTC_STALLED_FRAME_TIMEOUT_MS) { + if (!hasRenderedFrame) { this.handleConnectionError( target, generation, @@ -439,6 +440,9 @@ class WebRtcStreamClient implements StreamClientBackend { ); return; } + if (frameAgeMs > WEBRTC_STALLED_FRAME_TIMEOUT_MS) { + this.postDiagnostics(target, "video-frame-watchdog-stalled"); + } this.scheduleFrameWatchdog(target, generation); }, this.stats.renderedFrames > 0 @@ -575,7 +579,7 @@ class WebRtcStreamClient implements StreamClientBackend { private postDiagnostics(target: StreamConnectTarget, detail: string) { const payload = { ...this.stats, - clientId: "webrtc-page", + clientId: target.clientId ?? "webrtc-page", connectionId: this.connectGeneration, detail, iceConnectionState: this.diagnostics.iceConnectionState, @@ -784,10 +788,10 @@ function configureLowLatencyReceiver(receiver: RTCRtpReceiver) { playoutDelayHint?: number; }; if ("jitterBufferTarget" in lowLatencyReceiver) { - lowLatencyReceiver.jitterBufferTarget = 0.001; + lowLatencyReceiver.jitterBufferTarget = WEBRTC_RECEIVER_BUFFER_SECONDS; } if ("playoutDelayHint" in lowLatencyReceiver) { - lowLatencyReceiver.playoutDelayHint = 0.001; + lowLatencyReceiver.playoutDelayHint = WEBRTC_RECEIVER_BUFFER_SECONDS; } } diff --git a/client/src/features/stream/useLiveStream.ts b/client/src/features/stream/useLiveStream.ts index 9852b5e..4b6fc62 100644 --- a/client/src/features/stream/useLiveStream.ts +++ b/client/src/features/stream/useLiveStream.ts @@ -242,7 +242,12 @@ export function useLiveStream({ return; } - workerClient.connect(buildStreamTarget(simulator.udid, { remote })); + workerClient.connect( + buildStreamTarget(simulator.udid, { + clientId: clientTelemetryIdRef.current, + remote, + }), + ); return () => { workerClient.disconnect(); }; diff --git a/docs/guide/video.md b/docs/guide/video.md index edcef47..cb1be82 100644 --- a/docs/guide/video.md +++ b/docs/guide/video.md @@ -78,7 +78,7 @@ The WebRTC path favors freshness: stale frames are dropped and the sender reques A few practical guidelines: - **Start on the default for local preview.** `auto` lets VideoToolbox choose without requiring the shared hardware encoder. -- **Switch to `software` when the hardware encoder stalls or is unavailable.** The encoder scales the longest edge to 1600 pixels, can climb toward 60 fps, and backs off dynamically under encode latency. +- **Switch to `software` when the hardware encoder stalls or is unavailable.** The encoder scales the longest edge to 1600 pixels, can climb toward 90 fps for local preview, and backs off dynamically under encode latency. - **Studio providers default to software H.264 plus `--stream-quality smooth`.** This profile uses a 1170-pixel longest edge, allows up to 60 fps, raises the bitrate budget to reduce compression artifacts, and lets multiple provider sessions share CPU cores without depending on one hardware encoder. - **The remote browser renders the live stream as a native `