fix(timeline): include slow clip duration in range

This commit is contained in:
wiiiii123
2026-05-08 23:01:02 +07:00
parent ca9192a53c
commit b5bb128206
3 changed files with 45 additions and 4 deletions
+11 -3
View File
@@ -186,6 +186,7 @@ import {
extendAutoFullTrackClip,
type FigureData,
getClipSourceEndMs,
getTimelineDurationMs,
type Padding,
mapSourceTimeToTimelineTime as resolveSourceTimeToTimelineTime,
mapTimelineTimeToSourceTime as resolveTimelineTimeToSourceTime,
@@ -3418,6 +3419,10 @@ export default function VideoEditor() {
() => mapSourceTimeToTimelineTime(currentTime * 1000) / 1000,
[currentTime, mapSourceTimeToTimelineTime],
);
const timelineDuration = useMemo(
() => getTimelineDurationMs(clipRegions, duration * 1000) / 1000,
[clipRegions, duration],
);
// Merge clip speeds into speed regions so playback + export respect per-clip speed
const effectiveSpeedRegions = useMemo<SpeedRegion[]>(() => {
@@ -6549,14 +6554,17 @@ export default function VideoEditor() {
handleSeek(
next
? next.time / 1000
: Math.min(duration, timelinePlayheadTime + 5),
: Math.min(
timelineDuration,
timelinePlayheadTime + 5,
),
);
}}
>
<SkipForward className="w-3.5 h-3.5" weight="fill" />
</Button>
<span className="text-[10px] font-medium text-muted-foreground/70 tabular-nums ml-1">
{formatTime(duration)}
{formatTime(timelineDuration)}
</span>
</div>
</div>
@@ -6642,7 +6650,7 @@ export default function VideoEditor() {
<TimelineEditor
ref={timelineRef}
hideToolbar
videoDuration={duration}
videoDuration={timelineDuration}
currentTime={currentTime}
playheadTime={timelinePlayheadTime}
onSeek={handleSeek}
+22 -1
View File
@@ -1,13 +1,14 @@
import { describe, expect, it } from "vitest";
import { deriveNextId } from "./projectPersistence";
import {
extendAutoFullTrackClip,
findClipAtTimelineTime,
getTimelineDurationMs,
mapSourceTimeToTimelineTime,
mapTimelineTimeToSourceTime,
trimsToClips,
} from "./types";
import { deriveNextId } from "./projectPersistence";
describe("extendAutoFullTrackClip", () => {
it("extends the default full-track clip when metadata duration grows", () => {
@@ -157,3 +158,23 @@ describe("clip timeline mapping", () => {
expect(deriveNextId("clip", clipsFromTrims.map((clip) => clip.id))).toBe(4);
});
});
describe("getTimelineDurationMs", () => {
it("extends the timeline when a slow clip becomes longer than the source duration", () => {
expect(
getTimelineDurationMs(
[{ id: "clip-1", startMs: 0, endMs: 20_000, speed: 0.5 }],
10_000,
),
).toBe(20_000);
});
it("keeps the source duration when speed edits make clips shorter", () => {
expect(
getTimelineDurationMs(
[{ id: "clip-1", startMs: 0, endMs: 5_000, speed: 2 }],
10_000,
),
).toBe(10_000);
});
});
+12
View File
@@ -177,6 +177,18 @@ export function getClipSourceEndMs(clip: ClipRegion): number {
return Math.round(clip.startMs + displayDurationMs * speed);
}
export function getTimelineDurationMs(clips: ClipRegion[], sourceDurationMs: number): number {
const baseDurationMs = Math.max(0, Math.round(sourceDurationMs));
if (clips.length === 0) {
return baseDurationMs;
}
return clips.reduce(
(durationMs, clip) => Math.max(durationMs, Math.max(0, Math.round(clip.endMs))),
baseDurationMs,
);
}
export function sortClipRegions(clips: ClipRegion[]): ClipRegion[] {
return [...clips].sort((left, right) => left.startMs - right.startMs);
}