Repository navigation
fix(core): a video after a cut plays in sync instead of about 70 ms behind - #6494
Conversation
There was a problem hiding this comment.
Verdict: APPROVE · 0 blocker · 1 important · 0 nits
No blocker. One important finding inline: a looping video's start can feed the learned delay a sample taken across its wrap.
Scope
Read the diff against #6438 (media.ts, media.test.ts, playbench analyze.ts/run.ts/test) at 5700a2ee, plus the surrounding sync path: mediaSourceTimeAt clamps relTime to mediaStart before the cut, and the playing visit window (MEDIA_PRESEEK_SECONDS) reaches clips due within the lead. Locally: vitest run src/runtime 106 files / 2597 tests pass, playbench analyzer 7/7, oxlint and oxfmt clean on the changed files. Main's required CI does not run on a PR stacked on checklong/playstart, so it must run once this is retargeted to main. Playbench numbers taken from the body.
1920126 to
f90a557
Compare
5700a2e to
20a81e5
Compare
The base branch was changed.
Edit accuracy: accurate 271 (base branch 271), smooth 156 of thoseThe gate passes. Quarantined, measured but not gated (0) |
20a81e5 to
7fa1f88
Compare
c1fbfe0 to
fc0dfb5
Compare
The base branch was changed.
…ay, so it plays in sync
…behind the playhead
…t 100 ms under test
fc0dfb5 to
81a80e0
Compare
What
At a cut between video clips, the incoming video now plays in step with the playhead and its sound, instead of about 70 ms behind it.
Why
A user's detailed report measured that at every cut of a multi-take edit the incoming video's second frame arrived about 66 ms late. Measured on the playbench (#6418), the incoming picture runs 62-75 ms behind the playhead over the first half second of every cut, then takes over a second of rate steering to catch up; the steering's rate change is what makes the second frame late.
The cause is the browser, measured in isolation (a muted 25 fps
<video>,play(), Chrome 144, 8 runs each):play()play()A muted video still clocks itself off its file's audio track, so its playback starts 50-67 ms after
play(). The hidden/visible switch at a cut makes no difference.Related work
Builds on #6438 (a video no longer runs ahead of the playhead or past its out-point), now merged. Builds on #6515 (now merged), after which core's npm package no longer ships the unminified media sync, so this PR adds 908 B to core's tarball (the two minified runtime bundles) and fits its size budget with no raise.
How
Design call. Options:
Picked 3:
play(), the sync notes the video's offset from the playhead and how early it was started. A quarter second into the clip, the offset's change plus that lead is the start delay. The first sample is taken as is, later ones are averaged with the running value, and it is capped at 0.1 s.startsEarlypath audio already uses for its one-tick cue), so it starts on the tick nearest the right moment. Until a start has been measured the delay is unknown and nothing changes.play()is read after the seek, so the stale pre-seek offset never becomes a sample.play()and the sample, which would put the sample off by a lap.No extra work per frame or per render: one WeakMap lookup per playing video per tick, and an upcoming video starts decoding a few tens of milliseconds sooner.
The playbench gains a row: how far the incoming picture runs behind the playhead over its first 0.5 s. The existing second-frame metric reads only the first two frames, so it missed the same delay on the 30 fps edit.
Before
After
What I measured
Playbench in Chrome 144 on an 8-CPU Linux box, #6438's head against this branch, median [min-max] over 5 runs per setup (10 runs for the last column):
At 25 fps on a 60 Hz display, 33 and 50 ms are the normal frame steps.
vitest run src/runtime: 106 files, 2599 tests, 3 runs in a row; playbench analyzer tests 7/7; oxfmt, oxlint, coretsc, playbenchtsc, comment ratchet and fallow audit clean.What I did NOT exercise
Test plan