Repository navigation
fix(core): pressing play puts a sped-up or looping video at the right moment in one seek - #5800
Conversation
Edit accuracy: accurate 271 (base branch 271), smooth 168 of thoseThe gate passes. Quarantined, measured but not gated (0) |
076b6e1 to
43132ac
Compare
… moment in one seek Pressing play, or seeking while playing, placed every video as if it played at 1x from its start and never looped. The media sync that runs right after knew better, saw a gap of seconds on a 2x or looping clip, and seeked again: two decoder resets per press. Both now read one function for where a media element belongs in its source at a given time, so the first placement is the only seek.
…lready in place Setting the transport rate wrote that rate onto every media element, so a 1.15x clip played at 1x until the next media sync put it back; Studio sends a rate on every Play. The media sync already sets each element to its own rate times the transport rate, so the rate change now leaves elements alone and lets that sync apply it. Pressing Play also no longer seeks a video that is already at its source time, since every seek flushes the decoder.
…he slideshow is driving Play and pause placed every in-window video at the playhead, including a video the grading preview or a Studio scrub holds while paused, and slide media while the slideshow drives media itself. They now ask the media sync which clips are the runtime's to move.
43132ac to
c6cf633
Compare
There was a problem hiding this comment.
Verdict: APPROVE · 0 blocker · 0 important · 0 nits · head a511ff6a
Scope
Read the init.ts/media.ts diff against the full syncRuntimeMedia body. mediaSourceTimeAt behaves the same as the code it replaces, with two changes: it now clamps non-loop video before isHeldVideoTail, and lastRelativeTime now stores the looped-back time for loop clips. That value is only read by the audio re-entry check, which !clip.loop gates, and the >= sourceDuration checks still hold at the clamp. The preseek target is the same (it is video-only). Pause sets isPlaying=false before hardSyncAllMedia, so leases are respected. When applyPlaybackRate no longer writes every element, active clips still get the right rate from the forced next tick (media.ts:626), and the slideshow sets its own rate (hyperframes-slideshow.ts:804). CI is green on this head.
Reopened from #5754's split and rebased on current main. On this head: core runtime suite exit 0, 2158 passed; typecheck exit 0. Removing the rate from the placement fails 3 of 11 tests in
init.playPlacement.test.ts.What
Pressing Play, or seeking while playing, now puts every video at its real position in the source in a single seek, including a clip with its own playback rate, a speed ramp, or a loop.
Pause and play also no longer move a video that something else is in charge of: a video the color grading preview or a Studio scrub is holding while the preview is paused, and slide media while the slideshow is playing it.
Why
The first placement on play treated every video as if it played at 1x from its start and never looped. On a 2x clip, play at 5 s put the video at 5 s instead of 10 s; on a looping 4 s clip it put it at 5 s instead of 1 s. The media sync that runs right after computed the right position, saw the gap, and seeked again. That is two decoder resets for one press of Play, and a chance of a wrong frame in between.
The same placement also ignored what the per-tick media sync deliberately leaves alone. Pausing while the grading preview held a video (
window.__hf.leasePausedMedia) jumped that video to the playhead under the preview, and pressing play in a slideshow (native media sync disabled) rewrote the position of the slide video the slideshow had set.Related work
None.
How
There is now one function for "where this media element belongs in its source at time t":
mediaSourceTimeAtin the runtime's media module. It covers the rate (and rate lanes), the in-point, the loop wrap and the held last frame. The play placement, the per-tick media sync and the upcoming-video preseek all read it, and the play placement uses the same clip list as the sync.The sync's rule for which clips the runtime may move right now (none while native media sync is disabled; every clip except held ones while paused) is one function,
transportMovableClips, that both the per-tick sync and the play/pause placement call.Play also skips a video that is already at its source time, because every seek flushes the decoder. Setting the transport rate no longer writes that rate onto every element (a 1.15x clip briefly played at 1x on every Play, since Studio sends a rate with each Play). The media sync already sets each element to its own rate times the transport rate.
Decision: share the function rather than delete the play placement. A seek while playing leaves the videos playing, and the media sync deliberately never seeks a playing video for less than 0.5 s of drift, so without the placement a short seek while playing leaves the video behind (checked: a 0.3 s seek left it 0.3 s off the playhead with no seek).
Test plan
init.playPlacement.test.tsdrives the player: play at a time, and a seek while playing, on a 1x clip (control), a 2x clip and a looping clip. Each asserts the video is seeked exactly once, to the media sync's target. The 2x and looping cases fail on main with two seeks (for exampleexpected [ 5, 10 ] to deeply equal [ 10 ]), and fail the same way when the old 1x formula is put back in the play placement.expected [ 5 ] to deeply equal []), and a 1.15x clip's element rate is never written to anything but 1.15 when the transport rate is set to 1 (fails on the previous head withexpected [ 1 ] to deeply equal []).expected [ 0.3 ] to deeply equal []andexpected [ 5 ] to deeply equal [], and each fails again when its half of the rule is removed. Control: an unheld video is still placed on pause. The player suite passes too.