diff options
Diffstat (limited to 'apps/web/src/components/editor/panels/preview')
| -rw-r--r-- | apps/web/src/components/editor/panels/preview/index.tsx | 124 |
1 files changed, 124 insertions, 0 deletions
diff --git a/apps/web/src/components/editor/panels/preview/index.tsx b/apps/web/src/components/editor/panels/preview/index.tsx new file mode 100644 index 00000000..5ed9db00 --- /dev/null +++ b/apps/web/src/components/editor/panels/preview/index.tsx @@ -0,0 +1,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, + }} + /> + ); +} |
