Skip to content

fix(core): a video that starts a cut far behind the playhead catches up in steps, not at a flat 3% - #6198

Merged
vanceingalls merged 1 commit into
mainfrom
fix/video-catchup-steer
Oct 24, 2026
Merged

vanceingalls merged 1 commit into
mainfrom
fix/video-catchup-steer

Conversation

@vanceingalls

Copy link
Copy Markdown
Collaborator

A playing video is steered back onto the playhead by rate, not seeked (a seek resets the decoder), at a flat 3% (#4511). A clip that waits on data at a cut starts 100-350 ms behind, and 3% closes that in 3-10 s, longer than a short clip plays. The picture trails the voice for the whole clip.

Measured in Desktop on a Riverside two-camera podcast episode, sampling every playing <video> against the audio-implied playhead every 100 ms: clips 2-4 started up to 100 ms behind and closed at about 35 ms per second (the 3% steer), the longest taking about 3 s.

The steer now steps with the lag: 3% up to 100 ms, 10% up to 300 ms, 25% beyond, in both directions. The rate is still only written when a step changes.

Tests: a far-behind and a far-ahead video steer by step, then step down as the lag closes; the existing 3% case now uses a lead that is still in the 3% step. media.test.ts passes; the repo typecheck only needs the generated runtime files built.

Not verified: that this removes the desync Vance saw (intermittent, preview only). That needs a play of an episode with this runtime in Desktop.

🤖 Generated with Claude Code

…up in steps, not at a flat 3%

A playing video is steered back by rate, not seeked, at a flat 3%. A talking-head clip
that waits on data at a cut starts 100-350 ms behind and takes 3-10 s to close, longer
than a short clip plays, so the picture trails the voice for the whole clip.

Measured in Desktop on a Riverside two-camera episode: clips 2-4 started up to 100 ms
behind and closed at 35 ms per second. The steer now steps with the lag (3% up to
100 ms, 10% to 300 ms, 25% beyond), so the rate is still written only when a step
changes.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
@github-actions

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 271 (base branch 271), smooth 121 of those

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (0)

Unstable (2)

  • seqheldtwice-tlhold-px-r0-root-z100: tracking 120.29, pressJump 312.3, drop 0, reload 0, render 0.02, renderKey -, undo true, teleport false / tracking 0.13, pressJump 0.11, drop 0.07, reload 0.07, render 0.02, renderKey -, undo true, teleport true / tracking 0.13, pressJump 0.11, drop 0.07, reload 0.07, render 0.02, renderKey -, undo true, teleport true
  • seqplainkeys-none-pct-r0-root-z100: tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo false, teleport true / tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true / tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: APPROVE · 0 blocker · 0 important · 1 nit · head f977f3b7f

  • nit packages/core/src/runtime/media.ts:338: steering also changes the rate of a video playing its own audio (not WebAudio-owned), so its sound plays at 1.25× until the lag drops below 300 ms, then 1.1× until it drops below 100 ms (~2 s). That is audible on speech, where 3% was not. It's fine if those clips carry no sound; otherwise consider capping the step for unmuted elements.
Scope

Read media.ts steering (steeredVideoRate, its single call site, hard-sync and eviction paths) and both new tests. Each step ends where the next begins, so the rate falls 25% → 10% → 3% as the lag closes, a ~16 ms tick can't overshoot a step, and the 10 ms release still applies. The 0.5 s hard sync and the 1/16 clamp are unchanged. The rate is still only written when a step changes (at most 3 extra writes per catch-up). Note: a lag ≤100 ms, which is all the measured episode showed, still closes at 3%. The new steps only act above that. CI is green on this head.

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additive to the earlier approval, which covered the audible speed-up on videos that play their own audio and noted that lags of 100 ms or less still close at 3%.

The steps read cleanly, both directions are tested, and the tests go through syncRuntimeMedia, so they exercise the real tick path. Two small things:

  1. media.ts:316 still says the rate is written only when steering starts or stops. With steps it is also written each time the step changes, so that comment needs a word.
  2. media.ts:338: the step boundaries have no dead band, unlike the start and release of steering. As the lag closes through 100 ms at 10%, it moves only a millisecond or two per tick, so a few ms of jitter between the video clock and the playhead could flip between 1.1x and 1.03x for several ticks, and each flip is a playbackRate write. I have not measured this; a small margin around each boundary (or only stepping down within one catch-up) would rule it out.

Verdict: APPROVE
Reasoning: Small, contained change with tests through the production entry point and all checks green; remaining points are a stale comment and an unmeasured flapping risk at step boundaries.

@vanceingalls
vanceingalls added this pull request to the merge queue Oct 24, 2026
Merged via the queue into main with commit e586922 Oct 24, 2026
80 checks passed
@vanceingalls
vanceingalls deleted the fix/video-catchup-steer branch October 24, 2026 22:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants