fix(editor): preserve full-track duration growth

This commit is contained in:
wiiiii123
2026-04-21 01:43:13 +07:00
parent 7494c90863
commit a6653a5144
3 changed files with 105 additions and 6 deletions
+27 -6
View File
@@ -155,6 +155,7 @@ import {
DEFAULT_ZOOM_OUT_DURATION_MS,
DEFAULT_ZOOM_OUT_EASING,
type EditorEffectSection,
extendAutoFullTrackClip,
type FigureData,
getClipSourceEndMs,
type PlaybackSpeed,
@@ -1507,6 +1508,8 @@ export default function VideoEditor() {
setTrimRegions(normalizedEditor.trimRegions);
setClipRegions(normalizedEditor.clipRegions);
clipInitializedRef.current = normalizedEditor.clipRegions.length > 0;
autoFullTrackClipIdRef.current = null;
autoFullTrackClipEndMsRef.current = null;
setSpeedRegions(normalizedEditor.speedRegions);
setAnnotationRegions(normalizedEditor.annotationRegions);
setAudioRegions(normalizedEditor.audioRegions);
@@ -2336,15 +2339,33 @@ export default function VideoEditor() {
// Initialize a full-track clip when duration is first known
const clipInitializedRef = useRef(false);
const autoFullTrackClipIdRef = useRef<string | null>(null);
const autoFullTrackClipEndMsRef = useRef<number | null>(null);
useEffect(() => {
const totalMs = Math.round(duration * 1000);
if (totalMs <= 0 || clipInitializedRef.current) return;
if (clipRegions.length === 0) {
const id = `clip-${nextClipIdRef.current++}`;
setClipRegions([{ id, startMs: 0, endMs: totalMs, speed: 1 }]);
if (totalMs <= 0) return;
if (!clipInitializedRef.current) {
if (clipRegions.length === 0) {
const id = `clip-${nextClipIdRef.current++}`;
autoFullTrackClipIdRef.current = id;
autoFullTrackClipEndMsRef.current = totalMs;
setClipRegions([{ id, startMs: 0, endMs: totalMs, speed: 1 }]);
}
clipInitializedRef.current = true;
return;
}
clipInitializedRef.current = true;
}, [duration, clipRegions.length]);
const extendedClipRegions = extendAutoFullTrackClip(
clipRegions,
autoFullTrackClipIdRef.current,
autoFullTrackClipEndMsRef.current,
totalMs,
);
if (!extendedClipRegions) return;
autoFullTrackClipEndMsRef.current = totalMs;
setClipRegions(extendedClipRegions);
}, [duration, clipRegions]);
// Derive trimRegions from clipRegions so export/playback pipelines stay unchanged
useEffect(() => {
+49
View File
@@ -0,0 +1,49 @@
import { describe, expect, it } from "vitest";
import { extendAutoFullTrackClip } from "./types";
describe("extendAutoFullTrackClip", () => {
it("extends the default full-track clip when metadata duration grows", () => {
expect(
extendAutoFullTrackClip(
[{ id: "clip-1", startMs: 0, endMs: 5_000, speed: 1 }],
"clip-1",
5_000,
8_000,
),
).toEqual([{ id: "clip-1", startMs: 0, endMs: 8_000, speed: 1 }]);
});
it("does not change a clip that no longer matches the auto-created shape", () => {
expect(
extendAutoFullTrackClip(
[{ id: "clip-1", startMs: 0, endMs: 4_000, speed: 1.5 }],
"clip-1",
5_000,
8_000,
),
).toBeNull();
});
it("does not change multi-clip timelines or non-growing durations", () => {
expect(
extendAutoFullTrackClip(
[
{ id: "clip-1", startMs: 0, endMs: 3_000, speed: 1 },
{ id: "clip-2", startMs: 4_000, endMs: 8_000, speed: 1 },
],
"clip-1",
8_000,
10_000,
),
).toBeNull();
expect(
extendAutoFullTrackClip(
[{ id: "clip-1", startMs: 0, endMs: 8_000, speed: 1 }],
"clip-1",
8_000,
8_000,
),
).toBeNull();
});
});
+29
View File
@@ -155,6 +155,35 @@ export function getClipSourceEndMs(clip: ClipRegion): number {
return Math.round(clip.startMs + displayDurationMs * speed);
}
export function extendAutoFullTrackClip(
clips: ClipRegion[],
autoClipId: string | null,
previousAutoEndMs: number | null,
nextTotalDurationMs: number,
): ClipRegion[] | null {
if (
!autoClipId ||
!Number.isFinite(previousAutoEndMs) ||
!Number.isFinite(nextTotalDurationMs) ||
nextTotalDurationMs <= (previousAutoEndMs ?? 0) ||
clips.length !== 1
) {
return null;
}
const [clip] = clips;
if (
clip.id !== autoClipId ||
clip.startMs !== 0 ||
clip.speed !== 1 ||
clip.endMs !== previousAutoEndMs
) {
return null;
}
return [{ ...clip, endMs: nextTotalDurationMs }];
}
/** Convert clip regions (kept segments) to trim regions (gaps to remove). */
export function clipsToTrims(clips: ClipRegion[], totalDurationMs: number): TrimRegion[] {
if (clips.length === 0) return [];