Repository navigation
fix(core): audio clips join on time and their volume lane follows the audio - #6419
Conversation
Edit accuracy: accurate 271 (base branch 271), smooth 153 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 ed235f55
- important (follow-up, not a regression):
packages/player/src/parent-media.ts:236. When iframe autoplay is blocked, the parent proxy owns audible playback, andmirrorTimestill seeks a playing<audio>after 2 samples over 50 ms. That is the click this PR removes from the runtime.steerMediais now exported through./runtime/media-steerfor this, but nothing calls it yet. Worth wiring in a follow-up.
Scope
Read the full diff (13 files). The 6 touched runtime suites pass locally, 339/339. Mutations each fail a test: the dip stop guard, unfollow on dispose, the audio pre-seek, playing audio routed through strict sync, and setRate writing to routed fx. All 9 workflows on this head are green (69 jobs). No other callers of the renamed VIDEO_PRESEEK_SECONDS / prepareUpcomingVideo. Not run live in a browser.
— Rames
497cf9f to
290483a
Compare
There was a problem hiding this comment.
Verdict: APPROVE · 0 resolved, 1 open, 0 new · ed235f55 → 290483a6
Rebased onto #6432 (09f15140). range-diff: commits 1 and 5 differ from ed235f55 only in their #6432 context. Commits 7 and 8 send the dipped seek through mediaElementTime and update its tests. Runtime suites pass locally, 344/344. PR captures is red: #6432's player files are in this diff, and this body has no Before/After. It should clear once #6432 merges and this rebases. Merge #6432 first. Merging this now would squash #6432 in with it.
— Rames
290483a to
c791f52
Compare
c791f52 to
5b520a7
Compare
There was a problem hiding this comment.
Verdict: APPROVE · 0 resolved, 1 open, 0 new · c791f520 → 5b520a7a
range-diff: all 8 commits identical. Only #6432 moved (36602a8d, generator and serializer files, none shared with this PR). Runtime suites pass locally, 343/343. All 9 workflows are green, PR captures included. #6432 is still unmerged, so merge it first.
— Rames
2aa35c2 to
67a685a
Compare
There was a problem hiding this comment.
Verdict: REQUEST CHANGES · 0 resolved, 1 open, 1 new · 5b520a7a → 67a685a2
The range-diff shows all 8 commits identical. Runtime suites pass locally (2582/2582), and #6432 has merged.
- New blocker — CI Build fails on this head (run 40399621376,
verify:packed-manifests):@hyperframes/inspector packed size 817397 bytes is over its budget of 808905 by more than 1%. Main933ae3ebpasses Build, and this PR's runtime code is bundled into inspector, so the rebase pushes it over. The fix is a budget raise inscripts/package-size-budgets.jsonwith the reason, as in #6457. Measured size plus 5% gives packed858267. Re-measure unpacked too. Studio's edit-accuracy failure (0 of 5 shards) comes after Build.
67a685a to
661a098
Compare
|
Head 661a098 (rebased on main 1df003d). The Build failure reproduces: this PR's runtime is bundled into the inspector package, whose packed size is 817397 bytes, over its 808905 budget. Raised the inspector packed budget to 858267 (measured plus 5%, as in #6457); |
There was a problem hiding this comment.
Verdict: APPROVE · 1 resolved, 1 open, 0 new · 67a685a2 → 661a0982
The range-diff shows commits 1–8 identical. Main 933ae3eb..1df003df touches none of this PR's files. The new commit raises the inspector packed budget to 858267, which is the failing 817397 × 1.05, rounded up. Build's verify:packed-manifests now passes, and all 9 workflows are green. Windows engine-cli failed once on the known chromeOrphans.process.test.ts flake and passed when the failed job was re-run.
… audio An incoming <audio> clip is now pre-seeked to its in-point like a video, and a playing audio element that drifts is steered by rate instead of seeked while it sounds. A routed clip's volume lane and fades are re-aimed at the element's own position whenever it starts, is seeked or changes rate.
…ks under a short dip Speech audibly changes tempo past a 10% rate nudge, and a bare seek clicks. Audio that holds a lag rate cannot close in about a second is seeked under a 15 ms dip on its routed graph; an unrouted element is seeked plainly. Video keeps its steps.
…ping included A rate change re-aimed a routed clip's lane on the context clock and again when the element took the new rate. Only the element re-aims it now. A looping element's wrapped position is placed on the pass the transport is in, so a looping bed's lane follows it too.
…parent proxy The steering steps, the audio cap and limit, and the steering state move to runtime/mediaSteer.ts as steerMedia(el, offset, baseRate, settled) and forgetSteering(el), exported as @hyperframes/core/runtime/media-steer so the player's parent-frame proxy uses the same policy instead of a second one.
Routes the one currentTime write this branch adds through mediaElementTime, so Chrome's truncated currentTime cannot land it a frame early.
661a098 to
512a3d1
Compare
There was a problem hiding this comment.
Verdict: APPROVE · 0 resolved, 1 open, 0 new · 661a0982 → 512a3d1f
The range-diff shows commits 1–8 identical. The inspector budget commit was dropped because #6460 raised that budget on main to 858046. Main's only change to this PR's files is #6460's compositionFps in init.ts, which is independent of this PR. src/runtime passes locally (2589/2589), and all 9 workflows are green, Build included.
What
At a cut between two
<audio>clips, the incoming clip now starts at its in-point and is never seeked while it sounds. Its volume lane and fades stay on the audio they shape, including after pause and play, a seek, a rate change and a loop.Why
A user's detailed report measured this on edits with one
<video>and one<audio>per take, with overlapping audio windows at each join:data-automationvolume lane landed up to 310 ms early, and up to 350 ms late after pause and play. Edge fades and make-up gain applied to the wrong stretch of audio.There were three causes:
<video>was pre-seeked to its in-point before its window opened. Audio got its first seek at window open and started late.Related work
Depends on #6432. This branch is stacked on it: every
currentTimewrite here goes through itsmediaElementTime, including the dipped seek this PR adds. Merge #6432 first, then this; until then the diff includes #6432's commits.Refs #5344 (same family: the audio gap at a split). Builds on #5633 (video pre-seek) and #4511 / #6198 (rate steering). The player's parent-frame proxy (#6417) imports the drift policy exported here (
@hyperframes/core/runtime/media-steer).Design calls
1. Getting the incoming audio to its in-point
<audio>in the same preparation path video uses.2. Correcting drift on audio that is sounding
3. Keeping a routed clip's lane and fades on its audio
followElementis the single place that maps the element's position to clip seconds.setRateno longer re-aims routed clips on the context clock, so the clip has one writer, not two.playingre-books.4. Re-aiming without side effects
reanchorcleared limiter and compressor state, which is right for a new pass but audible on every rate nudge. The FX handle now hasretime, which re-aims the envelopes only.reanchoris a reset followed byretime.How
media.ts:prepareUpcomingMedia(wasprepareUpcomingVideo) pre-seeks any paused clip withinMEDIA_PRESEEK_SECONDSof its start.init.ts's indexed pass now visits upcoming audio too.seekUnderDip.webAudioTransport.ts:followElementre-books the lane and fades onplaying,seekedandratechange. The timing istimeAtSourceTime(spec, currentTime - mediaStart)atplaybackRate / rateAt(...), with loops unwrapped.seekUnderDipramps the dip down over 15 ms, seeks, and ramps back up when the element lands (or errors). If the clip is stopped in the meantime, it does nothing.setRatehandles decoded sources only.audioFx.ts: addsretime.The decoded-buffer route is unchanged. Its source and its lanes already share the context clock.
Test plan
retimeresetting effect statepackages/coresrc/runtimepassed 3 runs in a row: 104 files, 2561 tests, exit 0. Coretsc --noEmitexits 0. oxlint reports 0 warnings and 0 errors onsrc/runtime.oxfmt --checkis clean.The local pre-commit lint, format, typecheck, fallow and commitlint hooks were skipped because this checkout has no node_modules. lint, format and typecheck ran clean on a remote copy (above). fallow and commitlint are left to CI.
What I measured
Measured in headless Chrome on Linux with this branch's runtime and with origin/main's (the "base" rows), built from the same tree.
The compositions are shaped like the report's two edits, built with our own media:
<video>and one<audio>on that file.<audio>has adata-automationvolume lane with 30 ms edge fades and a make-up gain of 1.36-2.04.The modes:
play: straight through.pause: pause 0.3 s before the second cut, hold 0.8 s, then play.seek: while playing, jump to 1 s before the second cut.The method follows the report:
<audio>seek is logged with the playhead at that moment.AudioParambooking is logged.currentTimeis sampled againstAudioContext.currentTimeevery animation frame.pausedturns false atplay(), before audio flows.Across all runs, seeks of a playing
<audio>drop from 59 on base to 1 on this branch.seekingevent fires afterplay()has already un-paused it.Before
One join of the 10-take edit on main, measured as above. On main, 15 of 27 joins on this edit had a mid-play seek; this is the first of them in run 0.
After
The same join and run with this branch's runtime. The incoming audio stays within about 7 ms of its schedule with no seek, and every lane point lands within 2 ms of its audio.
What I did NOT exercise