Skip to content

fix: timeline/preview sync for speed-edited clips (clip offsets, mapping, zoom split)#439

Closed
ExtraBinoss wants to merge 10 commits intowebadderallorg:mainfrom
ExtraBinoss:fix/timeline-slow-clip-visibility
Closed

fix: timeline/preview sync for speed-edited clips (clip offsets, mapping, zoom split)#439
ExtraBinoss wants to merge 10 commits intowebadderallorg:mainfrom
ExtraBinoss:fix/timeline-slow-clip-visibility

Conversation

@ExtraBinoss
Copy link
Copy Markdown
Contributor

@ExtraBinoss ExtraBinoss commented May 7, 2026

Summary

Description

This PR fixes timeline/playback inconsistencies when changing clip speed, especially with adjacent clips.

Description

  1. Correct source vs timeline mapping for speed-edited clips
  • Introduced a source anchor on clips (sourceStartMs) and updated mapping helpers to use it.
  • Prevents wrong source-time remapping after timeline offsets.
  1. Proper timeline offset propagation after clip speed changes
  • When a clip duration changes due to speed, following clips are shifted by the exact delta.
  • Related regions are remapped consistently to keep alignment.
  1. Fixed clip->trim conversion after source/timeline separation
  • clipsToTrims now uses source boundaries (sourceStartMs / source end), avoiding invalid trim
    gaps and skip loops.
  1. Prevented repeated seek loops at trim boundaries
  • Added guard in trim-skip logic to avoid re-seeking when target time does not advance.
  1. Improved adjacent-clip boundary behavior
  • Mapping uses safer boundary handling for touching clips and tiny rounding gaps.
  1. Zoom behavior improvements
  • Splitting a clip now also splits overlapping zoom regions.
  • Zoom timing is remapped with speed changes (not only scaled in a limited way).
  1. Timeline duration reflects actual edited timeline
  • Timeline duration/UI now follows clip layout (end of timeline clips), not just raw source
    duration

Why

Previously, slowing down a clip and placing another clip after it could cause:

  • playhead jumps/skips,
  • incorrect speed spillover behavior,
  • trim/seek instability,
  • zoom/cursor timing drift.
    This PR makes timeline edits and preview playback consistent under speed changes.

Type of Change

  • Bug Fix

Related Issue(s)

[Bug]: Slow-speed clips are not fully visible on the timeline #434

Video:
speed_fix_recordly.webm

Testing Guide

See video for how to test it

Checklist

  • I have performed a self-review of my code.
  • I have added any necessary screenshots or videos.
  • I have linked related issue(s) and updated the changelog if applicable.

Summary by CodeRabbit

  • New Features

    • Audio waveform now renders per-pixel bars and can account for clip regions / source timing; timeline accepts clip regions so scale reflects clip-aware duration. Clip regions may include source-offsets.
  • Bug Fixes

    • Fixed timeline↔source mapping for seeking, speed edits, splits, trims, zooms and exports; audio playback sync improved; prevented seek loops during trim snapping.
  • Performance

    • Batched horizontal wheel panning for smoother scrolling.
  • Style

    • Simplified keyframe marker transition behavior.
  • Tests

    • Added coverage for source-shifted clips and mapping round-trips.
  • Chores

    • Improved export error diagnostics and logging.

add: VideoEditor fix when a clip is shorter than an other, it would skip it
fix: zoom will also be cut according to split/speed of a clip
Copilot AI review requested due to automatic review settings May 7, 2026 20:34
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR refactors clip timing throughout the video editor to be source-based rather than timeline-only. It introduces sourceStartMs on clips, updates bidirectional timeline-to-source mapping functions, and applies these changes to seeking, splitting, speed changes, waveform rendering, timeline sizing, export logging, and tests.

Changes

Source-Based Clip Timing & Timeline Mapping

Layer / File(s) Summary
Data Contracts & Timing Helpers
src/components/video-editor/types.ts
ClipRegion gains optional sourceStartMs; new helpers getClipSourceStartMs, getClipSourceEndMs, getSafeClipSpeed and clip-order normalization utilities added; clipsToTrims uses source-derived boundaries.
Timeline ↔ Source Mapping
src/components/video-editor/types.ts
mapTimelineTimeToSourceTime and mapSourceTimeToTimelineTime updated to return 0 for non-finite input, normalize clip ordering, use source-derived boundaries with exact-end special-casing and epsilon gap snapping; findClipAtTimelineTime interval semantics adjusted.
VideoEditor Imports & Timeline Duration
src/components/video-editor/VideoEditor.tsx
Imports getClipSourceStartMs; memoizes timelineDuration from clipRegions max end time; effectiveSpeedRegions derive startMs/endMs from source helpers and timeline-bound UI logic now uses timelineDuration.
Seek & Trim-Skip Validation
src/components/video-editor/VideoEditor.tsx, src/components/video-editor/videoPlayback/videoEventHandlers.ts
handleSeek maps timeline→source with finiteness validation and clamps mapped source to [0,duration]; skip-forward and remaining-time display use timelineDuration; skipPastTrimRegion guards against no-progress seek loops.
Clip Split & Speed Mutations
src/components/video-editor/VideoEditor.tsx
handleClipSplit computes left/right sourceStartMs using getClipSourceStartMs, applies a safeSpeed fallback, and splits zoom regions spanning the split; handleClipSpeedChange recomputes source durations/endMs, updates sourceStartMs for changed and moved clips, and remaps subsequent regions using shared remapping logic.
Waveform Rendering & Audio Sync
src/components/video-editor/timeline/AudioWaveform.tsx, src/components/video-editor/VideoEditor.tsx
AudioWaveform accepts optional clipRegions and remaps per-pixel timeline→source using region start/sourceStartMs/speed; rendering switches to per-pixel vertical bars from peakData bins; audio sync uses mapped timeline/source anchors.
TimelineEditor Wiring & Wheel Pan
src/components/video-editor/timeline/TimelineEditor.tsx
Derives totalMs from maximum end across zoom/clip/speed/annotation/audio regions with fallback to sourceDurationMs; adds clipRegions prop to Timeline and forwards to AudioWaveform; horizontal wheel-pan deltas are batched and flushed on RAF; trim normalization uses sourceDurationMs.
Export Diagnostics
src/components/video-editor/VideoEditor.tsx
Adds debug logging of export result shape and richer exception logging (message/stack/cause/code) plus contextual audio/speed region data.
Polish
src/components/video-editor/timeline/KeyframeMarkers.tsx
Removes conditional transition styling on keyframe marker; positioning and event handlers unchanged.
Tests
src/components/video-editor/types.test.ts
Adds tests for source-shifted clip mappings, round-trip mapping, and exact clip-end boundary behavior for shifted clips.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

Checked

Poem

🐰 I hop where source and timeline meet,
Clips keep their starts and speeds discreet,
Waveforms draw as pixel bars,
Seeks stay safe — no looping scars,
Logs and tests keep everything neat.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main changes: fixing timeline/preview sync for speed-edited clips, addressing clip offsets, mapping, and zoom split behavior.
Description check ✅ Passed The description covers all major template sections: comprehensive summary with seven numbered key improvements, clear motivation explaining why the changes were needed, correct bug fix classification, linked issue (#434), included test video, and completed checklist items.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@ExtraBinoss ExtraBinoss changed the title fix: timeline/preview sync for speed-edited clips (clip offsets, mapping, zoom split fix: timeline/preview sync for speed-edited clips (clip offsets, mapping, zoom split) May 7, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/video-editor/types.ts (1)

237-259: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clamp timeline gaps to source boundaries, not timeline boundaries.

When no clip contains timeMs, this function still falls back to the nearest timeline edge. For any clip where sourceStartMs !== startMs, seeking in dead space before/after a clip returns the wrong source offset and can jump preview playback into the wrong part of the recording. Use source-space boundaries for the fallback too.

Suggested fix
-	return clampToNearestClipBoundary(roundedTimeMs, sortedClips, "timeline");
+	return clampToNearestClipBoundary(roundedTimeMs, sortedClips, "source");
// Also update `clampToNearestClipBoundary` so its source branch uses:
[getClipSourceStartMs(clip), getClipSourceEndMs(clip)]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/video-editor/types.ts` around lines 237 - 259, The fallback
currently clamps empty-space seeks to the nearest timeline boundary, which
yields incorrect source offsets when a clip's sourceStartMs differs from
startMs; update the final return to call
clampToNearestClipBoundary(roundedTimeMs, sortedClips, "source") (and modify
clampToNearestClipBoundary so its "source" branch uses
[getClipSourceStartMs(clip), getClipSourceEndMs(clip)]), ensuring
normalizeSortedClipRegions, getClipSourceStartMs, getClipSourceEndMs,
getSafeClipSpeed and the sortedClips/roundedTimeMs logic produce source-space
clamping rather than timeline-space.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/video-editor/timeline/AudioWaveform.tsx`:
- Line 3: The waveform remapping currently computes sourceTimeMs anchored to
activeClip.startMs and therefore ignores the clip's sourceStartMs, causing bars
to sample the wrong source segment; update the mapping logic (where sourceTimeMs
is computed and in the same remap used around the waveform rendering code and
the block that mirrors lines ~97-101) to include clip.sourceStartMs so
sourceTimeMs = anchor + (time - clip.startMs) + clip.sourceStartMs (or
equivalent: compute offsets entirely in source-space using sourceStartMs),
ensuring all uses of sourceTimeMs and the remap function reference the
clip.sourceStartMs anchor (e.g., in the functions/variables named sourceTimeMs,
activeClip.startMs, sourceStartMs, and the waveform remap logic).

In `@src/components/video-editor/timeline/TimelineEditor.tsx`:
- Around line 1097-1120: totalMs currently uses region.endMs (which for
trimRegions is in source-space) and then forces Math.max(sourceDurationMs,
maxRegionEndMs), causing the timeline to never shrink; change the totalMs
calculation to use timeline-space end times for all region types (convert
trimRegions.endMs from source→timeline or call the existing helper that computes
a region's timeline end) when building allRegionEnds for zoomRegions,
trimRegions, clipRegions, speedRegions, annotationRegions and audioRegions, and
only fall back to sourceDurationMs when there are no timeline-space region ends
at all (i.e., when allRegionEnds is empty).

---

Outside diff comments:
In `@src/components/video-editor/types.ts`:
- Around line 237-259: The fallback currently clamps empty-space seeks to the
nearest timeline boundary, which yields incorrect source offsets when a clip's
sourceStartMs differs from startMs; update the final return to call
clampToNearestClipBoundary(roundedTimeMs, sortedClips, "source") (and modify
clampToNearestClipBoundary so its "source" branch uses
[getClipSourceStartMs(clip), getClipSourceEndMs(clip)]), ensuring
normalizeSortedClipRegions, getClipSourceStartMs, getClipSourceEndMs,
getSafeClipSpeed and the sortedClips/roundedTimeMs logic produce source-space
clamping rather than timeline-space.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: afdb209c-5513-42b8-9cfe-8d61a744fe34

📥 Commits

Reviewing files that changed from the base of the PR and between 493c85f and aa7c426.

📒 Files selected for processing (6)
  • src/components/video-editor/VideoEditor.tsx
  • src/components/video-editor/timeline/AudioWaveform.tsx
  • src/components/video-editor/timeline/KeyframeMarkers.tsx
  • src/components/video-editor/timeline/TimelineEditor.tsx
  • src/components/video-editor/types.ts
  • src/components/video-editor/videoPlayback/videoEventHandlers.ts
💤 Files with no reviewable changes (1)
  • src/components/video-editor/timeline/KeyframeMarkers.tsx

Comment thread src/components/video-editor/timeline/AudioWaveform.tsx
Comment thread src/components/video-editor/timeline/TimelineEditor.tsx
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR aims to fix timeline/playback inconsistencies after clip speed edits by separating “source time” from “timeline time” (via a sourceStartMs anchor), updating the mapping helpers, and remapping dependent regions (zoom/annotation/audio/speed) so UI + preview stay aligned.

Changes:

  • Introduces sourceStartMs on ClipRegion and updates source↔timeline mapping utilities to use it, with additional boundary handling.
  • Improves timeline edit behavior: propagates timeline offsets after speed changes, splits zoom regions on clip split, remaps zoom timing on speed changes, and clamps seeks more defensively.
  • Updates timeline UI behavior: timeline duration/scale derived from edited layout, wheel panning is RAF-batched, and audio waveform is made clip-aware.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/components/video-editor/videoPlayback/videoEventHandlers.ts Adds guard to prevent trim-boundary seek loops.
src/components/video-editor/VideoEditor.tsx Uses source-anchored clip speed regions, remaps regions on speed edits/split, clamps seek, and displays timeline-based duration.
src/components/video-editor/types.ts Adds sourceStartMs + mapping updates (timeline↔source), plus clip→trim conversion updates.
src/components/video-editor/timeline/TimelineEditor.tsx Passes clip regions down for waveform rendering, derives total timeline bounds from regions, and batches wheel pan updates.
src/components/video-editor/timeline/KeyframeMarkers.tsx Removes marker left-transition styling while not dragging.
src/components/video-editor/timeline/AudioWaveform.tsx Makes waveform rendering aware of clip regions and speed mapping.

Comment thread src/components/video-editor/types.ts
Comment thread src/components/video-editor/timeline/AudioWaveform.tsx
Comment thread src/components/video-editor/VideoEditor.tsx Outdated
Comment thread src/components/video-editor/types.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/video-editor/timeline/TimelineEditor.tsx`:
- Around line 1093-1118: The totalMs computed in TimelineEditor is
timeline-space but the trim normalization later is incorrectly clamping
trimRegions against totalMs (timeline end), which will shorten valid
source-space trims after speed edits; change the normalization logic that clamps
trimRegions to use sourceDurationMs (the source-space upper bound computed with
Math.round(videoDuration * 1000)) instead of totalMs so trims are validated
against the original source duration; locate the totalMs and sourceDurationMs
variables and the trim normalization code that references totalMs (e.g., the
normalize pass that adjusts trimRegions) and replace its upper-bound check to
compare/round against sourceDurationMs.

In `@src/components/video-editor/VideoEditor.tsx`:
- Around line 4323-4329: The audioOffset calculation uses raw source time and
jumps across trimmed cuts; replace it with a timeline-aware elapsed time:
compute timelineElapsedMs = currentTimelineTimeMs - region.startMs (this is
timeline-relative and already excludes skipped source segments) and set
audioOffset = timelineElapsedMs / 1000 (adding any region-specific source offset
if the AudioRegion model exposes one). Change the code that computes audioOffset
(currently using currentTimeMs - regionSourceStartMs and regionSourceStartMs
from mapTimelineTimeToSourceTime) to use currentTimelineTimeMs and
region.startMs so playback stays in sync across trimmed cuts.
- Around line 3773-3784: The code is incorrectly remapping speedRegions when
applying timeline shifts; remove speedRegions from the remap (do not call
remapTimelineTime for speedRegions) so only timeline-anchored regions
(annotationRegions, audioRegions) are transformed. In the block that runs when
deltaMs !== 0 (where remapTimelineTime is defined and
setAnnotationRegions/setAudioRegions/setSpeedRegions are called), stop calling
setSpeedRegions(remapRegions) and instead keep speedRegions unchanged (or
reapply previous state) so legacy speed effects remain in source-time
coordinates used by the runtime check (currentTimeMs >= region.startMs &&
currentTimeMs < region.endMs).
- Around line 4954-4981: The export error payload is leaking local filesystem
paths via audioPath and sourceAudioFallbackPaths in the console.error call;
update the payload construction (the console.error call around the audioContext
object and the audioRegions.map) to redact local paths by replacing audioPath
with either the filename basename or null and convert sourceAudioFallbackPaths
to either an array of basenames or a count (and similarly map
sourceAudioFallbackStartDelayMsByPath keys to basenames or remove exact paths),
while preserving IDs, startMs/endMs/trackIndex/volume and effectiveSpeedRegions
for debugging; ensure no full absolute paths are emitted in the console.error
payload.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b16f5f22-0e4c-444c-962a-bfeac1f993b2

📥 Commits

Reviewing files that changed from the base of the PR and between aa7c426 and b33333a.

📒 Files selected for processing (5)
  • src/components/video-editor/VideoEditor.tsx
  • src/components/video-editor/timeline/AudioWaveform.tsx
  • src/components/video-editor/timeline/TimelineEditor.tsx
  • src/components/video-editor/types.test.ts
  • src/components/video-editor/types.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/video-editor/types.ts

Comment thread src/components/video-editor/timeline/TimelineEditor.tsx
Comment thread src/components/video-editor/VideoEditor.tsx Outdated
Comment thread src/components/video-editor/VideoEditor.tsx
Comment thread src/components/video-editor/VideoEditor.tsx
@ExtraBinoss
Copy link
Copy Markdown
Contributor Author

Close as #434 fixes it

@ExtraBinoss ExtraBinoss closed this May 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants