Skip to content

Hybrid timeline sync: an Animation timeline-locks the video preview (source-media issue 05) - #16

Merged
lenxism merged 1 commit into
mainfrom
feat/source-media-05-hybrid-timeline-sync
Jul 14, 2026
Merged

Hybrid timeline sync: an Animation timeline-locks the video preview (source-media issue 05)#16
lenxism merged 1 commit into
mainfrom
feat/source-media-05-hybrid-timeline-sync

Conversation

@lenxism

@lenxism lenxism commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Summary

  • With an Animation, the Source Video preview is timeline-locked: the <video> element stays paused and a transient playhead subscription seeks currentTime = playheadT % videoDuration, so scrubbing the timeline scrubs the video and timeline play advances both in lockstep. Without an Animation the free-run path (autoplay/loop/rVFC, issue 04) is untouched; adding or removing an Animation switches modes.
  • The wrap/guard formula lives in core/media-time.ts (videoTimeAtPlayhead), written test-first: wrap at the intrinsic duration, whole-loop landings, negative playheads, and zero/NaN/Infinity duration guards.
  • The per-presented-frame rVFC → invalidate() loop now runs in both modes: VideoTexture flags its GPU upload from its own internal rVFC, which lands after the seeked event, so a seeked-driven repaint painted the stale frame (found during evidence capture). rVFC goes silent when nothing presents, so zero-frames-idle holds in both modes.
  • The dropzone's free-run play/pause chip hides while an Animation exists — the timeline transport owns playback then.

Mechanism per the ticket, settled by the proto/video-preview-playback prototype.

Evidence

Playwright + instrumented GL draw-call counter, in .scratch/source-media/evidence/05/ (script: .scratch/pw-runner/capture-hybrid-sync-evidence.cjs, frame-counter clip):

  • Scrub to 1.0 s / 3.5 s → the displayed counter reads exactly those times, element paused.
  • 30 s Animation over the 10 s clip, playhead 12 s → displays t=2 s (the wrap).
  • Timeline play: playhead and video advance in lockstep (1.86 → 3.73 s both), element paused throughout.
  • Paused timeline: 0 GL draw calls over 2.5 s.
  • Removing the Animation resumes free-run (element playing, mediaTime advancing).

Test plan

  • pnpm typecheck, pnpm lint, pnpm build, pnpm test (443 tests, 26 files) green
  • Browser-verified lockstep scrub/play, wrap, mode switching, zero-frames-idle (evidence above)

Note

Medium Risk
Touches preview rendering and playhead/video sync paths; behavior is well-tested in core but browser seek/rVFC timing remains inherently fragile.

Overview
When the document has an Animation, source video preview switches from free-run playback to timeline-locked mode: the <video> stays paused and seeks to videoTimeAtPlayhead(playheadT, duration) on playhead changes, so scrubbing and timeline transport stay in sync. Without an Animation, the existing free-run path (store play/pause + rVFC) is unchanged; mode flips when an Animation is added or removed.

videoTimeAtPlayhead in core/media-time.ts centralizes wrap-at-duration and guards for invalid duration/playhead; it is exported from core and covered by unit tests.

useSourceVideoTexture splits effects: free-run still subscribes to videoPreviewPlaying; timeline-locked subscribes to playheadT with a micro-seek epsilon to avoid decoder churn. rVFC → invalidate() now runs in both modes so locked seeks show the presented frame (not stale texture after seeked alone). The dropzone play/pause chip hides while timeline-locked so it does not fight timeline transport.

Reviewed by Cursor Bugbot for commit 11c4551. Bugbot is set up for automated code reviews on this repo. Configure here.

…source-media issue 05)

With an Animation the preview <video> stays paused and a transient playhead
subscription seeks it (playheadT % videoDuration, core's videoTimeAtPlayhead),
so scrubbing and timeline play drive video and animation in lockstep; without
one the free-run rVFC path is unchanged. The rVFC invalidate now runs in both
modes because VideoTexture uploads on its own rVFC, after `seeked`. The
free-run play/pause chip hides while the timeline owns playback.
@vercel

vercel Bot commented Jul 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
mockstudio Ready Ready Preview, Comment Jul 14, 2026 2:20am

@lenxism
lenxism merged commit 70faa85 into main Jul 14, 2026
4 checks passed
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.

1 participant