fix(web-ui): clamp non-monotonic video sample duration to survive spliced catchup streams#525
Merged
Conversation
…iced catchup streams Telco catchup recordings (e.g. Hunan/Jiangsu Telecom) splice segments with overlapping timestamps. A mid-batch dts regression produced a negative video sample duration, which underflowed to ~2^32 ms in the trun box, corrupted the MSE buffered range and killed playback with a decode error. Clamp non-positive durations to the reference frame duration and re-anchor the remaining samples of the batch, mirroring the existing inter-batch dtsCorrection and the audio track's monotonicity enforcement. Also clear aac_last_incomplete_data_ after consumption so a fully-parsed stale buffer is not prepended again on the next PES payload. Fixes #448 Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Contributor
|
Azure Static Web Apps: Your stage site is ready! Visit it here: https://thankful-water-0a297bf00-525.eastasia.1.azurestaticapps.net |
4 tasks
stackia
added a commit
that referenced
this pull request
Jun 12, 2026
…eams (#528) ## Summary Fixes the `addSourceBuffer` race that makes **audio+video MPEG-TS streams play without sound on Chrome** (reproduced 100% locally; likely behind the "CCTV 4K 无声音" report in #448). ### Root cause The race dates back to the mpegts.js fork 3.0 rewrite (Feb 2026), which moved transmuxing permanently into a Worker with one `postMessage` per event. Upstream mpegts.js ran the demux→remux→MSE chain synchronously on the main thread, so both `addSourceBuffer` calls always happened in a single event-loop task and the race could not occur. With the Worker architecture, each init segment arrives in its own task. The player appended the **video** init segment as soon as its message arrived, then yielded; the media engine finished parsing it and locked the SourceBuffer set. When the **audio** init-segment message arrived in the next task, `addSourceBuffer` threw `QuotaExceededError` ("This MediaSource has reached the limit of SourceBuffer objects"), and the audio track was silently dropped. Instrumented timeline before the fix (Chrome): ``` t=43ms addSourceBuffer(video/mp4; avc1.42c01e) ok t=43ms appendBuffer(video init, 659 B) ok t=44ms addSourceBuffer(audio/mp4; mp4a.40.2) QuotaExceededError ``` ### Fix - The player now stashes `init-segment` messages and flushes them together right before the first non-init message, creating **all SourceBuffers in a single task** — restoring the invariant upstream had implicitly. The MSE buffer-append algorithm runs as a queued task, so no init segment parse can complete between the two `addSourceBuffer` calls. - Mid-stream codec changes now use `SourceBuffer.changeType()` instead of `addSourceBuffer()`, which always throws for an existing track once the media engine has initialized. ### Notes / known limitation - Startup latency impact is negligible: the first media-segment message follows the init segments within the same worker parse round, and playback cannot start without media data anyway. - The pre-open (`pendingSourceBufferInit`) and ManagedMediaSource paths already batched init appends and are unchanged. - A stream whose audio PES starts only after video media segments have been flowing (PMT advertises audio but data arrives seconds late) would still hit the same error — that exotic case was equally broken before this fix and would need PMT-based track pre-announcement to solve; left out to keep the change minimal. ## Verification Same instrumented harness after the fix — both SourceBuffers created in one task, zero player errors, and `webkitAudioDecodedByteCount` confirms audio is actually decoding: ``` t=55ms addSourceBuffer(video) ok t=55ms appendBuffer(video init) ok t=55ms addSourceBuffer(audio) ok t=55ms appendBuffer(audio init) ok audioDecoded: 96523 bytes (was 0) ``` Also verified against the spliced catchup stream from #525: plays to the end with audio, single sane buffered range. ## Test plan - [x] Normal H.264+AAC TS stream: audio SourceBuffer created, audio decodes - [x] Spliced catchup stream (#448 scenario): plays through with audio - [x] type-check + biome - [ ] Smoke test on Safari / Firefox / iOS (ManagedMediaSource pre-open path already batches via pendingSourceBufferInit) --------- Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
0.00 – 4294971.67sobserved) and killed playback with a decode error → black screen after 3 retries._remuxVideonow clamps non-positive sample durations to the reference frame duration and re-anchors the remaining samples of the batch viadtsCorrection— mirroring the existing inter-batch re-anchoring and the audio track's monotonicity enforcement. One integer comparison per sample, no extra parsing.aac_last_incomplete_data_after consumption (ADTS + LOAS) so a fully-parsed stale buffer is not prepended again on the next PES payload.Verification
A/B test with an ffmpeg-crafted spliced TS (H.264 + AAC, 2 s timestamp regression at the 6 s splice point), played through
createPlayerin Chrome:0.00 – 4294971.67(corrupted)0.00 – 11.96(single sane range)A normal continuous TS stream plays unchanged (regression check).
tsc --noEmitandbiome checkpass.Test plan
Made with Cursor using Fable 5