diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-26 11:17:37 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-26 11:17:37 +0200 |
| commit | 27d59cbb2d11d863273e2282257d81ac9911ab97 (patch) | |
| tree | 52ca97dda523a148daae9e995ecd7b6ed092bfb0 /packages/meshbay-hub/src/meshbay_hub/static/video-player.js | |
| parent | 3be8bd2a7885fbd141f6cc12c2d2073f9a0ac56c (diff) | |
| download | meshbay-27d59cbb2d11d863273e2282257d81ac9911ab97.tar.gz | |
fix(transport): auto-reconnect after WebRTC failure, without dropping in-flight streams/downloads
Confirmed live (client trace + node logs, mobile screen-lock ~5min): ICE
goes disconnected -> failed within ~10s on both ends, but the DataChannel's
readyState stays "open" throughout, so nothing failed fast — every request
just sat out its own 8s/30s timeout, matching the reported symptom (poster
spinners, blocked chat, dead new streams).
transport.js: on connectionState "failed", reject pending requests
immediately (TransportLostError) and start a self-contained reconnect loop
(capped exponential backoff, redoes the full signaling handshake — the node
already discards the old session on its own "failed"/"closed", so there is
nothing lower-level to resume). New hooks: onNeedToken (fetch a fresh JWT,
since the captured one may have expired during the outage) and
onReconnected (let a consumer resume something that was mid-flight).
file-utils.js: pipelinedDownload retries a lost chunk instead of aborting
the whole transfer — covers Files downloads, poster/thumbnail fetches, and
music-player.js's blob-based track download, all of which go through it.
video-player.js: onReconnected reissues the existing seek-to-current-time
path, which already knows how to land a new stream_init on the live
SourceBuffer without resetting playback.
Playing audio is unaffected either way — musicbay.md's design downloads a
track to a blob before playing it, so a dead transport was never a network
dependency for what is already playing.
Stays on this branch until confirmed by real-device testing.
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/video-player.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/video-player.js | 19 |
1 files changed, 19 insertions, 0 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/video-player.js b/packages/meshbay-hub/src/meshbay_hub/static/video-player.js index 82e1116..78760d8 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/video-player.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/video-player.js @@ -529,6 +529,24 @@ function VideoPlayer({ entry, transportRef, gekRef, onClose, onDownload }) { setPhase('error'); }; + // The old stream died with the connection (the node retires it the + // moment its session goes away — see webrtc_server.py's + // on_state_change), so there is nothing to resume on the wire, only a + // reason to ask again. requestSeek already knows how to land a new + // stream_init on the live SourceBuffer without resetting playback — + // exactly what dragging the scrubber does — so reusing it here means a + // screen-lock reconnect looks like a seek to where the film already + // was, not a reload. + transport.onReconnected = () => { + if (cancelled) return; + const v = videoRef.current; + const seek = requestSeekRef.current; + if (!v || !seek) return; + console.log('[MeshBay] transport reconnected — resuming stream at', + v.currentTime.toFixed(1)); + seek(v.currentTime); + }; + transport.onStreamInit = (msg) => { if (cancelled) return; if (msg.file_id && msg.file_id !== entry.id) return; @@ -849,6 +867,7 @@ function VideoPlayer({ entry, transportRef, gekRef, onClose, onDownload }) { transport.onStreamData = null; transport.onStreamEnd = null; transport.onStreamError = null; + transport.onReconnected = null; } // The queue can hold several megabytes of decrypted video. queueRef.current = []; |