76 lines
2.2 KiB
TypeScript
76 lines
2.2 KiB
TypeScript
"use client";
|
|
import { useEffect, useMemo, useState } from "react";
|
|
import { type EditPlan, planTimeline, SECOND } from "@/lib/video-canvas/smart-edit/contract";
|
|
import { EditPreview } from "./EditPreview";
|
|
|
|
/** Preview the selected edit boundary with the same timing/compositing used by the editor. */
|
|
export function TransitionPreview({
|
|
plan,
|
|
index,
|
|
cover = false,
|
|
}: {
|
|
plan: EditPlan;
|
|
index: number;
|
|
cover?: boolean;
|
|
}) {
|
|
const preview = useMemo(
|
|
() => ({
|
|
...plan,
|
|
subtitles: false,
|
|
music: undefined,
|
|
audioClips: [],
|
|
clips: plan.clips
|
|
.slice(index - 1, index + 1)
|
|
.map((c) => ({ ...c, gain: 0, audioDetached: true })),
|
|
}),
|
|
[plan, index],
|
|
);
|
|
const timeline = useMemo(() => planTimeline(preview), [preview]);
|
|
const boundary = timeline.clips[1];
|
|
const start = Math.max(0, boundary.transitionStart / SECOND - 0.25);
|
|
const end = Math.min(
|
|
timeline.duration / SECOND,
|
|
(boundary.transitionStart + boundary.overlap) / SECOND + 0.25,
|
|
);
|
|
const [time, setTime] = useState(start),
|
|
[error, setError] = useState(""),
|
|
[visible, setVisible] = useState(true);
|
|
useEffect(() => {
|
|
const change = () => setVisible(!document.hidden);
|
|
document.addEventListener("visibilitychange", change);
|
|
return () => document.removeEventListener("visibilitychange", change);
|
|
}, []);
|
|
useEffect(() => {
|
|
let request = 0,
|
|
epoch = 0;
|
|
setError("");
|
|
setTime(start);
|
|
const tick = (now: number) => {
|
|
if (document.hidden) epoch = 0;
|
|
else {
|
|
if (!epoch) epoch = now;
|
|
setTime(start + (((now - epoch) / 1000) % (end - start)));
|
|
}
|
|
request = requestAnimationFrame(tick);
|
|
};
|
|
request = requestAnimationFrame(tick);
|
|
return () => cancelAnimationFrame(request);
|
|
}, [start, end, preview]);
|
|
return (
|
|
<div className="se-transition-preview" aria-label="当前片段转场动态预览">
|
|
<EditPreview
|
|
plan={preview}
|
|
time={time}
|
|
playing={visible}
|
|
externalClock
|
|
onTime={setTime}
|
|
onPlaying={() => {}}
|
|
onError={setError}
|
|
fit
|
|
cover={cover}
|
|
/>
|
|
{error && <div className="se-transition-preview-error">{error}</div>}
|
|
</div>
|
|
);
|
|
}
|