diff options
Diffstat (limited to 'packages/meshbay-hub/src')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/video-player.js | 65 |
1 files changed, 55 insertions, 10 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 42842d2..82e1116 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/video-player.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/video-player.js @@ -200,16 +200,26 @@ function VideoPlayer({ entry, transportRef, gekRef, onClose, onDownload }) { const evictBehind = useCallback(() => { const sb = sbRef.current; const v = videoRef.current; - if (!sb || !v || sb.updating || !sb.buffered.length) return false; - const keepFrom = Math.max(0, v.currentTime - BUFFER_BEHIND_S); - // The range being watched, not the first one: after a seek backwards the - // first range is somewhere else entirely, and removing from its start to - // just behind the playhead would take out everything in between — - // including what is playing. - const range = currentRange(); - const start = range ? range[0] : sb.buffered.start(0); - if (keepFrom - start < 10) return false; + if (!sb || !v) return false; + // `.buffered`/`.updating` throw InvalidStateError once the SourceBuffer + // has been removed from its MediaSource — same reasoning as currentRange + // and describeRanges just above, which already guard the same read. This + // one did not, and an uncaught throw here skips flushQueue right after it + // in pump() too, since nothing between them catches it. Found live: + // fired on every incoming segment once the SourceBuffer went stale, + // which pump() runs on a 1s timer regardless of whether new data is + // arriving — an unguarded read here is not a rare corner, it repeats + // forever. try { + if (sb.updating || !sb.buffered.length) return false; + const keepFrom = Math.max(0, v.currentTime - BUFFER_BEHIND_S); + // The range being watched, not the first one: after a seek backwards the + // first range is somewhere else entirely, and removing from its start to + // just behind the playhead would take out everything in between — + // including what is playing. + const range = currentRange(); + const start = range ? range[0] : sb.buffered.start(0); + if (keepFrom - start < 10) return false; sb.remove(start, keepFrom); return true; } catch { @@ -253,8 +263,22 @@ function VideoPlayer({ entry, transportRef, gekRef, onClose, onDownload }) { } queueRef.current.shift(); console.error('[MSE] appendBuffer error:', e); + // Anything else here does not get better by retrying: a SourceBuffer + // removed from its MediaSource stays removed. Discarding the segment + // and continuing left pump()'s credit grant running unchecked — it is + // driven by how much is successfully buffered, which never grows when + // nothing is actually appending — so the node kept sending and this + // kept discarding, forever. Found live: over 2 GB and 8000+ segments + // fetched for a picture that never appeared. Stop asking instead of + // spinning. + queueRef.current = []; + endedRef.current = true; + const transport = transportRef.current; + if (transport) transport.stopStream(); + setError(t('video.err_transport')); + setPhase('error'); } - }, [evictBehind]); + }, [evictBehind, transportRef]); /** * Decide whether the node may send more, and keep the pipeline moving. @@ -591,11 +615,32 @@ function VideoPlayer({ entry, transportRef, gekRef, onClose, onDownload }) { setPhase('streaming'); flushQueue(); }); + // Neither fires for the ordinary end-of-stream (that's `endOfStream()` + // succeeding, no event needed) — only for the browser's own decoder + // giving up on what was appended. Logged, not acted on: by the time + // this fires the MediaSource is already unusable and every SourceBuffer + // call from here on throws, which the existing catches already handle. + ms.addEventListener('sourceclose', () => { + console.error('[MSE] sourceclose — MediaSource left "open" on its own', + 'readyState:', ms.readyState); + }); if (videoRef.current) { videoRef.current.src = url; videoRef.current.addEventListener('seeking', onSeeking); videoRef.current.addEventListener('timeupdate', pump); + videoRef.current.addEventListener('error', () => { + const err = videoRef.current && videoRef.current.error; + console.error('[MSE] video element error, code:', err && err.code, + 'message:', err && err.message); + if (transportRef.current) { + transportRef.current.sendStreamDiag({ + event: 'video-element-error', + code: err ? err.code : null, + message: err ? err.message : null, + }); + } + }); // The element's own verdict. "buffering" on screen is this, and it // is the one thing the node cannot infer from a stream it is feeding. videoRef.current.addEventListener('waiting', onStarved); |