aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/video-player.js65
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);