feat(ui): timeline polish — filmstrips + draggable playhead#39
Merged
Conversation
GET /api/media/thumb?mediaId=&t=&h= extracts one small still from a SOURCE media file (not the composited timeline) via a fast input-seek frame grab (buildThumbnailArgs). Each thumb is keyed by (mediaId, t, h) and cached under the workspace, generated to a temp path then atomically renamed so concurrent filmstrip requests never serve a half-written file. Unknown media → 404; a non-thumbnailable source (e.g. audio-only) → 422, so a clip just falls back to its gradient block.
Media clips now show a row of source-frame thumbnails across their trimmed window — a filmstrip — instead of a flat gradient block. The clip samples one /api/media/thumb still per ~72px slot (capped at 16), at the middle of each slot; the gradient shows underneath until the frames load, a scrim keeps the label/duration legible, and text/color clips keep their solid block. Thumbs are lazy-loaded so long timelines don't fetch off-screen frames.
The playhead was a non-interactive line (pointer-events:none) and the only scrub control was a disconnected 220px slider in the header — there was nothing to grab on the timeline. Now: - The playhead is a draggable handle: pointer-down + drag scrubs to the cursor anywhere along the timeline (setPointerCapture + a clientX→sec map that accounts for the gutter and horizontal scroll). It renders as the conventional thin line + a downward-triangle marker. - A transparent native range over the ruler is the scrub surface: click or drag to position, arrow keys (one frame each) move the playhead — the accessible, keyboard-friendly control that replaces the removed header slider. Also removes a stale duplicate .doc-tl-playhead::before that survived an earlier refactor and was offsetting the line with a stray border wedge.
|
🎉 This PR is included in version 0.3.0 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Timeline polish: filmstrips + a draggable playhead
Two timeline-UX improvements heading into v0.3.0. Media clips read like a real NLE, and the playhead is finally interactive.
1 — Frame-thumbnail filmstrips on clips
Media clips were flat gradient blocks. Now each shows a row of source-frame thumbnails across its trimmed window.
GET /api/media/thumb?mediaId=&t=&h=— one small still from a source media file (fast input-seek), cached per(mediaId, t, h)under the workspace, generated to a temp file then atomically renamed so concurrent filmstrip requests never serve a half-written frame. Unknown media → 404; a non-thumbnailable source → 422 (the clip falls back to its gradient).DocTimelinelays out one thumb per ~72px slot (≤16), lazy-loaded; a scrim keeps the label/duration legible; text/color clips keep their solid block.2 — Draggable playhead + ruler scrub rail
The playhead was a non-interactive line (
pointer-events: none); the only scrub control was a disconnected 220px slider in the header — nothing to grab on the timeline.setPointerCaptureand aclientX → secondsmap that accounts for the gutter and horizontal scroll..doc-tl-playhead::beforethat was offsetting the line with a stray border wedge.Verification
pnpm type-check✓ · 548 tests ✓ (new:buildThumbnailArgsarg shape) ·pnpm lintexit 0 ·pnpm build✓.533px(128 + 4.5×90) with the readout at 4.50s; aclientXat the 3s mark maps to 3.00s; the playhead computespointer-events: auto/ew-resize; after the fix the line is centered (left: 8pxof the 16px strip, no stray border) and the marker is a border-triangle, not a rotated square. An adversarial review pass independently caught the duplicate-::beforedefect; fixed.🤖 Generated with Claude Code