From 7bf0984698e43b9aa2dd879f65951318ed410390 Mon Sep 17 00:00:00 2001 From: Maze <167211895+mazeincoding@users.noreply.github.com> Date: Sat, 31 Jan 2026 00:20:04 +0100 Subject: codebase overhaul (#697) --- apps/web/src/core/managers/renderer-manager.ts | 129 +++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 apps/web/src/core/managers/renderer-manager.ts (limited to 'apps/web/src/core/managers/renderer-manager.ts') diff --git a/apps/web/src/core/managers/renderer-manager.ts b/apps/web/src/core/managers/renderer-manager.ts new file mode 100644 index 00000000..22f1ebb8 --- /dev/null +++ b/apps/web/src/core/managers/renderer-manager.ts @@ -0,0 +1,129 @@ +import type { EditorCore } from "@/core"; +import type { RootNode } from "@/services/renderer/nodes/root-node"; +import type { ExportOptions, ExportResult } from "@/types/export"; +import { SceneExporter } from "@/services/renderer/scene-exporter"; +import { buildScene } from "@/services/renderer/scene-builder"; +import { createTimelineAudioBuffer } from "@/lib/media/audio"; + +export class RendererManager { + private renderTree: RootNode | null = null; + private listeners = new Set<() => void>(); + + constructor(private editor: EditorCore) {} + + setRenderTree({ renderTree }: { renderTree: RootNode | null }): void { + this.renderTree = renderTree; + this.notify(); + } + + getRenderTree(): RootNode | null { + return this.renderTree; + } + + async exportProject({ + options, + }: { + options: ExportOptions; + }): Promise { + const { format, quality, fps, includeAudio, onProgress, onCancel } = + options; + + try { + const tracks = this.editor.timeline.getTracks(); + const mediaAssets = this.editor.media.getAssets(); + const activeProject = this.editor.project.getActive(); + + if (!activeProject) { + return { success: false, error: "No active project" }; + } + + const duration = this.editor.timeline.getTotalDuration(); + if (duration === 0) { + return { success: false, error: "Project is empty" }; + } + + const exportFps = fps || activeProject.settings.fps; + const canvasSize = activeProject.settings.canvasSize; + + let audioBuffer: AudioBuffer | null = null; + if (includeAudio) { + onProgress?.({ progress: 0.05 }); + audioBuffer = await createTimelineAudioBuffer({ + tracks, + mediaAssets, + duration, + }); + } + + const scene = buildScene({ + tracks, + mediaAssets, + duration, + canvasSize, + background: activeProject.settings.background, + }); + + const exporter = new SceneExporter({ + width: canvasSize.width, + height: canvasSize.height, + fps: exportFps, + format, + quality, + shouldIncludeAudio: !!includeAudio, + audioBuffer: audioBuffer || undefined, + }); + + exporter.on("progress", (progress) => { + const adjustedProgress = includeAudio + ? 0.05 + progress * 0.95 + : progress; + onProgress?.({ progress: adjustedProgress }); + }); + + let cancelled = false; + const checkCancel = () => { + if (onCancel?.()) { + cancelled = true; + exporter.cancel(); + } + }; + + const cancelInterval = setInterval(checkCancel, 100); + + try { + const buffer = await exporter.export({ rootNode: scene }); + clearInterval(cancelInterval); + + if (cancelled) { + return { success: false, cancelled: true }; + } + + if (!buffer) { + return { success: false, error: "Export failed to produce buffer" }; + } + + return { + success: true, + buffer, + }; + } finally { + clearInterval(cancelInterval); + } + } catch (error) { + console.error("Export failed:", error); + return { + success: false, + error: error instanceof Error ? error.message : "Unknown export error", + }; + } + } + + subscribe(listener: () => void): () => void { + this.listeners.add(listener); + return () => this.listeners.delete(listener); + } + + private notify(): void { + this.listeners.forEach((fn) => fn()); + } +} -- cgit v1.3.1