diff --git a/components/canvas/smart-edit/EditPreview.tsx b/components/canvas/smart-edit/EditPreview.tsx
index e1a405e1f..41751f146 100644
--- a/components/canvas/smart-edit/EditPreview.tsx
+++ b/components/canvas/smart-edit/EditPreview.tsx
@@ -19,6 +19,8 @@ export function EditPreview({
onPlaying,
onError,
fit = false,
+ cover = false,
+ externalClock = false,
selectedClipId,
selectedCaptionId,
onSelectClip,
@@ -27,6 +29,8 @@ export function EditPreview({
onCaptionPosition,
}: {
fit?: boolean;
+ cover?: boolean;
+ externalClock?: boolean;
selectedClipId?: string;
selectedCaptionId?: string;
onSelectClip?: (id: string) => void;
@@ -58,12 +62,14 @@ export function EditPreview({
}, []);
const timeline = planTimeline(plan),
visibleTime = Math.min(time * SECOND, timeline.duration - 1),
- current = timeline.clips.filter((c) => visibleTime >= c.start && visibleTime < c.end),
+ current = timeline.clips.filter(
+ (c) => visibleTime >= c.renderStart && visibleTime < c.renderEnd,
+ ),
subs = plan.subtitles
? editableCaptions(plan).filter((s) => time * SECOND >= s.start && time * SECOND < s.end)
: [];
useEffect(() => {
- if (!playing) return;
+ if (!playing || externalClock) return;
let frame = 0,
last = 0;
const tick = (now: number) => {
@@ -91,7 +97,7 @@ export function EditPreview({
cancelAnimationFrame(frame);
document.removeEventListener("visibilitychange", hide);
};
- }, [playing]);
+ }, [playing, externalClock]);
useEffect(() => {
for (const clip of timeline.clips) {
const video = videos.current.get(clip.id);
@@ -101,20 +107,28 @@ export function EditPreview({
video.pause();
continue;
}
- const target = clip.sourceIn / SECOND + (time - clip.start / SECOND) * clip.speed;
+ const target = Math.max(
+ clip.sourceIn / SECOND,
+ Math.min(
+ clip.sourceOut / SECOND - 1 / 30,
+ clip.sourceIn / SECOND + (time - clip.start / SECOND) * clip.speed,
+ ),
+ );
+ const held = time * SECOND < clip.start || time * SECOND >= clip.end;
if (Number.isFinite(target) && Math.abs(video.currentTime - target) > 0.1)
video.currentTime = target;
video.playbackRate = clip.speed;
const index = timeline.clips.findIndex((c) => c.id === clip.id),
next = timeline.clips[index + 1];
let gain = 1;
- if (clip.overlap && time * SECOND < clip.start + clip.overlap)
- gain = (time * SECOND - clip.start) / clip.overlap;
- if (next?.overlap && time * SECOND > next.start)
- gain = Math.min(gain, (clip.end - time * SECOND) / next.overlap);
- video.volume = clip.audioDetached ? 0 : Math.min(1, Math.max(0, clip.gain * gain));
- if (playing)
- void video.play().catch(() => {
+ if (clip.overlap && time * SECOND < clip.transitionStart + clip.overlap)
+ gain = (time * SECOND - clip.transitionStart) / clip.overlap;
+ if (next?.overlap && time * SECOND > next.transitionStart)
+ gain = Math.min(gain, (next.transitionStart + next.overlap - time * SECOND) / next.overlap);
+ video.volume = clip.audioDetached || held ? 0 : Math.min(1, Math.max(0, clip.gain * gain));
+ if (playing && !held)
+ void video.play().catch((error) => {
+ if (error?.name === "AbortError") return;
onPlaying(false);
onError("预览播放失败,请检查素材链接或点击播放重试");
});
@@ -135,11 +149,23 @@ export function EditPreview({
fadeIn = Math.min(clip.fadeIn, length),
fadeOut = Math.min(clip.fadeOut, length);
const gain =
- Math.min(1, fadeIn ? relative / fadeIn : 1) *
- Math.min(1, fadeOut ? (length - relative) / fadeOut : 1);
+ Math.min(
+ 1,
+ fadeIn
+ ? (clip.fadeInStart || 0) + ((1 - (clip.fadeInStart || 0)) * relative) / fadeIn
+ : 1,
+ ) *
+ Math.min(
+ 1,
+ fadeOut
+ ? (clip.fadeOutEnd || 0) +
+ ((1 - (clip.fadeOutEnd || 0)) * (length - relative)) / fadeOut
+ : 1,
+ );
audio.volume = Math.max(0, Math.min(1, clip.gain * gain));
if (playing)
- void audio.play().catch(() => {
+ void audio.play().catch((error) => {
+ if (error?.name === "AbortError") return;
onPlaying(false);
onError("独立音轨无法播放");
});
@@ -155,7 +181,8 @@ export function EditPreview({
if (Math.abs(audio.currentTime - target) > 0.15) audio.currentTime = target;
audio.volume = Math.min(1, plan.music.gain);
if (playing)
- void audio.play().catch(() => {
+ void audio.play().catch((error) => {
+ if (error?.name === "AbortError") return;
onPlaying(false);
onError("背景音乐无法播放");
});
@@ -166,8 +193,12 @@ export function EditPreview({
const ratio = ratioWidth / ratioHeight;
const height = Math.min(frameSize.height || 720, (frameSize.width || 1280) / ratio);
const scale = height / 720;
- const incomingClip = current.find((c) => c.overlap && visibleTime < c.start + c.overlap);
- const progress = incomingClip ? (visibleTime - incomingClip.start) / incomingClip.overlap : 1;
+ const incomingClip = current.find(
+ (c) => c.overlap && visibleTime < c.transitionStart + c.overlap,
+ );
+ const progress = incomingClip
+ ? (visibleTime - incomingClip.transitionStart) / incomingClip.overlap
+ : 1;
const [gesture, setGesture] = useState<{
clipId?: string;
captionId?: string;
@@ -291,7 +322,7 @@ export function EditPreview({
>
{
const asset = plan.assets.find((a) => a.id === clip.assetId)!;
const active = current.some((c) => c.id === clip.id),
- incoming = clip.overlap && visibleTime < clip.start + clip.overlap;
+ incoming = clip.overlap && visibleTime < clip.transitionStart + clip.overlap;
return (
-
-
-
-
- {transition?.duration ? transitionNames[transition.type] : "未选择转场"}
-
+
+
名称
+
+ {transition?.duration ? transitionNames[transition.type] : "未选择转场"}
-
-
- {
- if (Number.isFinite(e.target.valueAsNumber))
- setTransition(
- transition?.type || "fade",
- clamp(e.target.valueAsNumber, 0, 0.5),
- );
- }}
- />
-
-
+ 时长
+
+
setTransition(transition?.type || "fade", value)}
/>
+ {transition?.duration && transitionIndex > 0 ? (
+
+ ) : (
+ 选择片段接点和转场,预览实际效果
+ )}
+ {transition?.duration &&
+ Math.abs(timeline.clips[transitionIndex].overlap / SECOND - transition.duration) >
+ 0.04 ? (
+
+ 相邻片段较短,实际转场时长为{" "}
+ {(timeline.clips[transitionIndex].overlap / SECOND).toFixed(1)} 秒
+
+ ) : null}
@@ -1287,6 +1299,8 @@ export function SmartEditEditor({
onClick={() => {
setTransitionsOpen((v) => !v);
setPanel(transitionsOpen ? null : "transition");
+ setLoopTransition(true);
+ setPlaying(false);
}}
>
@@ -1579,11 +1593,21 @@ export function SmartEditEditor({
(transitionsOpen || c.overlap > 0) && (