1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
|
"use client";
import { useCallback, useMemo, useRef } from "react";
import useDeepCompareEffect from "use-deep-compare-effect";
import { useEditor } from "@/hooks/use-editor";
import { useRafLoop } from "@/hooks/use-raf-loop";
import { CanvasRenderer } from "@/services/renderer/canvas-renderer";
import type { RootNode } from "@/services/renderer/nodes/root-node";
import { buildScene } from "@/services/renderer/scene-builder";
import { getLastFrameTime } from "@/lib/time";
function usePreviewSize() {
const editor = useEditor();
const activeProject = editor.project.getActive();
return {
width: activeProject?.settings.canvasSize.width,
height: activeProject?.settings.canvasSize.height,
};
}
function RenderTreeController() {
const editor = useEditor();
const tracks = editor.timeline.getTracks();
const mediaAssets = editor.media.getAssets();
const activeProject = editor.project.getActive();
const { width, height } = usePreviewSize();
useDeepCompareEffect(() => {
if (!activeProject) return;
const duration = editor.timeline.getTotalDuration();
const renderTree = buildScene({
tracks,
mediaAssets,
duration,
canvasSize: { width, height },
background: activeProject.settings.background,
});
editor.renderer.setRenderTree({ renderTree });
}, [tracks, mediaAssets, activeProject?.settings.background, width, height]);
return null;
}
export function PreviewPanel() {
return (
<div className="bg-panel relative flex h-full min-h-0 w-full min-w-0 flex-col rounded-sm">
<div className="flex min-h-0 min-w-0 flex-1 items-center justify-center p-2">
<PreviewCanvas />
<RenderTreeController />
</div>
</div>
);
}
function PreviewCanvas() {
const ref = useRef<HTMLCanvasElement>(null);
const lastFrameRef = useRef(-1);
const lastSceneRef = useRef<RootNode | null>(null);
const renderingRef = useRef(false);
const { width, height } = usePreviewSize();
const editor = useEditor();
const activeProject = editor.project.getActive();
const renderer = useMemo(() => {
return new CanvasRenderer({
width,
height,
fps: activeProject.settings.fps,
});
}, [width, height, activeProject.settings.fps]);
const renderTree = editor.renderer.getRenderTree();
const render = useCallback(() => {
if (ref.current && renderTree && !renderingRef.current) {
const time = editor.playback.getCurrentTime();
const lastFrameTime = getLastFrameTime({
duration: renderTree.duration,
fps: renderer.fps,
});
const renderTime = Math.min(time, lastFrameTime);
const frame = Math.floor(renderTime * renderer.fps);
if (
frame !== lastFrameRef.current ||
renderTree !== lastSceneRef.current
) {
renderingRef.current = true;
lastSceneRef.current = renderTree;
lastFrameRef.current = frame;
renderer
.renderToCanvas({
node: renderTree,
time: renderTime,
targetCanvas: ref.current,
})
.then(() => {
renderingRef.current = false;
});
}
}
}, [renderer, renderTree, editor.playback]);
useRafLoop(render);
return (
<canvas
ref={ref}
width={width}
height={height}
className="block max-h-full max-w-full border"
style={{
background:
activeProject.settings.background.type === "blur"
? "transparent"
: activeProject?.settings.background.color,
}}
/>
);
}
|