From 288b2ccc6fe8e696eae57707ee4ae0d8afb4217a Mon Sep 17 00:00:00 2001 From: Paolo Stivanin Date: Tue, 28 Jul 2026 11:05:55 +0200 Subject: [PATCH] fix: RangeError flood when the MSE staging buffer overflows video-rtc.js buffers incoming websocket frames into a fixed 2 MiB Uint8Array while the SourceBuffer is busy, and drains it on updateend. Two gaps make that wedge permanently: - if the drain's appendBuffer throws (QuotaExceededError is the common one, when a mobile browser backgrounds the tab and the decoder stops evicting), the exception is swallowed and bufLen is never reset. No update cycle was started, so updateend never fires again and the drain is never re-entered. - ondata then always takes the buffering branch, and buf.set(b, bufLen) has no bounds check. Once bufLen passes 2 MiB it throws "RangeError: offset is out of bounds" on every frame that arrives, at stream frame rate, until the socket closes. Observed as ~150 errors/second reported back to the server by Home Assistant's frontend error reporter. Reset bufLen when the drain fails, and bound-check the staging append, dropping the backlog and restarting the update cycle so the stream can resync on the next keyframe. --- www/video-rtc.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/www/video-rtc.js b/www/video-rtc.js index 953fdae66..34847f47e 100644 --- a/www/video-rtc.js +++ b/www/video-rtc.js @@ -460,7 +460,10 @@ export class VideoRTC extends HTMLElement { sb.appendBuffer(data); bufLen = 0; } catch (e) { - // console.debug(e); + // A failed appendBuffer starts no update cycle, so updateend + // never fires again and this drain is never re-entered. Clear + // the backlog instead of wedging it forever. + bufLen = 0; } } @@ -487,6 +490,21 @@ export class VideoRTC extends HTMLElement { this.ondata = data => { if (sb.updating || bufLen > 0) { const b = new Uint8Array(data); + if (bufLen + b.byteLength > buf.byteLength) { + // Staging buffer full because the SourceBuffer stopped draining. + // Drop the backlog and try to restart the update cycle, instead + // of throwing RangeError on every frame that follows. + bufLen = 0; + if (b.byteLength > buf.byteLength) return; + if (!sb.updating) { + try { + sb.appendBuffer(b); + } catch (e) { + // console.debug(e); + } + return; + } + } buf.set(b, bufLen); bufLen += b.byteLength; // console.debug('VideoRTC.buffer', b.byteLength, bufLen);