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()); } }