mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-28 16:55:36 +00:00
fix(editor): preserve full-track duration growth
This commit is contained in:
@@ -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(() => {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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 [];
|
||||
|
||||
Reference in New Issue
Block a user