Repository navigation
fix(player): preview voice no longer goes silent while other tracks play - #6859
Conversation
Edit accuracy: accurate 271 (base branch 271), smooth 112 of thoseThe gate passes. Quarantined, measured but not gated (0) Unstable (1)
|
There was a problem hiding this comment.
Verdict: APPROVE · 0 blocker · 1 important · 0 nits · head 023e86a5
The change only reaches the decode fallback (init.ts ~5934, after element capture fails). A short copy now takes the same path as an undecodable file, which stays audible on the element. The fails without the fix: line is present.
Scope / CI
I read the diff, fetchAndDecode, decodeKey, and the one caller in init.ts. decodeAudioData has no other runtime call site that feeds the transport. All required checks are green. Six Studio: edit accuracy shards failed with "Unable to download artifact" (infra) and passed on rerun.
023e86a to
4568017
Compare
There was a problem hiding this comment.
Verdict: REQUEST CHANGES — 1 resolved, 0 open, 2 new (1 blocker, 1 important) · 023e86a → 4568017. My approval on 023e86a covered only the short-decode check; the Buffering hold is new.
- blocker: the required
Studio and player capturescheck fails. The PR touchespackages/player, and the body has its two recordings but no## Before/## Afterheadings. Put each video under its own heading; theeditedevent re-runs the check.
CI / scope
All other required jobs are green. Smoke: global install failed with an npm 404 on tslib-2.9.1.tgz: registry/infra, not required. I read the full diff since 023e86a, plus the tick order in init.ts around the hold (the hold check runs before syncMediaForCurrentState), the visibility/prefetch pass, and the player card.
What
A voice track in the preview no longer goes silent while the rest of the film plays on. Two causes, one fix each:
<audio>element, so its last seconds are not silent.Why
The runtime only noticed a waiting clip for video, and only to report it. An
<audio>clip whose data had not arrived (a cold cache or a slow network) sat atreadyState1 while the playhead followed another track, so in a podcast the voice went quiet while the music bed and the picture kept going. Reproduced on a synthetic podcast (a 40 s music bed, three 12 s voice clips, one video) served at 60 kB/s with a cold cache: on main the first voice clip was due but had no data for about 11 s while the music played and the playhead kept moving, and nothing on the stage said why.The second cause is narrower: the Web Audio transport plays a copy of each audio file made with
decodeAudioData. When an MP4's track timescale disagrees with its AAC stream,decodeAudioDatatrusts the container and returns a copy that is short by the ratio of the two rates, while the element plays the whole file. A 4 s, 48 kHz AAC file whosemdhdtimescale says 44.1 kHz decodes to 3.669 s in Chromium 156 and in WebKit 27.2; its element reports and plays 4 s. The transport's copy runs out 8% before the clip does and the rest of the clip is silent, which reads as a dropout. A re-mux rewrites the timescale and hides it.How
Buffering
init.ts: each tick, after media sync,mediaNotReadyAt(t)lists clips due at the playhead (inside their window, not merely cued) that have no data ahead, skipping what Web Audio is already playing from a decoded copy. A clip has data ahead when it reportsHAVE_FUTURE_DATA, or when it has at least its current frame and a buffered range runs 1 s past its position (or to its end): WebKit reports a paused element asHAVE_CURRENT_DATAhowever much it has buffered, so onreadyStatealone a hold there never ended. Running after media sync means a clip a slow frame skipped into has been asked to play, and so to fetch, before the transport holds for it. A clip counts only if the clock would also treat it as playable (isPlayableTrack, now shared withfollowedOrLongestRunningAudio): connected, not hidden, has a source, no error, not ended. If any are listed while playing, the transport halts the same way a pause does and remembers them; once none of them is still not ready, it plays again. A user pause during the hold clears it, so it does not resume by itself; Play during a hold stays in the hold. The transport loop does not park while holding.buffering, andisPlayingstays true during the hold so hosts keep showing a playing transport.bufferingstate shows the existing loading card titled "Buffering" (after the same 150 ms delay as "Loading assets", so a short hold does not flash it) and sets abufferingattribute on the element.assets-loading-ui="none"hides the card as before.hasFrozenPicturefrom feat(core): playback health reports how long a video on screen showed no moving picture #6856 is not reused: it reports a video belowHAVE_CURRENT_DATAor one whose time stands still over a 1 s look, while the hold needs any clip, audio included, belowHAVE_FUTURE_DATA.audioClockSourcetests described the old rule ("then lets the picture run on" after 3 s); they now assert that the playhead holds until data arrives, then plays on from where it held.Short decode
fetchAndDecode, the one place a file becomes the transport's copy, compares the copy's length with the element's. A copy more than 0.25 s short is treated like an undecodable file: no copy, the source is not retried, and the clip stays on its element, which the runtime leaves audible when the transport does not own it. 0.25 s sits above AAC priming and padding: Apple's HE-AAC of the same tone decodes to 4.0 s under a 4.133 s element, and AAC-LC to 3.989 s in WebKit, and both keep their decoded copy. A copy is kept when the element's duration is not finite (streams, MediaRecorder recordings), since there is nothing to compare it with.Test plan
init.buffering.test.ts"a track not ready pauses everything with Buffering, and resumes when it can play", "holds on the frame a slow tick skips into a clip with no data, after asking it to play", "resumes for a held clip with data buffered ahead that reports only its current frame, as WebKit's do" and "a pause while buffering stays paused once the track is ready"; player "shows Buffering while the runtime holds playback for a track, and hides it on resume";webAudioTransport.test.ts"hands a clip that decodes short to its element, and keeps one short only by its priming".expected 1.0166666666666657 to be less than 0.1: the playhead ran on for a second while the voice had no data) and the player test fails (nobufferingattribute); with the check back before media sync the slow-tick test fails (expected { isPlaying: true, buffering: false } to deeply equal { isPlaying: true, buffering: true }); with readiness onreadyStatealone the WebKit-shaped test fails (expected 0.016666666666666666 to be greater than 0.9); with theNumber.isFiniteguard removed the short-decode test fails on the infinite-duration recording (expected null not to be null)init.buffering.test.ts,audioClockSource.test.tsandcapturedMediaIdleRoute.test.tspass 3 runs in a row (24/24);webAudioTransport.test.ts3 runs (122/122); the runtime suite 2670/2670 and the player suite 575/575; oxfmt, oxlint and tsc (core and player) cleanreadyStatecase above); this branch plays the film to its end in 48.8 s. Headless WebKit keeps the clips paused under its autoplay rules even on main, so its silence counts are not a measure here.mdhdtimescale at 44.1 kHz; HE-AAC with explicit SBR, with implicit SBR, and implicit with a 44.1 kHz sample entry). Every file plays at 1000 Hz through bothdecodeAudioDataand the element, so none changes pitch; only themdhdmismatch changes length (3.669 s in WebKit, 3.672 s in Chromium, against 4 s).Before
Main, Chromium, 60 kB/s: the voice drops out while the music plays on, the picture freezes at 1.5 s, and nothing on the stage says why.
buffering-before.mp4
After
This branch, same fixture: playback pauses with the Buffering card until every due clip has data, then plays on with all tracks.
buffering-after-v2.mp4