@bendyline/squisq-video-react 1.2.2 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/VideoExportModal.tsx","../src/hooks/useVideoExport.ts","../src/mainThreadEncoder.ts","../src/workerEncoder.ts","../src/audioTrack.ts","../src/hooks/useFrameCapture.ts","../src/VideoExportButton.tsx"],"sourcesContent":["/**\n * VideoExportModal — Modal dialog for configuring and monitoring video export.\n *\n * States:\n * configure → exporting (capturing + encoding) → complete | error\n *\n * Inline styles match the site's cream/gold palette (from FileToolbar).\n */\n\nimport { useState, useCallback } from 'react';\nimport type { Doc } from '@bendyline/squisq/schemas';\nimport type { MediaProvider } from '@bendyline/squisq/schemas';\nimport type { VideoQuality, VideoOrientation } from '@bendyline/squisq-video';\nimport type { CaptionMode } from '@bendyline/squisq-react';\nimport { useVideoExport, type VideoExportConfig } from './hooks/useVideoExport.js';\n\n// ── Types ──────────────────────────────────────────────────────────\n\nexport interface VideoExportModalProps {\n /** The document to export */\n doc: Doc;\n /**\n * Player IIFE bundle source. Unused by the browser export path (frames\n * are captured from a live in-page DocPlayer); only forwarded for\n * CLI/Playwright-style pipelines that render standalone HTML.\n */\n playerScript?: string;\n /** Optional media provider for resolving images/audio */\n mediaProvider?: MediaProvider;\n /** Pre-collected images map (alternative to mediaProvider) */\n images?: Map<string, ArrayBuffer>;\n /** Pre-collected audio map */\n audio?: Map<string, ArrayBuffer>;\n /**\n * Seeds the modal's initial quality/fps/orientation/captionMode selections\n * and is merged (as a base) into the config passed to the export hook, so a\n * host can share one config shape with `useVideoExport`. The individual\n * `images`/`audio`/`mediaProvider`/`playerScript` props still take\n * precedence over any matching key here.\n */\n defaultConfig?: Partial<VideoExportConfig>;\n /** Called when the modal should close */\n onClose: () => void;\n}\n\n// ── Helpers ────────────────────────────────────────────────────\n\nfunction formatDuration(seconds: number): string {\n if (seconds < 60) return `${seconds}s`;\n const m = Math.floor(seconds / 60);\n const s = seconds % 60;\n return `${m}m ${s}s`;\n}\n\n// ── Styles ─────────────────────────────────────────────────────────\n\nconst overlayStyle: React.CSSProperties = {\n position: 'fixed',\n inset: 0,\n background: 'rgba(0, 0, 0, 0.5)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n zIndex: 10000,\n};\n\nconst modalStyle: React.CSSProperties = {\n background: '#FFFDF7',\n border: '1px solid #c9b98a',\n borderRadius: 0,\n padding: '24px 28px',\n minWidth: 380,\n maxWidth: 480,\n boxShadow: '0 8px 32px rgba(0,0,0,0.18)',\n fontFamily: 'system-ui, -apple-system, sans-serif',\n color: '#4a3c1f',\n};\n\nconst titleStyle: React.CSSProperties = {\n margin: '0 0 16px 0',\n fontSize: 18,\n fontWeight: 600,\n color: '#2d2310',\n};\n\nconst labelStyle: React.CSSProperties = {\n display: 'block',\n fontSize: 13,\n fontWeight: 500,\n marginBottom: 4,\n color: '#5a4a2a',\n};\n\nconst selectStyle: React.CSSProperties = {\n width: '100%',\n padding: '6px 8px',\n fontSize: 13,\n fontFamily: 'inherit',\n border: '1px solid #c9b98a',\n borderRadius: 0,\n background: '#fff',\n color: '#4a3c1f',\n marginBottom: 12,\n};\n\nconst btnPrimary: React.CSSProperties = {\n padding: '8px 20px',\n fontSize: 14,\n fontFamily: 'inherit',\n fontWeight: 500,\n cursor: 'pointer',\n background: '#8B6914',\n color: '#fff',\n border: '1px solid #7a5c10',\n borderRadius: 0,\n};\n\nconst btnSecondary: React.CSSProperties = {\n padding: '8px 20px',\n fontSize: 14,\n fontFamily: 'inherit',\n fontWeight: 500,\n cursor: 'pointer',\n background: '#E8DFC6',\n color: '#4a3c1f',\n border: '1px solid #c9b98a',\n borderRadius: 0,\n};\n\nconst progressBarOuterStyle: React.CSSProperties = {\n width: '100%',\n height: 8,\n background: '#E8DFC6',\n borderRadius: 0,\n overflow: 'hidden',\n marginBottom: 8,\n};\n\nconst footerStyle: React.CSSProperties = {\n display: 'flex',\n justifyContent: 'flex-end',\n gap: 8,\n marginTop: 20,\n};\n\n// ── Component ──────────────────────────────────────────────────────\n\nexport function VideoExportModal({\n doc,\n playerScript,\n mediaProvider,\n images,\n audio,\n defaultConfig,\n onClose,\n}: VideoExportModalProps) {\n const [quality, setQuality] = useState<VideoQuality>(defaultConfig?.quality ?? 'normal');\n const [fps, setFps] = useState(defaultConfig?.fps ?? 24);\n const [orientation, setOrientation] = useState<VideoOrientation>(\n defaultConfig?.orientation ?? 'landscape',\n );\n const [captionMode, setCaptionMode] = useState<CaptionMode>(defaultConfig?.captionMode ?? 'off');\n\n const exportHook = useVideoExport();\n const {\n state,\n progress,\n backend,\n downloadUrl,\n fileSize,\n audioIncluded,\n audioSkippedReason,\n error,\n elapsed,\n estimatedRemaining,\n startExport,\n cancel: cancelExport,\n reset: resetExport,\n } = exportHook;\n\n const handleExport = useCallback(async () => {\n const config: VideoExportConfig = {\n // defaultConfig is the base; explicit props/selections win over it.\n ...defaultConfig,\n quality,\n fps,\n orientation,\n captionMode,\n images,\n audio,\n mediaProvider,\n // Only thread the bundle through when the host actually supplied one.\n ...(playerScript !== undefined ? { playerScript } : {}),\n };\n await startExport(doc, config);\n }, [\n doc,\n quality,\n fps,\n orientation,\n captionMode,\n images,\n audio,\n mediaProvider,\n playerScript,\n defaultConfig,\n startExport,\n ]);\n\n const handleDownload = useCallback(() => {\n if (!downloadUrl) return;\n const a = document.createElement('a');\n a.href = downloadUrl;\n const ts = new Date().toISOString().slice(0, 10);\n a.download = `document-${ts}.mp4`;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n }, [downloadUrl]);\n\n const handleClose = useCallback(() => {\n if (state === 'capturing' || state === 'encoding' || state === 'preparing') {\n cancelExport();\n }\n resetExport();\n onClose();\n }, [state, cancelExport, resetExport, onClose]);\n\n const isExporting = state === 'preparing' || state === 'capturing' || state === 'encoding';\n\n return (\n <div style={overlayStyle} onClick={handleClose}>\n <div style={modalStyle} onClick={(e) => e.stopPropagation()}>\n <h2 style={titleStyle}>Export Video</h2>\n\n {/* ── Configure State ── */}\n {state === 'idle' && (\n <>\n <div>\n <label style={labelStyle}>Quality</label>\n <select\n style={selectStyle}\n value={quality}\n onChange={(e) => setQuality(e.target.value as VideoQuality)}\n >\n <option value=\"draft\">Draft — fast, lower quality</option>\n <option value=\"normal\">Normal — balanced</option>\n <option value=\"high\">High — best quality, slower</option>\n </select>\n </div>\n\n <div>\n <label style={labelStyle}>Frame Rate</label>\n <select\n style={selectStyle}\n value={fps}\n onChange={(e) => setFps(Number(e.target.value))}\n >\n <option value={15}>15 fps — fast export</option>\n <option value={24}>24 fps — cinematic</option>\n <option value={30}>30 fps — smooth</option>\n </select>\n </div>\n\n <div>\n <label style={labelStyle}>Orientation</label>\n <select\n style={selectStyle}\n value={orientation}\n onChange={(e) => setOrientation(e.target.value as VideoOrientation)}\n >\n <option value=\"landscape\">Landscape (1920 × 1080)</option>\n <option value=\"portrait\">Portrait (1080 × 1920)</option>\n </select>\n </div>\n\n <div>\n <label style={labelStyle}>Captions</label>\n <select\n style={selectStyle}\n value={captionMode}\n onChange={(e) => setCaptionMode(e.target.value as CaptionMode)}\n >\n <option value=\"off\">None</option>\n <option value=\"standard\">Standard (top bar)</option>\n <option value=\"social\">Social media (large words)</option>\n </select>\n </div>\n\n <div style={footerStyle}>\n <button style={btnSecondary} onClick={handleClose}>\n Cancel\n </button>\n <button style={btnPrimary} onClick={handleExport}>\n Export Video\n </button>\n </div>\n </>\n )}\n\n {/* ── Exporting State ── */}\n {isExporting && (\n <>\n {backend && (\n <p style={{ fontSize: 12, color: '#8a7a5a', margin: '0 0 8px 0' }}>\n Encoder: WebCodecs (H.264)\n </p>\n )}\n\n <div style={progressBarOuterStyle}>\n <div\n style={{\n width: `${progress}%`,\n height: '100%',\n background: '#8B6914',\n transition: 'width 0.3s ease',\n }}\n />\n </div>\n\n <p style={{ fontSize: 13, margin: '0 0 4px 0' }}>{progress}% complete</p>\n <p style={{ fontSize: 12, color: '#8a7a5a', margin: 0 }}>\n {formatDuration(elapsed)} elapsed\n {estimatedRemaining > 0 && ` · ~${formatDuration(estimatedRemaining)} remaining`}\n </p>\n\n <div style={footerStyle}>\n <button style={btnSecondary} onClick={cancelExport}>\n Cancel\n </button>\n </div>\n </>\n )}\n\n {/* ── Complete State ── */}\n {state === 'complete' && (\n <>\n <p style={{ fontSize: 14, margin: '0 0 8px 0', color: '#2d6a10' }}>Export complete!</p>\n <p style={{ fontSize: 13, color: '#5a4a2a', margin: '0 0 4px 0' }}>\n File size: {(fileSize / (1024 * 1024)).toFixed(1)} MB\n </p>\n {audioIncluded ? (\n <p style={{ fontSize: 12, color: '#2d6a10', margin: '0 0 4px 0' }}>\n Audio included ✓\n </p>\n ) : (\n <p style={{ fontSize: 12, color: '#8a7a5a', margin: '0 0 4px 0' }}>\n Video only{audioSkippedReason ? ` — ${audioSkippedReason}` : ''}\n </p>\n )}\n {backend && (\n <p style={{ fontSize: 12, color: '#8a7a5a', margin: '0 0 12px 0' }}>\n Encoded with WebCodecs (H.264)\n </p>\n )}\n\n <div style={footerStyle}>\n <button style={btnSecondary} onClick={handleClose}>\n Close\n </button>\n <button style={btnPrimary} onClick={handleDownload}>\n Download MP4\n </button>\n </div>\n </>\n )}\n\n {/* ── Error State ── */}\n {state === 'error' && (\n <>\n <p style={{ fontSize: 14, margin: '0 0 8px 0', color: '#a03020' }}>Export failed</p>\n <p\n style={{\n fontSize: 13,\n color: '#5a4a2a',\n margin: '0 0 12px 0',\n wordBreak: 'break-word',\n }}\n >\n {error}\n </p>\n\n <div style={footerStyle}>\n <button style={btnSecondary} onClick={handleClose}>\n Close\n </button>\n <button style={btnPrimary} onClick={handleExport}>\n Retry\n </button>\n </div>\n </>\n )}\n </div>\n </div>\n );\n}\n","/**\n * useVideoExport — Main orchestration hook for browser video export.\n *\n * Coordinates frame capture (hidden iframe + html2canvas) with\n * main-thread WebCodecs encoding via mp4-muxer. Manages the full\n * lifecycle: prepare → capture + encode → download.\n *\n * Encoding runs on the main thread because frame capture via html2canvas\n * (~100-200ms per frame) is the bottleneck, not encoding (~1ms per frame\n * with hardware-accelerated WebCodecs). Worker offloading would add\n * complexity with minimal benefit.\n *\n * Usage:\n * const { state, progress, phase, startExport, cancel, downloadUrl } = useVideoExport();\n * <button onClick={() => startExport(doc, options)}>Export</button>\n */\n\nimport { useState, useRef, useCallback, useEffect } from 'react';\nimport type { Doc } from '@bendyline/squisq/schemas';\nimport type { MediaProvider } from '@bendyline/squisq/schemas';\nimport type { VideoQuality, VideoOrientation, AudioTimelineClip } from '@bendyline/squisq-video';\nimport { resolveDimensions, computeAudioTimeline, QUALITY_PRESETS } from '@bendyline/squisq-video';\nimport type { CaptionMode } from '@bendyline/squisq-react';\nimport {\n createEncoder,\n supportsWebCodecs,\n supportsWebCodecsH264,\n type MainThreadEncoder,\n} from '../mainThreadEncoder.js';\nimport { createWorkerEncoder } from '../workerEncoder.js';\nimport {\n supportsWebCodecsAac,\n selectAudioTier,\n renderAudioTimeline,\n encodeAacTrack,\n audioBufferToWav,\n muxAudioWithFfmpegWasm,\n EXPORT_AUDIO_SAMPLE_RATE,\n EXPORT_AUDIO_CHANNELS,\n} from '../audioTrack.js';\nimport { useFrameCapture } from './useFrameCapture.js';\n\n// ── Audio resolution ───────────────────────────────────────────────\n\n/**\n * Resolve the raw bytes for every unique source in the audio timeline from\n * (in order) the pre-collected audio map, the images map, then the\n * MediaProvider. Sources that can't be resolved are simply omitted — the\n * caller degrades gracefully rather than failing.\n */\nasync function resolveAudioBuffers(\n clips: AudioTimelineClip[],\n sources: {\n audio?: Map<string, ArrayBuffer>;\n images?: Map<string, ArrayBuffer>;\n mediaProvider?: MediaProvider;\n },\n): Promise<Map<string, ArrayBuffer>> {\n const srcs = new Set(clips.map((c) => c.src));\n const out = new Map<string, ArrayBuffer>();\n for (const src of srcs) {\n let data = sources.audio?.get(src) ?? sources.images?.get(src);\n if (!data && sources.mediaProvider) {\n try {\n const url = await sources.mediaProvider.resolveUrl(src);\n const res = await fetch(url);\n if (res.ok) data = await res.arrayBuffer();\n } catch {\n // Unresolvable source; skip it.\n }\n }\n if (data) out.set(src, data);\n }\n return out;\n}\n\n// ── Types ──────────────────────────────────────────────────────────\n\nexport type VideoExportState =\n | 'idle'\n | 'preparing'\n | 'capturing'\n | 'encoding'\n | 'complete'\n | 'error';\n\nexport interface VideoExportConfig {\n /** Encoding quality preset (default: 'normal') */\n quality?: VideoQuality;\n /** Frames per second (default: 30) */\n fps?: number;\n /** Viewport orientation (default: 'landscape') */\n orientation?: VideoOrientation;\n /**\n * Map of relative image paths to binary data.\n * Used to embed images into the render HTML.\n */\n images?: Map<string, ArrayBuffer>;\n /**\n * Map of audio segment names to binary data.\n * Used to embed audio into the render HTML.\n */\n audio?: Map<string, ArrayBuffer>;\n /** MediaProvider to resolve media URLs (alternative to passing images directly) */\n mediaProvider?: MediaProvider;\n /** Caption mode for the exported video (default: 'off') */\n captionMode?: CaptionMode;\n /** Player IIFE bundle (unused in browser export, kept for CLI/Playwright path) */\n playerScript?: string;\n}\n\nexport interface VideoExportResult {\n /** Current export state */\n state: VideoExportState;\n /** 0–100 progress percentage */\n progress: number;\n /** Human-readable description of the current phase */\n phase: string;\n /** Video duration detected from the doc (seconds) */\n duration: number;\n /** Encoder backend ('webcodecs' when WebCodecs H.264 active, 'ffmpeg-wasm' when worker fallback active, null when idle) */\n backend: 'webcodecs' | 'ffmpeg-wasm' | null;\n /** Blob download URL (populated when state === 'complete') */\n downloadUrl: string | null;\n /** File size in bytes (populated when state === 'complete') */\n fileSize: number;\n /**\n * Whether an audio track was muxed into the exported MP4 (populated when\n * state === 'complete'). False when the doc had no audio or when audio was\n * skipped/degraded — see {@link audioSkippedReason}.\n */\n audioIncluded: boolean;\n /**\n * Why audio is absent, when `audioIncluded` is false. `null` means the doc\n * simply had no audio (not a failure); a non-null string explains a genuine\n * capability shortfall or runtime failure. Audio problems never fail the\n * export — the video always completes.\n */\n audioSkippedReason: string | null;\n /** Error message (populated when state === 'error') */\n error: string | null;\n /** Seconds elapsed since export started */\n elapsed: number;\n /** Estimated seconds remaining (0 when idle or complete) */\n estimatedRemaining: number;\n /** Start a new export */\n startExport: (doc: Doc, config: VideoExportConfig) => Promise<void>;\n /** Cancel an in-progress export */\n cancel: () => void;\n /** Reset state back to idle (e.g., after complete or error) */\n reset: () => void;\n}\n\n// ── Hook ───────────────────────────────────────────────────────────\n\nexport function useVideoExport(): VideoExportResult {\n const [state, setState] = useState<VideoExportState>('idle');\n const [progress, setProgress] = useState(0);\n const [phase, setPhase] = useState('');\n const [duration, setDuration] = useState(0);\n const [backend, setBackend] = useState<'webcodecs' | 'ffmpeg-wasm' | null>(null);\n const [downloadUrl, setDownloadUrl] = useState<string | null>(null);\n const [fileSize, setFileSize] = useState(0);\n const [audioIncluded, setAudioIncluded] = useState(false);\n const [audioSkippedReason, setAudioSkippedReason] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n const [elapsed, setElapsed] = useState(0);\n const [estimatedRemaining, setEstimatedRemaining] = useState(0);\n\n const encoderRef = useRef<MainThreadEncoder | null>(null);\n const cancelledRef = useRef(false);\n const downloadUrlRef = useRef<string | null>(null);\n const startTimeRef = useRef<number>(0);\n const elapsedTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);\n\n const frameCapture = useFrameCapture();\n\n // Clean up on unmount\n useEffect(() => {\n return () => {\n if (elapsedTimerRef.current) clearInterval(elapsedTimerRef.current);\n if (downloadUrlRef.current) {\n URL.revokeObjectURL(downloadUrlRef.current);\n }\n if (encoderRef.current) {\n encoderRef.current.close();\n }\n frameCapture.destroy();\n };\n }, [frameCapture]);\n\n const reset = useCallback(() => {\n if (downloadUrlRef.current) {\n URL.revokeObjectURL(downloadUrlRef.current);\n downloadUrlRef.current = null;\n }\n if (encoderRef.current) {\n encoderRef.current.close();\n encoderRef.current = null;\n }\n frameCapture.destroy();\n setState('idle');\n setProgress(0);\n setPhase('');\n setDuration(0);\n setBackend(null);\n setDownloadUrl(null);\n setFileSize(0);\n setAudioIncluded(false);\n setAudioSkippedReason(null);\n setError(null);\n setElapsed(0);\n setEstimatedRemaining(0);\n if (elapsedTimerRef.current) clearInterval(elapsedTimerRef.current);\n cancelledRef.current = false;\n }, [frameCapture]);\n\n const cancel = useCallback(() => {\n cancelledRef.current = true;\n if (elapsedTimerRef.current) clearInterval(elapsedTimerRef.current);\n if (encoderRef.current) {\n encoderRef.current.close();\n encoderRef.current = null;\n }\n frameCapture.destroy();\n setState('idle');\n setProgress(0);\n setPhase('Cancelled');\n }, [frameCapture]);\n\n const startExport = useCallback(\n async (doc: Doc, config: VideoExportConfig) => {\n // Clear previous state\n cancelledRef.current = false;\n if (downloadUrlRef.current) {\n URL.revokeObjectURL(downloadUrlRef.current);\n downloadUrlRef.current = null;\n }\n setDownloadUrl(null);\n setFileSize(0);\n setAudioIncluded(false);\n setAudioSkippedReason(null);\n setError(null);\n\n const quality = config.quality ?? 'normal';\n const fps = config.fps ?? 30;\n const orientation = config.orientation ?? 'landscape';\n const { width, height } = resolveDimensions({ orientation });\n\n try {\n // ── Check browser support ─────────────────────────────────\n const webCodecsAvailable = supportsWebCodecs();\n const sharedArrayBufferAvailable = typeof SharedArrayBuffer !== 'undefined';\n if (!webCodecsAvailable && !sharedArrayBufferAvailable) {\n throw new Error(\n 'No video encoder available. WebCodecs requires Chrome 94+ / Edge 94+, ' +\n 'and the ffmpeg.wasm fallback requires SharedArrayBuffer ' +\n '(Cross-Origin-Isolation headers).',\n );\n }\n\n // ── Step 1: Prepare ───────────────────────────────────────\n setState('preparing');\n setPhase('Loading document…');\n setProgress(0);\n setElapsed(0);\n setEstimatedRemaining(0);\n\n // Start elapsed timer\n startTimeRef.current = performance.now();\n if (elapsedTimerRef.current) clearInterval(elapsedTimerRef.current);\n elapsedTimerRef.current = setInterval(() => {\n setElapsed(Math.floor((performance.now() - startTimeRef.current) / 1000));\n }, 1000);\n\n // Collect images from MediaProvider if provided and images not passed directly\n let images = config.images;\n if (!images && config.mediaProvider) {\n images = new Map<string, ArrayBuffer>();\n const entries = await config.mediaProvider.listMedia();\n for (const entry of entries) {\n const url = await config.mediaProvider.resolveUrl(entry.name);\n const res = await fetch(url);\n if (res.ok) {\n const data = await res.arrayBuffer();\n images.set(entry.name, data);\n }\n }\n }\n\n const docDuration = await frameCapture.init(\n doc,\n { images, audio: config.audio, width, height },\n config.captionMode,\n );\n\n if (cancelledRef.current) return;\n\n setDuration(docDuration);\n if (docDuration <= 0) {\n throw new Error('Document has zero duration — nothing to export');\n }\n\n // ── Step 2: Create encoder ────────────────────────────────\n setPhase('Starting encoder…');\n setProgress(5);\n\n // Prefer main-thread WebCodecs (fast), but probe whether H.264\n // is actually supported. Linux Chromium has VideoEncoder but\n // no proprietary H.264 codec — fall back to the worker, which\n // loads ffmpeg.wasm in that case.\n const canUseWebCodecs =\n webCodecsAvailable && (await supportsWebCodecsH264({ width, height, fps, quality }));\n\n // ── Audio: tier selection + (best-effort) render ──────────\n // The browser frame-capture path has no cover pre-roll (Playwright\n // only), so the timeline is unshifted. Every audio operation below is\n // wrapped so a failure degrades to a silent video with a reason —\n // audio never aborts the export.\n const audioBitrate = (QUALITY_PRESETS[quality] ?? QUALITY_PRESETS.normal).audioBitrate;\n const timeline = computeAudioTimeline(doc, 0);\n const aacSupported =\n timeline.length > 0\n ? await supportsWebCodecsAac(EXPORT_AUDIO_SAMPLE_RATE, EXPORT_AUDIO_CHANNELS)\n : false;\n const tierDecision = selectAudioTier({\n hasClips: timeline.length > 0,\n aacSupported,\n sharedArrayBufferAvailable,\n canUseMainThreadWebCodecs: canUseWebCodecs,\n });\n\n let renderedAudio: AudioBuffer | null = null;\n let audioIncludedLocal = false;\n let audioReasonLocal: string | null = tierDecision.reason;\n\n if (tierDecision.tier === 1 || tierDecision.tier === 2) {\n setPhase('Preparing audio…');\n try {\n const buffers = await resolveAudioBuffers(timeline, {\n audio: config.audio,\n images,\n mediaProvider: config.mediaProvider,\n });\n if (buffers.size === 0) {\n audioReasonLocal = 'Audio files for this document could not be loaded.';\n } else {\n const totalAudioDur = timeline.reduce(\n (max, c) => Math.max(max, c.startSec + c.durationSec),\n docDuration,\n );\n renderedAudio = await renderAudioTimeline(\n timeline,\n buffers,\n totalAudioDur,\n EXPORT_AUDIO_SAMPLE_RATE,\n );\n }\n } catch (audioErr: unknown) {\n renderedAudio = null;\n audioReasonLocal = `Audio could not be prepared: ${\n audioErr instanceof Error ? audioErr.message : String(audioErr)\n }`;\n }\n }\n\n const useInlineAudio = renderedAudio !== null && tierDecision.tier === 1;\n const useFfmpegAudio = renderedAudio !== null && tierDecision.tier === 2;\n\n if (cancelledRef.current) return;\n\n let encoder: MainThreadEncoder;\n if (canUseWebCodecs) {\n encoder = createEncoder({\n width,\n height,\n fps,\n quality,\n ...(useInlineAudio && renderedAudio\n ? {\n audio: {\n numberOfChannels: renderedAudio.numberOfChannels,\n sampleRate: renderedAudio.sampleRate,\n },\n }\n : {}),\n });\n setBackend('webcodecs');\n } else if (sharedArrayBufferAvailable) {\n const workerEncoder = createWorkerEncoder({ width, height, fps, quality });\n encoder = workerEncoder;\n const selectedBackend = await workerEncoder.ready;\n setBackend(selectedBackend);\n setPhase(\n selectedBackend === 'ffmpeg-wasm'\n ? 'Starting encoder (ffmpeg.wasm)…'\n : 'Starting encoder…',\n );\n } else {\n throw new Error(\n 'WebCodecs H.264 is unavailable in this browser and the ffmpeg.wasm ' +\n 'fallback requires SharedArrayBuffer (Cross-Origin-Isolation headers).',\n );\n }\n encoderRef.current = encoder;\n\n if (cancelledRef.current) return;\n\n // ── Step 3: Capture frames and encode ─────────────────────\n setState('capturing');\n const totalFrames = Math.ceil(docDuration * fps);\n\n const captureStartTime = performance.now();\n // Throttle UI updates to every ~10 frames to avoid excessive re-renders.\n // Each setState between awaits triggers a separate render cycle.\n const UI_UPDATE_INTERVAL = 10;\n\n for (let i = 0; i < totalFrames; i++) {\n if (cancelledRef.current) return;\n\n const time = i / fps;\n\n // Update UI periodically (not every frame)\n if (i % UI_UPDATE_INTERVAL === 0 || i === totalFrames - 1) {\n const captureProgress = Math.round((i / totalFrames) * 90);\n setProgress(5 + captureProgress);\n setPhase(`Capturing frame ${i + 1}/${totalFrames} (${time.toFixed(1)}s)`);\n\n if (i > 0) {\n const elapsedCapture = (performance.now() - captureStartTime) / 1000;\n const avgPerFrame = elapsedCapture / i;\n const remaining = Math.round(avgPerFrame * (totalFrames - i));\n setEstimatedRemaining(remaining);\n }\n }\n\n const bitmap = await frameCapture.captureFrame(time);\n\n if (cancelledRef.current) {\n bitmap.close();\n return;\n }\n\n // Encode immediately — WebCodecs is fast and async internally\n encoder.encodeFrame(bitmap, i);\n }\n\n if (cancelledRef.current) return;\n\n // ── Step 4a: Inline audio (tier 1) ────────────────────────\n // Encode the rendered audio into the same muxer before finalizing.\n if (useInlineAudio && renderedAudio && encoder.addAudioChunk) {\n setState('encoding');\n setPhase('Encoding audio…');\n try {\n await encodeAacTrack(\n renderedAudio,\n { addAudioChunk: encoder.addAudioChunk.bind(encoder) },\n audioBitrate,\n );\n audioIncludedLocal = true;\n } catch (audioErr: unknown) {\n audioIncludedLocal = false;\n audioReasonLocal = `Audio encoding failed: ${\n audioErr instanceof Error ? audioErr.message : String(audioErr)\n }`;\n }\n }\n\n // ── Step 4: Finalize MP4 ──────────────────────────────────\n setState('encoding');\n setPhase('Finalizing video…');\n setProgress(95);\n\n let outputBytes: ArrayBuffer | Uint8Array = await encoder.finalize();\n encoderRef.current = null;\n\n if (cancelledRef.current) return;\n\n // ── Step 4b: ffmpeg.wasm audio mux (tier 2) ───────────────\n // Video is finalized; add the audio in a single copy-video pass.\n if (useFfmpegAudio && renderedAudio) {\n setPhase('Muxing audio…');\n try {\n const wav = audioBufferToWav(renderedAudio);\n const videoOnly =\n outputBytes instanceof Uint8Array ? outputBytes : new Uint8Array(outputBytes);\n outputBytes = await muxAudioWithFfmpegWasm(videoOnly, wav, audioBitrate);\n audioIncludedLocal = true;\n } catch (audioErr: unknown) {\n audioIncludedLocal = false;\n audioReasonLocal = `Audio muxing failed: ${\n audioErr instanceof Error ? audioErr.message : String(audioErr)\n }`;\n }\n }\n\n if (cancelledRef.current) return;\n\n // ── Step 5: Create download URL ───────────────────────────\n // Normalize to a plain ArrayBuffer-backed view for Blob (ffmpeg.wasm\n // output may be typed over SharedArrayBuffer).\n const finalBytes =\n outputBytes instanceof Uint8Array ? outputBytes.slice() : new Uint8Array(outputBytes);\n const blob = new Blob([finalBytes], { type: 'video/mp4' });\n const url = URL.createObjectURL(blob);\n downloadUrlRef.current = url;\n\n setDownloadUrl(url);\n setFileSize(finalBytes.byteLength);\n setAudioIncluded(audioIncludedLocal);\n setAudioSkippedReason(audioIncludedLocal ? null : audioReasonLocal);\n setState('complete');\n setProgress(100);\n setPhase('Export complete');\n setEstimatedRemaining(0);\n if (elapsedTimerRef.current) clearInterval(elapsedTimerRef.current);\n\n // Clean up\n frameCapture.destroy();\n } catch (err: unknown) {\n if (elapsedTimerRef.current) clearInterval(elapsedTimerRef.current);\n if (cancelledRef.current) return;\n const message = err instanceof Error ? err.message : String(err);\n setState('error');\n setError(message);\n setPhase('Export failed');\n\n // Clean up on error\n if (encoderRef.current) {\n encoderRef.current.close();\n encoderRef.current = null;\n }\n frameCapture.destroy();\n }\n },\n [frameCapture],\n );\n\n return {\n state,\n progress,\n phase,\n duration,\n backend,\n downloadUrl,\n fileSize,\n audioIncluded,\n audioSkippedReason,\n error,\n elapsed,\n estimatedRemaining,\n startExport,\n cancel,\n reset,\n };\n}\n","/**\n * Main-thread WebCodecs encoder.\n *\n * Encodes video frames to MP4 using the WebCodecs API and mp4-muxer,\n * running directly on the main thread. This is simpler and avoids\n * worker module-resolution issues with bundlers. Since frame capture\n * via html2canvas (~100-200ms per frame) is the bottleneck — not\n * encoding (~1ms per frame with hardware-accelerated WebCodecs) —\n * worker offloading provides minimal benefit.\n *\n * Requirements: Chrome 94+ / Edge 94+ (WebCodecs support).\n */\n\nimport { bitrateForQuality } from '@bendyline/squisq-video';\n\nimport { createMp4Muxer, type Mp4MuxerHandle } from './mp4Mux.js';\n\nexport interface EncoderConfig {\n width: number;\n height: number;\n fps: number;\n quality: 'draft' | 'normal' | 'high';\n /**\n * When present, the underlying muxer is configured with an AAC audio track\n * and {@link MainThreadEncoder.addAudioChunk} becomes usable. Absent → the\n * encoder produces a video-only MP4 exactly as before.\n */\n audio?: {\n numberOfChannels: number;\n sampleRate: number;\n };\n}\n\nexport interface MainThreadEncoder {\n /** Encode a single frame. The bitmap is closed after encoding. */\n encodeFrame(bitmap: ImageBitmap, frameIndex: number): void;\n /**\n * Hand an encoded audio chunk (from a WebCodecs `AudioEncoder`) to the muxer.\n * Only valid when the encoder was created with an `audio` config; otherwise a\n * no-op. Must be called before {@link finalize}.\n */\n addAudioChunk?(chunk: EncodedAudioChunk, meta?: EncodedAudioChunkMetadata): void;\n /** Flush pending frames and finalize the MP4. Returns the MP4 ArrayBuffer. */\n finalize(): Promise<ArrayBuffer>;\n /** Close the encoder without producing output (e.g., on cancel). */\n close(): void;\n}\n\n/**\n * Check whether the browser supports WebCodecs video encoding.\n */\nexport function supportsWebCodecs(): boolean {\n return typeof VideoEncoder !== 'undefined' && typeof VideoFrame !== 'undefined';\n}\n\n/**\n * Probe whether the WebCodecs encoder actually supports the H.264 profile\n * we use. The `VideoEncoder` global can exist while the specific codec is\n * unavailable — this is the case on Linux Chromium, which ships without\n * the proprietary H.264 encoder.\n */\nexport async function supportsWebCodecsH264(config: EncoderConfig): Promise<boolean> {\n if (!supportsWebCodecs()) return false;\n try {\n const support = await VideoEncoder.isConfigSupported({\n codec: 'avc1.640028',\n width: config.width,\n height: config.height,\n bitrate: bitrateForQuality(config.quality, config.width, config.height),\n framerate: config.fps,\n });\n return support.supported === true;\n } catch {\n return false;\n }\n}\n\n/**\n * Create a main-thread WebCodecs encoder.\n *\n * Throws if WebCodecs is not available.\n */\nexport function createEncoder(config: EncoderConfig): MainThreadEncoder {\n if (!supportsWebCodecs()) {\n throw new Error(\n 'WebCodecs is not available in this browser. ' +\n 'Video export requires Chrome 94+, Edge 94+, or another Chromium-based browser.',\n );\n }\n\n if (!config.fps || config.fps <= 0 || !config.width || !config.height) {\n throw new Error(\n `Invalid encoder config: fps=${config.fps}, width=${config.width}, height=${config.height}`,\n );\n }\n\n const muxer: Mp4MuxerHandle = createMp4Muxer({\n width: config.width,\n height: config.height,\n fps: config.fps,\n ...(config.audio ? { audio: config.audio } : {}),\n });\n\n let closed = false;\n const frameDuration = 1_000_000 / config.fps; // microseconds per frame\n\n const encoder = new VideoEncoder({\n output(chunk, meta) {\n if (closed) return;\n muxer.addVideoChunk(chunk, meta ?? undefined);\n },\n error(err) {\n console.error('WebCodecs encoder error:', err.message);\n },\n });\n\n encoder.configure({\n // Deliberate profile split from the fallback worker (avc1.42001f, Baseline):\n // this primary WebCodecs path targets H.264 High@4.0 for better quality up\n // to 1080p; the wasm-fallback worker uses Baseline for max decoder compat.\n codec: 'avc1.640028', // H.264 High profile, level 4.0 (supports up to 1080p)\n width: config.width,\n height: config.height,\n bitrate: bitrateForQuality(config.quality, config.width, config.height),\n framerate: config.fps,\n });\n\n return {\n encodeFrame(bitmap: ImageBitmap, frameIndex: number) {\n if (closed) {\n bitmap.close();\n return;\n }\n const timestamp = Math.round(frameIndex * frameDuration);\n const frame = new VideoFrame(bitmap, { timestamp });\n const keyFrame = frameIndex % 30 === 0;\n encoder.encode(frame, { keyFrame });\n frame.close();\n bitmap.close();\n },\n\n addAudioChunk(chunk: EncodedAudioChunk, meta?: EncodedAudioChunkMetadata) {\n if (closed || !muxer.hasAudioTrack) return;\n muxer.addAudioChunk(chunk, meta);\n },\n\n async finalize(): Promise<ArrayBuffer> {\n if (closed) throw new Error('Encoder already closed');\n await encoder.flush();\n encoder.close();\n closed = true;\n return muxer.finalize();\n },\n\n close() {\n if (closed) return;\n closed = true;\n if (encoder.state !== 'closed') {\n encoder.close();\n }\n },\n };\n}\n","/**\n * Worker-backed video encoder.\n *\n * Spawns `workers/encode.worker.ts` and exposes the same shape as\n * `MainThreadEncoder` so `useVideoExport` can treat both backends\n * uniformly. Used as a fallback when WebCodecs H.264 isn't supported\n * (typical on Linux Chromium): the worker auto-selects its ffmpeg.wasm\n * path in that case.\n */\n\nimport type { MainThreadEncoder, EncoderConfig } from './mainThreadEncoder.js';\nimport type { MainToWorkerMessage, WorkerToMainMessage } from './workers/workerTypes.js';\n\n/** Resolves once the worker has reported which backend it picked. */\nexport interface WorkerEncoder extends MainThreadEncoder {\n /** Backend the worker selected ('webcodecs' or 'ffmpeg-wasm'). */\n readonly ready: Promise<'webcodecs' | 'ffmpeg-wasm'>;\n}\n\nexport function createWorkerEncoder(config: EncoderConfig): WorkerEncoder {\n if (!config.fps || config.fps <= 0 || !config.width || !config.height) {\n throw new Error(\n `Invalid encoder config: fps=${config.fps}, width=${config.width}, height=${config.height}`,\n );\n }\n\n const worker = new Worker(new URL('./workers/encode.worker.js', import.meta.url), {\n type: 'module',\n });\n\n let closed = false;\n let fatalError: Error | null = null;\n let finalizeResolve: ((buffer: ArrayBuffer) => void) | null = null;\n let finalizeReject: ((err: Error) => void) | null = null;\n let readyResolve: ((backend: 'webcodecs' | 'ffmpeg-wasm') => void) | null = null;\n let readyReject: ((err: Error) => void) | null = null;\n\n const ready = new Promise<'webcodecs' | 'ffmpeg-wasm'>((resolve, reject) => {\n readyResolve = resolve;\n readyReject = reject;\n });\n\n const frameDuration = 1_000_000 / config.fps; // microseconds per frame\n\n function post(msg: MainToWorkerMessage, transfer?: Transferable[]) {\n worker.postMessage(msg, transfer ?? []);\n }\n\n worker.onmessage = (event: MessageEvent<WorkerToMainMessage>) => {\n const msg = event.data;\n switch (msg.type) {\n case 'capabilities':\n readyResolve?.(msg.backend);\n readyResolve = readyReject = null;\n break;\n case 'complete':\n finalizeResolve?.(msg.data);\n finalizeResolve = finalizeReject = null;\n worker.terminate();\n break;\n case 'error': {\n const err = new Error(msg.message);\n fatalError = err;\n readyReject?.(err);\n finalizeReject?.(err);\n readyResolve = readyReject = null;\n finalizeResolve = finalizeReject = null;\n worker.terminate();\n break;\n }\n // 'progress' is informational; ignored at this layer\n }\n };\n\n worker.onerror = (event) => {\n const err = new Error(event.message || 'Worker error');\n fatalError = err;\n readyReject?.(err);\n finalizeReject?.(err);\n readyResolve = readyReject = null;\n finalizeResolve = finalizeReject = null;\n worker.terminate();\n };\n\n post({\n type: 'init',\n width: config.width,\n height: config.height,\n fps: config.fps,\n quality: config.quality,\n });\n\n return {\n ready,\n\n encodeFrame(bitmap: ImageBitmap, frameIndex: number) {\n if (closed || fatalError) {\n bitmap.close();\n return;\n }\n const timestamp = Math.round(frameIndex * frameDuration);\n post({ type: 'frame', bitmap, frameIndex, timestamp }, [bitmap]);\n },\n\n async finalize(): Promise<ArrayBuffer> {\n if (closed) throw new Error('Encoder already closed');\n if (fatalError) throw fatalError;\n closed = true;\n return new Promise<ArrayBuffer>((resolve, reject) => {\n finalizeResolve = resolve;\n finalizeReject = reject;\n post({ type: 'finalize' });\n });\n },\n\n close() {\n if (closed) return;\n closed = true;\n post({ type: 'cancel' });\n worker.terminate();\n },\n };\n}\n","/**\n * audioTrack — In-browser audio rendering + AAC encoding for MP4 export.\n *\n * Replaces the CLI's ffmpeg `adelay`/`atrim`/`amix` graph with browser-native\n * primitives:\n *\n * - {@link supportsWebCodecsAac} probes whether the browser can encode AAC\n * (WebCodecs `AudioEncoder`).\n * - {@link renderAudioTimeline} mixes an {@link AudioTimelineClip} list into a\n * single `AudioBuffer` using an `OfflineAudioContext` (decode → schedule →\n * render).\n * - {@link encodeAacTrack} feeds that buffer through a WebCodecs\n * `AudioEncoder` and hands the chunks to an mp4-muxer audio track (tier 1).\n * - {@link audioBufferToWav} + {@link muxAudioWithFfmpegWasm} cover the\n * ffmpeg.wasm fallback (tier 2).\n * - {@link selectAudioTier} is the pure capability→tier decision shared with\n * `useVideoExport` (and unit-tested at the module boundary).\n */\n\nimport type { AudioTimelineClip } from '@bendyline/squisq-video';\n\n/** Sample rate the export renders + encodes audio at. */\nexport const EXPORT_AUDIO_SAMPLE_RATE = 48_000;\n/** Channel count the export renders + encodes audio at (stereo). */\nexport const EXPORT_AUDIO_CHANNELS = 2;\n\n/** Tier-3 reason used when AAC encoding is impossible and no ffmpeg fallback. */\nexport const REASON_NO_AAC_NO_SAB =\n 'This browser cannot encode AAC audio (WebCodecs AudioEncoder unavailable) ' +\n 'and the ffmpeg.wasm fallback requires cross-origin isolation (SharedArrayBuffer).';\n\n// ── Capability probe ───────────────────────────────────────────────\n\n/**\n * Whether the browser can encode AAC (`mp4a.40.2`) via WebCodecs.\n * Returns false when `AudioEncoder` is undefined (e.g. Node/jsdom, Safari,\n * older Chromium) or the config is unsupported.\n */\nexport async function supportsWebCodecsAac(\n sampleRate: number = EXPORT_AUDIO_SAMPLE_RATE,\n channels: number = EXPORT_AUDIO_CHANNELS,\n): Promise<boolean> {\n if (typeof AudioEncoder === 'undefined') return false;\n try {\n const support = await AudioEncoder.isConfigSupported({\n codec: 'mp4a.40.2',\n sampleRate,\n numberOfChannels: channels,\n bitrate: 128_000,\n });\n return support.supported === true;\n } catch {\n return false;\n }\n}\n\n// ── Tier selection (pure) ──────────────────────────────────────────\n\nexport interface AudioTierInputs {\n /** Whether the doc has any audio timeline clips to include at all. */\n hasClips: boolean;\n /** Whether WebCodecs AAC encoding is available. */\n aacSupported: boolean;\n /** Whether `SharedArrayBuffer` (ffmpeg.wasm path) is available. */\n sharedArrayBufferAvailable: boolean;\n /** Whether the main-thread WebCodecs H.264 encoder is in use (tier-1 host). */\n canUseMainThreadWebCodecs: boolean;\n}\n\nexport interface AudioTierDecision {\n /**\n * 1 → inline AAC into the video muxer.\n * 2 → finalize video-only then ffmpeg.wasm mux pass.\n * 3 → video-only (no audio).\n */\n tier: 1 | 2 | 3;\n /**\n * Reason audio will be absent, when tier 3. `null` means \"there was simply\n * no audio to include\" (not a failure). Non-null for a genuine capability\n * shortfall. Always `null` for tiers 1 and 2 (the attempt is expected to\n * succeed; the caller downgrades with a fresh reason on runtime failure).\n */\n reason: string | null;\n}\n\n/**\n * Decide which audio tier to run from capability booleans. Pure — the single\n * source of truth for tier selection, shared with `useVideoExport`.\n */\nexport function selectAudioTier(inputs: AudioTierInputs): AudioTierDecision {\n if (!inputs.hasClips) {\n return { tier: 3, reason: null };\n }\n if (inputs.aacSupported && inputs.canUseMainThreadWebCodecs) {\n return { tier: 1, reason: null };\n }\n if (inputs.sharedArrayBufferAvailable) {\n return { tier: 2, reason: null };\n }\n return { tier: 3, reason: REASON_NO_AAC_NO_SAB };\n}\n\n// ── OfflineAudioContext rendering ──────────────────────────────────\n\ntype OfflineCtor = new (\n numberOfChannels: number,\n length: number,\n sampleRate: number,\n) => OfflineAudioContext;\n\nfunction resolveOfflineAudioContext(): OfflineCtor {\n const g = globalThis as unknown as {\n OfflineAudioContext?: OfflineCtor;\n webkitOfflineAudioContext?: OfflineCtor;\n };\n const ctor = g.OfflineAudioContext ?? g.webkitOfflineAudioContext;\n if (!ctor) {\n throw new Error('OfflineAudioContext is not available in this environment.');\n }\n return ctor;\n}\n\n/**\n * Mix a list of timeline clips into one `AudioBuffer`.\n *\n * Decodes each unique source once, then schedules an `AudioBufferSourceNode`\n * per clip at its `startSec` with the trim window (`offset = sourceInSec`,\n * `duration = durationSec`). Undecodable sources are skipped rather than\n * failing the whole render.\n */\nexport async function renderAudioTimeline(\n clips: AudioTimelineClip[],\n buffers: Map<string, ArrayBuffer>,\n totalDurationSec: number,\n sampleRate: number = EXPORT_AUDIO_SAMPLE_RATE,\n): Promise<AudioBuffer> {\n const Ctor = resolveOfflineAudioContext();\n const channels = EXPORT_AUDIO_CHANNELS;\n const length = Math.max(1, Math.ceil(totalDurationSec * sampleRate));\n const ctx = new Ctor(channels, length, sampleRate);\n\n // Decode each unique source once. decodeAudioData detaches the buffer, so\n // hand it a copy to keep the caller's ArrayBuffer reusable.\n const decoded = new Map<string, AudioBuffer>();\n for (const [src, data] of buffers) {\n try {\n decoded.set(src, await ctx.decodeAudioData(data.slice(0)));\n } catch {\n // Skip undecodable sources; the clip is simply omitted.\n }\n }\n\n for (const clip of clips) {\n const buffer = decoded.get(clip.src);\n if (!buffer) continue;\n const node = ctx.createBufferSource();\n node.buffer = buffer;\n node.connect(ctx.destination);\n const when = Math.max(0, clip.startSec);\n const offset = Math.max(0, clip.sourceInSec);\n const duration = Math.max(0, clip.durationSec);\n node.start(when, offset, duration);\n }\n\n return ctx.startRendering();\n}\n\n// ── WebCodecs AAC encode (tier 1) ──────────────────────────────────\n\n/** Minimal muxer surface {@link encodeAacTrack} needs. */\nexport interface AudioChunkSink {\n addAudioChunk(chunk: EncodedAudioChunk, meta?: EncodedAudioChunkMetadata): void;\n}\n\n/**\n * Encode an `AudioBuffer` to AAC via WebCodecs and feed the chunks to `sink`.\n * Resolves once every chunk has been flushed to the muxer. Throws only if the\n * `AudioEncoder` errors — the caller wraps this so audio failure never aborts\n * the video export.\n */\nexport async function encodeAacTrack(\n audioBuffer: AudioBuffer,\n sink: AudioChunkSink,\n bitrate: number,\n): Promise<void> {\n if (typeof AudioEncoder === 'undefined' || typeof AudioData === 'undefined') {\n throw new Error('WebCodecs AudioEncoder is not available.');\n }\n\n const sampleRate = audioBuffer.sampleRate;\n const channels = audioBuffer.numberOfChannels;\n\n let encodeError: Error | null = null;\n const encoder = new AudioEncoder({\n output: (chunk, meta) => sink.addAudioChunk(chunk, meta ?? undefined),\n error: (err) => {\n encodeError = err instanceof Error ? err : new Error(String(err));\n },\n });\n\n encoder.configure({ codec: 'mp4a.40.2', sampleRate, numberOfChannels: channels, bitrate });\n\n const FRAME = 1024; // samples per AudioData chunk\n const total = audioBuffer.length;\n const channelData: Float32Array[] = [];\n for (let ch = 0; ch < channels; ch++) {\n channelData.push(audioBuffer.getChannelData(ch));\n }\n\n for (let offset = 0; offset < total; offset += FRAME) {\n if (encodeError) break;\n const count = Math.min(FRAME, total - offset);\n // Planar layout: all of channel 0, then all of channel 1, …\n const planar = new Float32Array(count * channels);\n for (let ch = 0; ch < channels; ch++) {\n planar.set(channelData[ch].subarray(offset, offset + count), ch * count);\n }\n const timestamp = Math.round((offset / sampleRate) * 1_000_000);\n const audioData = new AudioData({\n format: 'f32-planar',\n sampleRate,\n numberOfFrames: count,\n numberOfChannels: channels,\n timestamp,\n data: planar,\n });\n encoder.encode(audioData);\n audioData.close();\n }\n\n await encoder.flush();\n encoder.close();\n if (encodeError) throw encodeError;\n}\n\n// ── WAV serialization (tier 2 input) ───────────────────────────────\n\n/**\n * Serialize an `AudioBuffer` to a 16-bit PCM WAV byte array. Used to hand the\n * rendered audio to the ffmpeg.wasm fallback for muxing.\n */\nexport function audioBufferToWav(buffer: AudioBuffer): Uint8Array {\n const channels = buffer.numberOfChannels;\n const sampleRate = buffer.sampleRate;\n const frames = buffer.length;\n const bytesPerSample = 2;\n const blockAlign = channels * bytesPerSample;\n const dataSize = frames * blockAlign;\n const out = new ArrayBuffer(44 + dataSize);\n const view = new DataView(out);\n\n const writeStr = (offset: number, str: string) => {\n for (let i = 0; i < str.length; i++) view.setUint8(offset + i, str.charCodeAt(i));\n };\n\n writeStr(0, 'RIFF');\n view.setUint32(4, 36 + dataSize, true);\n writeStr(8, 'WAVE');\n writeStr(12, 'fmt ');\n view.setUint32(16, 16, true); // PCM chunk size\n view.setUint16(20, 1, true); // PCM format\n view.setUint16(22, channels, true);\n view.setUint32(24, sampleRate, true);\n view.setUint32(28, sampleRate * blockAlign, true); // byte rate\n view.setUint16(32, blockAlign, true);\n view.setUint16(34, bytesPerSample * 8, true);\n writeStr(36, 'data');\n view.setUint32(40, dataSize, true);\n\n const channelData: Float32Array[] = [];\n for (let ch = 0; ch < channels; ch++) channelData.push(buffer.getChannelData(ch));\n\n let pos = 44;\n for (let i = 0; i < frames; i++) {\n for (let ch = 0; ch < channels; ch++) {\n let sample = channelData[ch][i];\n sample = Math.max(-1, Math.min(1, sample));\n view.setInt16(pos, sample < 0 ? sample * 0x8000 : sample * 0x7fff, true);\n pos += 2;\n }\n }\n\n return new Uint8Array(out);\n}\n\n// ── ffmpeg.wasm mux fallback (tier 2) ──────────────────────────────\n\n/**\n * Mux a pre-encoded video-only MP4 with a WAV audio track in a single\n * ffmpeg.wasm pass (`-c:v copy -c:a aac -shortest`). Requires\n * `SharedArrayBuffer` (cross-origin isolation). Returns the muxed MP4 bytes.\n */\nexport async function muxAudioWithFfmpegWasm(\n videoMp4: Uint8Array,\n wav: Uint8Array,\n audioBitrate: number,\n): Promise<Uint8Array> {\n const { FFmpeg } = await import('@ffmpeg/ffmpeg');\n const ffmpeg = new FFmpeg();\n await ffmpeg.load();\n try {\n await ffmpeg.writeFile('video.mp4', videoMp4);\n await ffmpeg.writeFile('audio.wav', wav);\n await ffmpeg.exec([\n '-i',\n 'video.mp4',\n '-i',\n 'audio.wav',\n '-c:v',\n 'copy',\n '-c:a',\n 'aac',\n '-b:a',\n String(audioBitrate),\n '-shortest',\n 'out.mp4',\n ]);\n const data = await ffmpeg.readFile('out.mp4');\n return data instanceof Uint8Array ? data : new TextEncoder().encode(data as string);\n } finally {\n ffmpeg.terminate();\n }\n}\n","/**\n * useFrameCapture — Hidden div + html2canvas frame capture.\n *\n * Mounts a DocPlayer in renderMode inside a hidden div (same document),\n * then captures individual frames by seeking the player and rendering\n * the DOM to a canvas via html2canvas.\n *\n * Uses React directly — no script injection, no iframes, no eval.\n *\n * Returns an ImageBitmap for each frame (transferable to a Worker).\n */\n\nimport { createElement } from 'react';\nimport { createRoot, type Root } from 'react-dom/client';\nimport { useRef, useCallback, useMemo } from 'react';\nimport type { Doc, MediaProvider } from '@bendyline/squisq/schemas';\nimport type { RenderHtmlOptions } from '@bendyline/squisq-video';\nimport { DocPlayer, MediaContext } from '@bendyline/squisq-react';\nimport type { SquisqWindow, CaptionMode, CaptionStyle } from '@bendyline/squisq-react';\nimport html2canvas from 'html2canvas';\n\nexport interface FrameCaptureHandle {\n /** Initialize the hidden player. Returns the video duration in seconds. */\n init: (\n doc: Doc,\n renderOptions: Omit<RenderHtmlOptions, 'playerScript'>,\n captionMode?: CaptionMode,\n ) => Promise<number>;\n /** Capture a single frame at the given time (seconds). Returns an ImageBitmap. */\n captureFrame: (time: number) => Promise<ImageBitmap>;\n /** Clean up resources. */\n destroy: () => void;\n}\n\n/** Extension → MIME type map (hoisted to avoid per-image allocation). */\nconst MIME_MAP: Record<string, string> = {\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n png: 'image/png',\n gif: 'image/gif',\n webp: 'image/webp',\n svg: 'image/svg+xml',\n bmp: 'image/bmp',\n avif: 'image/avif',\n};\n\n/** Convert an ArrayBuffer to a base64 data URI using chunked encoding (O(n)). */\nfunction arrayBufferToDataUrl(buffer: ArrayBuffer, mime: string): string {\n const bytes = new Uint8Array(buffer);\n const chunks: string[] = [];\n const CHUNK = 8192;\n for (let i = 0; i < bytes.length; i += CHUNK) {\n chunks.push(String.fromCharCode(...bytes.subarray(i, i + CHUNK)));\n }\n return `data:${mime};base64,${btoa(chunks.join(''))}`;\n}\n\n/**\n * Create an inline MediaProvider from a map of paths to ArrayBuffers.\n */\nfunction createInlineProvider(images: Map<string, ArrayBuffer>): MediaProvider {\n const dataUrls = new Map<string, string>();\n const mimeTypes = new Map<string, string>();\n for (const [path, buffer] of images) {\n const ext = path.split('.').pop()?.toLowerCase() ?? '';\n const mime = MIME_MAP[ext] ?? 'application/octet-stream';\n dataUrls.set(path, arrayBufferToDataUrl(buffer, mime));\n mimeTypes.set(path, mime);\n }\n\n return {\n async resolveUrl(relativePath: string): Promise<string> {\n return dataUrls.get(relativePath) ?? relativePath;\n },\n async listMedia() {\n return [...dataUrls.keys()].map((name) => ({\n name,\n mimeType: mimeTypes.get(name) ?? 'application/octet-stream',\n size: 0,\n }));\n },\n async addMedia() {\n throw new Error('Read-only');\n },\n async removeMedia() {\n throw new Error('Read-only');\n },\n dispose() {},\n };\n}\n\n/**\n * Hook that manages a hidden div for frame capture.\n */\nexport function useFrameCapture(): FrameCaptureHandle {\n const containerRef = useRef<HTMLDivElement | null>(null);\n const rootRef = useRef<Root | null>(null);\n const dimensionsRef = useRef<{ width: number; height: number }>({ width: 1920, height: 1080 });\n\n const init = useCallback(\n async (\n doc: Doc,\n renderOptions: Omit<RenderHtmlOptions, 'playerScript'>,\n captionMode?: CaptionMode,\n ): Promise<number> => {\n // Clean up any existing container.\n // Defer unmount to avoid \"synchronously unmount a root while React\n // was already rendering\" when init() is called from a React handler.\n if (rootRef.current || containerRef.current) {\n const oldRoot = rootRef.current;\n const oldContainer = containerRef.current;\n rootRef.current = null;\n containerRef.current = null;\n await new Promise<void>((resolve) => {\n setTimeout(() => {\n if (oldRoot) oldRoot.unmount();\n if (oldContainer) oldContainer.remove();\n resolve();\n }, 0);\n });\n }\n\n const width = renderOptions.width ?? 1920;\n const height = renderOptions.height ?? 1080;\n dimensionsRef.current = { width, height };\n\n // Create a hidden container\n const container = document.createElement('div');\n container.style.cssText =\n `position:fixed;left:0;top:0;width:${width}px;height:${height}px;` +\n 'opacity:0;pointer-events:none;z-index:-1;overflow:hidden;';\n document.body.appendChild(container);\n containerRef.current = container;\n\n // Create render root\n const renderRoot = document.createElement('div');\n renderRoot.id = 'squisq-capture-root';\n renderRoot.style.cssText = `width:${width}px;height:${height}px;`;\n container.appendChild(renderRoot);\n\n // Build media provider from images\n const mediaProvider = renderOptions.images\n ? createInlineProvider(renderOptions.images)\n : null;\n\n // Mount DocPlayer in renderMode via React\n const root = createRoot(renderRoot);\n rootRef.current = root;\n\n // Derive caption props from captionMode\n const captionsEnabled = captionMode !== undefined && captionMode !== 'off';\n const captionStyle: CaptionStyle = captionMode === 'social' ? 'social' : 'standard';\n\n const playerElement = createElement(DocPlayer, {\n doc,\n basePath: '.',\n renderMode: true,\n showControls: false,\n autoPlay: false,\n forceViewport: { width, height, name: 'export' },\n captionsEnabled,\n captionStyle,\n });\n\n // Defer rendering to the next microtask to avoid \"synchronously unmount\n // a root while React was already rendering\" when init() is called during\n // a React render cycle (e.g., from startExport in VideoExportModal).\n await new Promise<void>((resolve) => setTimeout(resolve, 0));\n\n if (mediaProvider) {\n root.render(createElement(MediaContext.Provider, { value: mediaProvider }, playerElement));\n } else {\n root.render(playerElement);\n }\n\n // Wait for the render API to appear on window\n return new Promise<number>((resolve, reject) => {\n const timeout = setTimeout(() => {\n const w = window as SquisqWindow;\n const hasSeek = typeof w.seekTo === 'function';\n const hasDur = typeof w.getDuration === 'function';\n const rootEl = containerRef.current?.querySelector('#squisq-capture-root');\n const hasPlayer = rootEl ? rootEl.querySelector('.doc-player') !== null : false;\n reject(\n new Error(\n `Render API did not initialize within 15s. ` +\n `seekTo=${hasSeek}, getDuration=${hasDur}, player=${hasPlayer}, root=${!!rootEl}`,\n ),\n );\n }, 15000);\n\n const checkApi = () => {\n const w = window as SquisqWindow;\n if (typeof w.getDuration === 'function' && typeof w.seekTo === 'function') {\n clearTimeout(timeout);\n const duration = w.getDuration();\n resolve(duration);\n } else {\n requestAnimationFrame(checkApi);\n }\n };\n\n // Give React time to mount and run useEffects\n setTimeout(checkApi, 500);\n });\n },\n [],\n );\n\n const captureFrame = useCallback(async (time: number): Promise<ImageBitmap> => {\n const container = containerRef.current;\n const w = window as SquisqWindow;\n if (!container || typeof w.seekTo !== 'function') {\n throw new Error('Frame capture not initialized — call init() first');\n }\n\n const { width, height } = dimensionsRef.current;\n\n // Seek the player to the target time\n await w.seekTo(time);\n\n // Wait for the DOM to update after seek\n await new Promise<void>((resolve) =>\n requestAnimationFrame(() => requestAnimationFrame(() => resolve())),\n );\n\n const root = container.querySelector('#squisq-capture-root') as HTMLElement;\n if (!root) {\n throw new Error('Capture root element not found');\n }\n\n // Render the DOM to a canvas via html2canvas.\n // We're in the same document (no iframe), so COEP doesn't block cloning.\n const canvas = await html2canvas(root, {\n width,\n height,\n scale: 1,\n useCORS: true,\n allowTaint: true,\n backgroundColor: '#000000',\n logging: false,\n });\n\n // Convert to ImageBitmap (transferable to worker — zero-copy)\n const bitmap = await createImageBitmap(canvas);\n return bitmap;\n }, []);\n\n const destroy = useCallback(() => {\n if (rootRef.current) {\n rootRef.current.unmount();\n rootRef.current = null;\n }\n if (containerRef.current) {\n containerRef.current.remove();\n containerRef.current = null;\n }\n }, []);\n\n // Return a stable object to prevent useEffect cleanup loops\n // in consumers that depend on the handle reference.\n return useMemo(() => ({ init, captureFrame, destroy }), [init, captureFrame, destroy]);\n}\n","/**\n * VideoExportButton — Simple button that opens the VideoExportModal in a portal.\n *\n * Convenience wrapper for consumers who want a drop-in button.\n * The modal renders via `createPortal` into `document.body`.\n */\n\nimport { useState, useCallback } from 'react';\nimport { createPortal } from 'react-dom';\nimport type { Doc } from '@bendyline/squisq/schemas';\nimport type { MediaProvider } from '@bendyline/squisq/schemas';\nimport { VideoExportModal } from './VideoExportModal.js';\nimport type { VideoExportConfig } from './hooks/useVideoExport.js';\n\nexport interface VideoExportButtonProps {\n /** The document to export */\n doc: Doc;\n /**\n * Player IIFE bundle source. Unused by the browser export path (frames\n * are captured from a live in-page DocPlayer); only forwarded for\n * CLI/Playwright-style pipelines that render standalone HTML.\n */\n playerScript?: string;\n /** Optional media provider for resolving images/audio */\n mediaProvider?: MediaProvider;\n /** Pre-collected images map */\n images?: Map<string, ArrayBuffer>;\n /** Pre-collected audio map */\n audio?: Map<string, ArrayBuffer>;\n /**\n * Seeds the modal's initial export settings and is merged as a base into the\n * config passed to the export hook. Forwarded to {@link VideoExportModal}.\n */\n defaultConfig?: Partial<VideoExportConfig>;\n /** Button label (default: \"Export Video\") */\n label?: string;\n /** Additional inline styles for the button */\n style?: React.CSSProperties;\n /** Whether the button is disabled */\n disabled?: boolean;\n}\n\nexport function VideoExportButton({\n doc,\n playerScript,\n mediaProvider,\n images,\n audio,\n defaultConfig,\n label = 'Export Video',\n style,\n disabled,\n}: VideoExportButtonProps) {\n const [showModal, setShowModal] = useState(false);\n\n const handleOpen = useCallback(() => setShowModal(true), []);\n const handleClose = useCallback(() => setShowModal(false), []);\n\n return (\n <>\n <button onClick={handleOpen} disabled={disabled} style={style}>\n {label}\n </button>\n\n {showModal &&\n createPortal(\n <VideoExportModal\n doc={doc}\n playerScript={playerScript}\n mediaProvider={mediaProvider}\n images={images}\n audio={audio}\n defaultConfig={defaultConfig}\n onClose={handleClose}\n />,\n document.body,\n )}\n </>\n );\n}\n"],"mappings":"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","names":["useState","useCallback","useRef","useCallback","useRef","useCallback","url","useState","useCallback","useState","useCallback","Fragment","jsx","jsxs","useState","useCallback"]}
1
+ {"version":3,"sources":["../src/VideoExportModal.tsx","../src/hooks/useVideoExport.ts","../src/mainThreadEncoder.ts","../src/workerEncoder.ts","../src/audioTrack.ts","../src/gifTranscode.ts","../src/hooks/useFrameCapture.ts","../src/VideoExportButton.tsx"],"sourcesContent":["/**\n * VideoExportModal — Modal dialog for configuring and monitoring video export.\n *\n * States:\n * configure → exporting (capturing + encoding) → complete | error\n *\n * Hosts may supply a resolved color scheme so this portaled dialog matches\n * the surface that opened it.\n */\n\nimport { useState, useCallback } from 'react';\nimport type { Doc } from '@bendyline/squisq/schemas';\nimport type { MediaProvider } from '@bendyline/squisq/schemas';\nimport type { VideoQuality, VideoOrientation } from '@bendyline/squisq-video';\nimport type { CaptionMode } from '@bendyline/squisq-react';\nimport {\n useVideoExport,\n type VideoExportConfig,\n type VideoOutputFormat,\n} from './hooks/useVideoExport.js';\n\n// ── Types ──────────────────────────────────────────────────────────\n\nexport interface VideoExportModalProps {\n /** The document to export */\n doc: Doc;\n /**\n * Player IIFE bundle source. Unused by the browser export path (frames\n * are captured from a live in-page DocPlayer); only forwarded for\n * CLI/Playwright-style pipelines that render standalone HTML.\n */\n playerScript?: string;\n /** Optional media provider for resolving images/audio */\n mediaProvider?: MediaProvider;\n /** Pre-collected images map (alternative to mediaProvider) */\n images?: Map<string, ArrayBuffer>;\n /** Pre-collected audio map */\n audio?: Map<string, ArrayBuffer>;\n /**\n * Seeds the modal's initial format, motion, quality, FPS, orientation, and\n * caption selections. It is merged (as a base) into the config passed to the\n * export hook, so a host can share one config shape with `useVideoExport`.\n * The individual `images`/`audio`/`mediaProvider`/`playerScript` props still\n * take precedence over any matching key here.\n */\n defaultConfig?: Partial<VideoExportConfig>;\n /** Visual color scheme for the portaled dialog. Defaults to light. */\n colorScheme?: 'light' | 'dark';\n /** Called when the modal should close */\n onClose: () => void;\n}\n\n// ── Helpers ────────────────────────────────────────────────────\n\nfunction formatDuration(seconds: number): string {\n if (seconds < 60) return `${seconds}s`;\n const m = Math.floor(seconds / 60);\n const s = seconds % 60;\n return `${m}m ${s}s`;\n}\n\nfunction encoderLabel(\n outputFormat: VideoOutputFormat,\n backend: 'webcodecs' | 'ffmpeg-wasm',\n): string {\n if (outputFormat === 'gif') {\n return backend === 'webcodecs'\n ? 'WebCodecs (H.264) → ffmpeg.wasm (GIF)'\n : 'ffmpeg.wasm (H.264 → GIF)';\n }\n return backend === 'webcodecs' ? 'WebCodecs (H.264)' : 'ffmpeg.wasm (H.264)';\n}\n\n// ── Styles ─────────────────────────────────────────────────────────\n\ninterface VideoExportPalette {\n overlay: string;\n surface: string;\n control: string;\n border: string;\n text: string;\n heading: string;\n label: string;\n muted: string;\n secondary: string;\n primary: string;\n primaryBorder: string;\n success: string;\n danger: string;\n}\n\nconst VIDEO_EXPORT_PALETTES: Record<'light' | 'dark', VideoExportPalette> = {\n light: {\n overlay: 'rgba(0, 0, 0, 0.5)',\n surface: '#FFFDF7',\n control: '#ffffff',\n border: '#c9b98a',\n text: '#4a3c1f',\n heading: '#2d2310',\n label: '#5a4a2a',\n muted: '#8a7a5a',\n secondary: '#E8DFC6',\n primary: '#8B6914',\n primaryBorder: '#7a5c10',\n success: '#2d6a10',\n danger: '#a03020',\n },\n dark: {\n overlay: 'rgba(2, 6, 23, 0.72)',\n surface: '#111827',\n control: '#0f172a',\n border: '#475569',\n text: '#e5e7eb',\n heading: '#f8fafc',\n label: '#cbd5e1',\n muted: '#94a3b8',\n secondary: '#1e293b',\n primary: '#9a7416',\n primaryBorder: '#d1a73b',\n success: '#86efac',\n danger: '#fca5a5',\n },\n};\n\nconst overlayStyle: React.CSSProperties = {\n position: 'fixed',\n inset: 0,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n zIndex: 10000,\n};\n\nconst modalStyle: React.CSSProperties = {\n borderRadius: 0,\n padding: '24px 28px',\n minWidth: 380,\n maxWidth: 480,\n boxShadow: '0 8px 32px rgba(0,0,0,0.18)',\n fontFamily: 'system-ui, -apple-system, sans-serif',\n};\n\nconst titleStyle: React.CSSProperties = {\n margin: '0 0 16px 0',\n fontSize: 18,\n fontWeight: 600,\n};\n\nconst labelStyle: React.CSSProperties = {\n display: 'block',\n fontSize: 13,\n fontWeight: 500,\n marginBottom: 4,\n};\n\nconst selectStyle: React.CSSProperties = {\n width: '100%',\n padding: '6px 8px',\n fontSize: 13,\n fontFamily: 'inherit',\n borderRadius: 0,\n marginBottom: 12,\n};\n\nconst btnPrimary: React.CSSProperties = {\n padding: '8px 20px',\n fontSize: 14,\n fontFamily: 'inherit',\n fontWeight: 500,\n cursor: 'pointer',\n color: '#fff',\n borderRadius: 0,\n};\n\nconst btnSecondary: React.CSSProperties = {\n padding: '8px 20px',\n fontSize: 14,\n fontFamily: 'inherit',\n fontWeight: 500,\n cursor: 'pointer',\n borderRadius: 0,\n};\n\nconst progressBarOuterStyle: React.CSSProperties = {\n width: '100%',\n height: 8,\n borderRadius: 0,\n overflow: 'hidden',\n marginBottom: 8,\n};\n\nconst footerStyle: React.CSSProperties = {\n display: 'flex',\n justifyContent: 'flex-end',\n gap: 8,\n marginTop: 20,\n};\n\n// ── Component ──────────────────────────────────────────────────────\n\nexport function VideoExportModal({\n doc,\n playerScript,\n mediaProvider,\n images,\n audio,\n defaultConfig,\n colorScheme = 'light',\n onClose,\n}: VideoExportModalProps) {\n const initialOutputFormat = defaultConfig?.outputFormat ?? 'mp4';\n const [outputFormat, setOutputFormat] = useState<VideoOutputFormat>(initialOutputFormat);\n const [quality, setQuality] = useState<VideoQuality>(defaultConfig?.quality ?? 'normal');\n const [fps, setFps] = useState(defaultConfig?.fps ?? (initialOutputFormat === 'gif' ? 10 : 24));\n const [orientation, setOrientation] = useState<VideoOrientation>(\n defaultConfig?.orientation ?? 'landscape',\n );\n const [captionMode, setCaptionMode] = useState<CaptionMode>(defaultConfig?.captionMode ?? 'off');\n const [animationsEnabled, setAnimationsEnabled] = useState(\n defaultConfig?.animationsEnabled ?? initialOutputFormat === 'mp4',\n );\n const palette = VIDEO_EXPORT_PALETTES[colorScheme];\n const themedModalStyle: React.CSSProperties = {\n ...modalStyle,\n background: palette.surface,\n border: `1px solid ${palette.border}`,\n color: palette.text,\n colorScheme,\n };\n const themedTitleStyle: React.CSSProperties = { ...titleStyle, color: palette.heading };\n const themedLabelStyle: React.CSSProperties = { ...labelStyle, color: palette.label };\n const themedSelectStyle: React.CSSProperties = {\n ...selectStyle,\n border: `1px solid ${palette.border}`,\n background: palette.control,\n color: palette.text,\n colorScheme,\n };\n const themedPrimaryButtonStyle: React.CSSProperties = {\n ...btnPrimary,\n background: palette.primary,\n border: `1px solid ${palette.primaryBorder}`,\n };\n const themedSecondaryButtonStyle: React.CSSProperties = {\n ...btnSecondary,\n background: palette.secondary,\n color: palette.text,\n border: `1px solid ${palette.border}`,\n };\n\n const exportHook = useVideoExport();\n const {\n state,\n progress,\n backend,\n outputFormat: completedOutputFormat,\n downloadUrl,\n fileSize,\n audioIncluded,\n audioSkippedReason,\n error,\n elapsed,\n estimatedRemaining,\n startExport,\n cancel: cancelExport,\n reset: resetExport,\n } = exportHook;\n\n const handleOutputFormatChange = useCallback((next: VideoOutputFormat) => {\n setOutputFormat(next);\n if (next === 'gif') {\n setFps(10);\n setAnimationsEnabled(false);\n } else {\n setFps(24);\n setAnimationsEnabled(true);\n }\n }, []);\n\n const handleExport = useCallback(async () => {\n const config: VideoExportConfig = {\n // defaultConfig is the base; explicit props/selections win over it.\n ...defaultConfig,\n outputFormat,\n animationsEnabled,\n quality,\n fps,\n orientation,\n captionMode,\n images,\n audio,\n mediaProvider,\n // Only thread the bundle through when the host actually supplied one.\n ...(playerScript !== undefined ? { playerScript } : {}),\n };\n await startExport(doc, config);\n }, [\n doc,\n outputFormat,\n animationsEnabled,\n quality,\n fps,\n orientation,\n captionMode,\n images,\n audio,\n mediaProvider,\n playerScript,\n defaultConfig,\n startExport,\n ]);\n\n const handleDownload = useCallback(() => {\n if (!downloadUrl) return;\n const a = document.createElement('a');\n a.href = downloadUrl;\n const ts = new Date().toISOString().slice(0, 10);\n a.download = `document-${ts}.${completedOutputFormat}`;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n }, [downloadUrl, completedOutputFormat]);\n\n const handleClose = useCallback(() => {\n if (state === 'capturing' || state === 'encoding' || state === 'preparing') {\n cancelExport();\n }\n resetExport();\n onClose();\n }, [state, cancelExport, resetExport, onClose]);\n\n const isExporting = state === 'preparing' || state === 'capturing' || state === 'encoding';\n\n return (\n <div\n style={{ ...overlayStyle, background: palette.overlay }}\n data-color-scheme={colorScheme}\n onClick={handleClose}\n >\n <div style={themedModalStyle} onClick={(e) => e.stopPropagation()}>\n <h2 style={themedTitleStyle}>\n {outputFormat === 'gif' ? 'Export Animated GIF' : 'Export Video'}\n </h2>\n\n {/* ── Configure State ── */}\n {state === 'idle' && (\n <>\n <div>\n <label style={themedLabelStyle}>Format</label>\n <select\n aria-label=\"Format\"\n style={themedSelectStyle}\n value={outputFormat}\n onChange={(e) => handleOutputFormatChange(e.target.value as VideoOutputFormat)}\n >\n <option value=\"mp4\">MP4 video</option>\n <option value=\"gif\">Animated GIF</option>\n </select>\n </div>\n\n <div>\n <label style={themedLabelStyle}>Quality</label>\n <select\n aria-label=\"Quality\"\n style={themedSelectStyle}\n value={quality}\n onChange={(e) => setQuality(e.target.value as VideoQuality)}\n >\n <option value=\"draft\">Draft — fast, lower quality</option>\n <option value=\"normal\">Normal — balanced</option>\n <option value=\"high\">High — best quality, slower</option>\n </select>\n </div>\n\n <div>\n <label style={themedLabelStyle}>Frame Rate</label>\n <select\n aria-label=\"Frame Rate\"\n style={themedSelectStyle}\n value={fps}\n onChange={(e) => setFps(Number(e.target.value))}\n >\n <option value={10}>10 fps — recommended for GIF</option>\n <option value={15}>15 fps — fast export</option>\n <option value={24}>24 fps — cinematic</option>\n <option value={30}>30 fps — smooth</option>\n </select>\n </div>\n\n <div>\n <label style={themedLabelStyle}>Orientation</label>\n <select\n aria-label=\"Orientation\"\n style={themedSelectStyle}\n value={orientation}\n onChange={(e) => setOrientation(e.target.value as VideoOrientation)}\n >\n <option value=\"landscape\">\n Landscape ({outputFormat === 'gif' ? '960 × 540' : '1920 × 1080'})\n </option>\n <option value=\"portrait\">\n Portrait ({outputFormat === 'gif' ? '540 × 960' : '1080 × 1920'})\n </option>\n </select>\n </div>\n\n <div>\n <label style={themedLabelStyle}>Captions</label>\n <select\n aria-label=\"Captions\"\n style={themedSelectStyle}\n value={captionMode}\n onChange={(e) => setCaptionMode(e.target.value as CaptionMode)}\n >\n <option value=\"off\">None</option>\n <option value=\"standard\">Standard (top bar)</option>\n <option value=\"social\">Social media (large words)</option>\n </select>\n </div>\n\n <div>\n <label style={themedLabelStyle}>Animations &amp; transitions</label>\n <select\n aria-label=\"Animations and transitions\"\n style={themedSelectStyle}\n value={animationsEnabled ? 'enabled' : 'disabled'}\n onChange={(e) => setAnimationsEnabled(e.target.value === 'enabled')}\n >\n <option value=\"enabled\">Enabled</option>\n <option value=\"disabled\">Disabled — smaller files</option>\n </select>\n {outputFormat === 'gif' && animationsEnabled && (\n <p style={{ fontSize: 12, color: palette.muted, margin: '-6px 0 12px' }}>\n Disabling motion is recommended for much smaller GIFs.\n </p>\n )}\n </div>\n\n <div style={footerStyle}>\n <button style={themedSecondaryButtonStyle} onClick={handleClose}>\n Cancel\n </button>\n <button style={themedPrimaryButtonStyle} onClick={handleExport}>\n {outputFormat === 'gif' ? 'Export GIF' : 'Export Video'}\n </button>\n </div>\n </>\n )}\n\n {/* ── Exporting State ── */}\n {isExporting && (\n <>\n {backend && (\n <p style={{ fontSize: 12, color: palette.muted, margin: '0 0 8px 0' }}>\n Encoder: {encoderLabel(completedOutputFormat, backend)}\n </p>\n )}\n\n <div style={{ ...progressBarOuterStyle, background: palette.secondary }}>\n <div\n style={{\n width: `${progress}%`,\n height: '100%',\n background: palette.primary,\n transition: 'width 0.3s ease',\n }}\n />\n </div>\n\n <p style={{ fontSize: 13, margin: '0 0 4px 0' }}>{progress}% complete</p>\n <p style={{ fontSize: 12, color: palette.muted, margin: 0 }}>\n {formatDuration(elapsed)} elapsed\n {estimatedRemaining > 0 && ` · ~${formatDuration(estimatedRemaining)} remaining`}\n </p>\n\n <div style={footerStyle}>\n <button style={themedSecondaryButtonStyle} onClick={cancelExport}>\n Cancel\n </button>\n </div>\n </>\n )}\n\n {/* ── Complete State ── */}\n {state === 'complete' && (\n <>\n <p style={{ fontSize: 14, margin: '0 0 8px 0', color: palette.success }}>\n Export complete!\n </p>\n <p style={{ fontSize: 13, color: palette.label, margin: '0 0 4px 0' }}>\n File size: {(fileSize / (1024 * 1024)).toFixed(1)} MB\n </p>\n {completedOutputFormat === 'gif' ? (\n <p style={{ fontSize: 12, color: palette.muted, margin: '0 0 4px 0' }}>\n Animated GIF does not include audio.\n </p>\n ) : audioIncluded ? (\n <p style={{ fontSize: 12, color: palette.success, margin: '0 0 4px 0' }}>\n Audio included ✓\n </p>\n ) : (\n <p style={{ fontSize: 12, color: palette.muted, margin: '0 0 4px 0' }}>\n Video only{audioSkippedReason ? ` — ${audioSkippedReason}` : ''}\n </p>\n )}\n {backend && (\n <p style={{ fontSize: 12, color: palette.muted, margin: '0 0 12px 0' }}>\n Encoded with {encoderLabel(completedOutputFormat, backend)}\n </p>\n )}\n\n <div style={footerStyle}>\n <button style={themedSecondaryButtonStyle} onClick={handleClose}>\n Close\n </button>\n <button style={themedPrimaryButtonStyle} onClick={handleDownload}>\n Download {completedOutputFormat.toUpperCase()}\n </button>\n </div>\n </>\n )}\n\n {/* ── Error State ── */}\n {state === 'error' && (\n <>\n <p style={{ fontSize: 14, margin: '0 0 8px 0', color: palette.danger }}>\n Export failed\n </p>\n <p\n style={{\n fontSize: 13,\n color: palette.label,\n margin: '0 0 12px 0',\n wordBreak: 'break-word',\n }}\n >\n {error}\n </p>\n\n <div style={footerStyle}>\n <button style={themedSecondaryButtonStyle} onClick={handleClose}>\n Close\n </button>\n <button style={themedPrimaryButtonStyle} onClick={handleExport}>\n Retry {outputFormat === 'gif' ? 'GIF' : 'video'}\n </button>\n </div>\n </>\n )}\n </div>\n </div>\n );\n}\n","/**\n * useVideoExport — Main orchestration hook for browser video export.\n *\n * Coordinates frame capture (hidden iframe + html2canvas) with\n * main-thread WebCodecs encoding via mp4-muxer. Manages the full\n * lifecycle: prepare → capture + encode → download.\n *\n * Encoding runs on the main thread because frame capture via html2canvas\n * (~100-200ms per frame) is the bottleneck, not encoding (~1ms per frame\n * with hardware-accelerated WebCodecs). Worker offloading would add\n * complexity with minimal benefit.\n *\n * Usage:\n * const { state, progress, phase, startExport, cancel, downloadUrl } = useVideoExport();\n * <button onClick={() => startExport(doc, options)}>Export</button>\n */\n\nimport { useState, useRef, useCallback, useEffect } from 'react';\nimport type { Doc } from '@bendyline/squisq/schemas';\nimport type { MediaProvider } from '@bendyline/squisq/schemas';\nimport type {\n VideoQuality,\n VideoOrientation,\n AudioTimelineClip,\n FfmpegWasmLoadConfig,\n} from '@bendyline/squisq-video';\nimport { resolveDimensions, computeAudioTimeline, QUALITY_PRESETS } from '@bendyline/squisq-video';\nimport type { CaptionMode } from '@bendyline/squisq-react';\nimport {\n createEncoder,\n supportsWebCodecs,\n supportsWebCodecsH264,\n type MainThreadEncoder,\n} from '../mainThreadEncoder.js';\nimport { createWorkerEncoder } from '../workerEncoder.js';\nimport {\n supportsWebCodecsAac,\n selectAudioTier,\n renderAudioTimeline,\n encodeAacTrack,\n audioBufferToWav,\n muxAudioWithFfmpegWasm,\n EXPORT_AUDIO_SAMPLE_RATE,\n EXPORT_AUDIO_CHANNELS,\n} from '../audioTrack.js';\nimport { transcodeMp4ToGifWithFfmpegWasm } from '../gifTranscode.js';\nimport { useFrameCapture } from './useFrameCapture.js';\n\n// ── Audio resolution ───────────────────────────────────────────────\n\n/**\n * Resolve the raw bytes for every unique source in the audio timeline from\n * (in order) the pre-collected audio map, the images map, then the\n * MediaProvider. Sources that can't be resolved are simply omitted — the\n * caller degrades gracefully rather than failing.\n */\nasync function resolveAudioBuffers(\n clips: AudioTimelineClip[],\n sources: {\n audio?: Map<string, ArrayBuffer>;\n images?: Map<string, ArrayBuffer>;\n mediaProvider?: MediaProvider;\n },\n): Promise<Map<string, ArrayBuffer>> {\n const srcs = new Set(clips.map((c) => c.src));\n const out = new Map<string, ArrayBuffer>();\n for (const src of srcs) {\n let data = sources.audio?.get(src) ?? sources.images?.get(src);\n if (!data && sources.mediaProvider) {\n try {\n const url = await sources.mediaProvider.resolveUrl(src);\n const res = await fetch(url);\n if (res.ok) data = await res.arrayBuffer();\n } catch {\n // Unresolvable source; skip it.\n }\n }\n if (data) out.set(src, data);\n }\n return out;\n}\n\n// ── Types ──────────────────────────────────────────────────────────\n\nexport type VideoExportState =\n | 'idle'\n | 'preparing'\n | 'capturing'\n | 'encoding'\n | 'complete'\n | 'error';\n\n/** Browser export container format. */\nexport type VideoOutputFormat = 'mp4' | 'gif';\n\nexport interface VideoExportConfig {\n /** Output container (default: 'mp4') */\n outputFormat?: VideoOutputFormat;\n /** Render authored animations and slide transitions (default: true for MP4, false for GIF). */\n animationsEnabled?: boolean;\n /** Encoding quality preset (default: 'normal') */\n quality?: VideoQuality;\n /** Frames per second (default: 30) */\n fps?: number;\n /** Viewport orientation (default: 'landscape') */\n orientation?: VideoOrientation;\n /** Explicit output width. GIF defaults to 960 landscape / 540 portrait. */\n width?: number;\n /** Explicit output height. GIF defaults to 540 landscape / 960 portrait. */\n height?: number;\n /**\n * Map of relative image paths to binary data.\n * Used to embed images into the render HTML.\n */\n images?: Map<string, ArrayBuffer>;\n /**\n * Map of audio segment names to binary data.\n * Used to embed audio into the render HTML.\n */\n audio?: Map<string, ArrayBuffer>;\n /** MediaProvider to resolve media URLs (alternative to passing images directly) */\n mediaProvider?: MediaProvider;\n /** Caption mode for the exported video (default: 'off') */\n captionMode?: CaptionMode;\n /** Player IIFE bundle (unused in browser export, kept for CLI/Playwright path) */\n playerScript?: string;\n /** Optional self-hosted ffmpeg.wasm core URLs for fallback/offline/CSP use. */\n ffmpegWasm?: FfmpegWasmLoadConfig;\n}\n\nexport interface VideoExportResult {\n /** Current export state */\n state: VideoExportState;\n /** 0–100 progress percentage */\n progress: number;\n /** Human-readable description of the current phase */\n phase: string;\n /** Video duration detected from the doc (seconds) */\n duration: number;\n /** Effective output format for the current or most recent export. */\n outputFormat: VideoOutputFormat;\n /** Encoder backend ('webcodecs' when WebCodecs H.264 active, 'ffmpeg-wasm' when worker fallback active, null when idle) */\n backend: 'webcodecs' | 'ffmpeg-wasm' | null;\n /** Blob download URL (populated when state === 'complete') */\n downloadUrl: string | null;\n /** File size in bytes (populated when state === 'complete') */\n fileSize: number;\n /**\n * Whether an audio track was muxed into the exported MP4 (populated when\n * state === 'complete'). False when the doc had no audio or when audio was\n * skipped/degraded — see {@link audioSkippedReason}.\n */\n audioIncluded: boolean;\n /**\n * Why audio is absent, when `audioIncluded` is false. `null` means the doc\n * simply had no audio (not a failure); a non-null string explains a genuine\n * capability shortfall or runtime failure. Audio problems never fail the\n * export — the video always completes.\n */\n audioSkippedReason: string | null;\n /** Error message (populated when state === 'error') */\n error: string | null;\n /** Seconds elapsed since export started */\n elapsed: number;\n /** Estimated seconds remaining (0 when idle or complete) */\n estimatedRemaining: number;\n /** Start a new export */\n startExport: (doc: Doc, config: VideoExportConfig) => Promise<void>;\n /** Cancel an in-progress export */\n cancel: () => void;\n /** Reset state back to idle (e.g., after complete or error) */\n reset: () => void;\n}\n\n// ── Hook ───────────────────────────────────────────────────────────\n\nexport function useVideoExport(): VideoExportResult {\n const [state, setState] = useState<VideoExportState>('idle');\n const [progress, setProgress] = useState(0);\n const [phase, setPhase] = useState('');\n const [duration, setDuration] = useState(0);\n const [outputFormat, setOutputFormat] = useState<VideoOutputFormat>('mp4');\n const [backend, setBackend] = useState<'webcodecs' | 'ffmpeg-wasm' | null>(null);\n const [downloadUrl, setDownloadUrl] = useState<string | null>(null);\n const [fileSize, setFileSize] = useState(0);\n const [audioIncluded, setAudioIncluded] = useState(false);\n const [audioSkippedReason, setAudioSkippedReason] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n const [elapsed, setElapsed] = useState(0);\n const [estimatedRemaining, setEstimatedRemaining] = useState(0);\n\n const encoderRef = useRef<MainThreadEncoder | null>(null);\n const gifAbortRef = useRef<AbortController | null>(null);\n const cancelledRef = useRef(false);\n const downloadUrlRef = useRef<string | null>(null);\n const startTimeRef = useRef<number>(0);\n const elapsedTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);\n\n const frameCapture = useFrameCapture();\n\n // Clean up on unmount\n useEffect(() => {\n return () => {\n if (elapsedTimerRef.current) clearInterval(elapsedTimerRef.current);\n if (downloadUrlRef.current) {\n URL.revokeObjectURL(downloadUrlRef.current);\n }\n if (encoderRef.current) {\n encoderRef.current.close();\n }\n gifAbortRef.current?.abort();\n frameCapture.destroy();\n };\n }, [frameCapture]);\n\n const reset = useCallback(() => {\n if (downloadUrlRef.current) {\n URL.revokeObjectURL(downloadUrlRef.current);\n downloadUrlRef.current = null;\n }\n if (encoderRef.current) {\n encoderRef.current.close();\n encoderRef.current = null;\n }\n gifAbortRef.current?.abort();\n gifAbortRef.current = null;\n frameCapture.destroy();\n setState('idle');\n setProgress(0);\n setPhase('');\n setDuration(0);\n setOutputFormat('mp4');\n setBackend(null);\n setDownloadUrl(null);\n setFileSize(0);\n setAudioIncluded(false);\n setAudioSkippedReason(null);\n setError(null);\n setElapsed(0);\n setEstimatedRemaining(0);\n if (elapsedTimerRef.current) clearInterval(elapsedTimerRef.current);\n cancelledRef.current = false;\n }, [frameCapture]);\n\n const cancel = useCallback(() => {\n cancelledRef.current = true;\n if (elapsedTimerRef.current) clearInterval(elapsedTimerRef.current);\n if (encoderRef.current) {\n encoderRef.current.close();\n encoderRef.current = null;\n }\n gifAbortRef.current?.abort();\n gifAbortRef.current = null;\n frameCapture.destroy();\n setState('idle');\n setProgress(0);\n setPhase('Cancelled');\n }, [frameCapture]);\n\n const startExport = useCallback(\n async (doc: Doc, config: VideoExportConfig) => {\n // Clear previous state\n cancelledRef.current = false;\n if (downloadUrlRef.current) {\n URL.revokeObjectURL(downloadUrlRef.current);\n downloadUrlRef.current = null;\n }\n setDownloadUrl(null);\n setFileSize(0);\n setAudioIncluded(false);\n setAudioSkippedReason(null);\n setError(null);\n\n const quality = config.quality ?? 'normal';\n const effectiveOutputFormat = config.outputFormat ?? 'mp4';\n const fps = config.fps ?? (effectiveOutputFormat === 'gif' ? 10 : 30);\n const orientation = config.orientation ?? 'landscape';\n const animationsEnabled = config.animationsEnabled ?? effectiveOutputFormat === 'mp4';\n setOutputFormat(effectiveOutputFormat);\n\n try {\n const gifDefaults =\n orientation === 'portrait' ? { width: 540, height: 960 } : { width: 960, height: 540 };\n const { width, height } = resolveDimensions({\n orientation,\n fps,\n quality,\n ...(config.width !== undefined\n ? { width: config.width }\n : effectiveOutputFormat === 'gif'\n ? { width: gifDefaults.width }\n : {}),\n ...(config.height !== undefined\n ? { height: config.height }\n : effectiveOutputFormat === 'gif'\n ? { height: gifDefaults.height }\n : {}),\n });\n // ── Check browser support ─────────────────────────────────\n const webCodecsAvailable = supportsWebCodecs();\n const sharedArrayBufferAvailable = typeof SharedArrayBuffer !== 'undefined';\n if (effectiveOutputFormat === 'gif' && !sharedArrayBufferAvailable) {\n throw new Error(\n 'Animated GIF export requires ffmpeg.wasm and SharedArrayBuffer ' +\n '(Cross-Origin-Isolation headers).',\n );\n }\n if (!webCodecsAvailable && !sharedArrayBufferAvailable) {\n throw new Error(\n 'No video encoder available. WebCodecs requires Chrome 94+ / Edge 94+, ' +\n 'and the ffmpeg.wasm fallback requires SharedArrayBuffer ' +\n '(Cross-Origin-Isolation headers).',\n );\n }\n\n // ── Step 1: Prepare ───────────────────────────────────────\n setState('preparing');\n setPhase('Loading document…');\n setProgress(0);\n setElapsed(0);\n setEstimatedRemaining(0);\n\n // Start elapsed timer\n startTimeRef.current = performance.now();\n if (elapsedTimerRef.current) clearInterval(elapsedTimerRef.current);\n elapsedTimerRef.current = setInterval(() => {\n setElapsed(Math.floor((performance.now() - startTimeRef.current) / 1000));\n }, 1000);\n\n // Collect images from MediaProvider if provided and images not passed directly\n let images = config.images;\n if (!images && config.mediaProvider) {\n images = new Map<string, ArrayBuffer>();\n const entries = await config.mediaProvider.listMedia();\n for (const entry of entries) {\n const url = await config.mediaProvider.resolveUrl(entry.name);\n const res = await fetch(url);\n if (res.ok) {\n const data = await res.arrayBuffer();\n images.set(entry.name, data);\n }\n }\n }\n\n const docDuration = await frameCapture.init(\n doc,\n { images, audio: config.audio, width, height, animationsEnabled },\n config.captionMode,\n );\n\n if (cancelledRef.current) return;\n\n setDuration(docDuration);\n if (docDuration <= 0) {\n throw new Error('Document has zero duration — nothing to export');\n }\n\n // ── Step 2: Create encoder ────────────────────────────────\n setPhase('Starting encoder…');\n setProgress(5);\n\n // Prefer main-thread WebCodecs (fast), but probe whether H.264\n // is actually supported. Linux Chromium has VideoEncoder but\n // no proprietary H.264 codec — fall back to the worker, which\n // loads ffmpeg.wasm in that case.\n const canUseWebCodecs =\n webCodecsAvailable && (await supportsWebCodecsH264({ width, height, fps, quality }));\n\n // ── Audio: tier selection + (best-effort) render ──────────\n // The browser frame-capture path has no cover pre-roll (Playwright\n // only), so the timeline is unshifted. Every audio operation below is\n // wrapped so a failure degrades to a silent video with a reason —\n // audio never aborts the export.\n const audioBitrate = (QUALITY_PRESETS[quality] ?? QUALITY_PRESETS.normal).audioBitrate;\n // GIF has no audio track. An empty timeline skips preparation and\n // muxing without reporting the format limitation as an export error.\n const timeline = effectiveOutputFormat === 'mp4' ? computeAudioTimeline(doc, 0) : [];\n const aacSupported =\n timeline.length > 0\n ? await supportsWebCodecsAac(EXPORT_AUDIO_SAMPLE_RATE, EXPORT_AUDIO_CHANNELS)\n : false;\n const tierDecision = selectAudioTier({\n hasClips: timeline.length > 0,\n aacSupported,\n sharedArrayBufferAvailable,\n canUseMainThreadWebCodecs: canUseWebCodecs,\n });\n\n let renderedAudio: AudioBuffer | null = null;\n let audioIncludedLocal = false;\n let audioReasonLocal: string | null = tierDecision.reason;\n\n if (tierDecision.tier === 1 || tierDecision.tier === 2) {\n setPhase('Preparing audio…');\n try {\n const buffers = await resolveAudioBuffers(timeline, {\n audio: config.audio,\n images,\n mediaProvider: config.mediaProvider,\n });\n if (buffers.size === 0) {\n audioReasonLocal = 'Audio files for this document could not be loaded.';\n } else {\n const totalAudioDur = timeline.reduce(\n (max, c) => Math.max(max, c.startSec + c.durationSec),\n docDuration,\n );\n renderedAudio = await renderAudioTimeline(\n timeline,\n buffers,\n totalAudioDur,\n EXPORT_AUDIO_SAMPLE_RATE,\n );\n }\n } catch (audioErr: unknown) {\n renderedAudio = null;\n audioReasonLocal = `Audio could not be prepared: ${\n audioErr instanceof Error ? audioErr.message : String(audioErr)\n }`;\n }\n }\n\n const useInlineAudio = renderedAudio !== null && tierDecision.tier === 1;\n const useFfmpegAudio = renderedAudio !== null && tierDecision.tier === 2;\n\n if (cancelledRef.current) return;\n\n let encoder: MainThreadEncoder;\n if (canUseWebCodecs) {\n encoder = createEncoder({\n width,\n height,\n fps,\n quality,\n ...(useInlineAudio && renderedAudio\n ? {\n audio: {\n numberOfChannels: renderedAudio.numberOfChannels,\n sampleRate: renderedAudio.sampleRate,\n },\n }\n : {}),\n });\n setBackend('webcodecs');\n } else if (sharedArrayBufferAvailable) {\n const workerEncoder = createWorkerEncoder({\n width,\n height,\n fps,\n quality,\n ffmpegWasm: config.ffmpegWasm,\n });\n encoder = workerEncoder;\n const selectedBackend = await workerEncoder.ready;\n setBackend(selectedBackend);\n setPhase(\n selectedBackend === 'ffmpeg-wasm'\n ? 'Starting encoder (ffmpeg.wasm)…'\n : 'Starting encoder…',\n );\n } else {\n throw new Error(\n 'WebCodecs H.264 is unavailable in this browser and the ffmpeg.wasm ' +\n 'fallback requires SharedArrayBuffer (Cross-Origin-Isolation headers).',\n );\n }\n encoderRef.current = encoder;\n\n if (cancelledRef.current) return;\n\n // ── Step 3: Capture frames and encode ─────────────────────\n setState('capturing');\n const totalFrames = Math.ceil(docDuration * fps);\n\n const captureStartTime = performance.now();\n // Throttle UI updates to every ~10 frames to avoid excessive re-renders.\n // Each setState between awaits triggers a separate render cycle.\n const UI_UPDATE_INTERVAL = 10;\n\n for (let i = 0; i < totalFrames; i++) {\n if (cancelledRef.current) return;\n\n const time = i / fps;\n\n // Update UI periodically (not every frame)\n if (i % UI_UPDATE_INTERVAL === 0 || i === totalFrames - 1) {\n const captureProgress = Math.round((i / totalFrames) * 90);\n setProgress(5 + captureProgress);\n setPhase(`Capturing frame ${i + 1}/${totalFrames} (${time.toFixed(1)}s)`);\n\n if (i > 0) {\n const elapsedCapture = (performance.now() - captureStartTime) / 1000;\n const avgPerFrame = elapsedCapture / i;\n const remaining = Math.round(avgPerFrame * (totalFrames - i));\n setEstimatedRemaining(remaining);\n }\n }\n\n const bitmap = await frameCapture.captureFrame(time);\n\n if (cancelledRef.current) {\n bitmap.close();\n return;\n }\n\n // Encode immediately — WebCodecs is fast and async internally\n await encoder.encodeFrame(bitmap, i);\n }\n\n if (cancelledRef.current) return;\n\n // ── Step 4a: Inline audio (tier 1) ────────────────────────\n // Encode the rendered audio into the same muxer before finalizing.\n if (useInlineAudio && renderedAudio && encoder.addAudioChunk) {\n setState('encoding');\n setPhase('Encoding audio…');\n try {\n await encodeAacTrack(\n renderedAudio,\n { addAudioChunk: encoder.addAudioChunk.bind(encoder) },\n audioBitrate,\n );\n audioIncludedLocal = true;\n } catch (audioErr: unknown) {\n audioIncludedLocal = false;\n audioReasonLocal = `Audio encoding failed: ${\n audioErr instanceof Error ? audioErr.message : String(audioErr)\n }`;\n }\n }\n\n // ── Step 4: Finalize MP4 (or GIF's MP4 intermediate) ─────\n setState('encoding');\n setPhase(effectiveOutputFormat === 'gif' ? 'Finalizing GIF frames…' : 'Finalizing video…');\n setProgress(95);\n\n let outputBytes: ArrayBuffer | Uint8Array = await encoder.finalize();\n encoderRef.current = null;\n\n if (cancelledRef.current) return;\n\n // ── Step 4b: GIF palette transcode or audio mux ───────────\n if (effectiveOutputFormat === 'gif') {\n setPhase('Generating GIF palette…');\n const videoOnly =\n outputBytes instanceof Uint8Array ? outputBytes : new Uint8Array(outputBytes);\n const gifAbort = new AbortController();\n gifAbortRef.current = gifAbort;\n try {\n outputBytes = await transcodeMp4ToGifWithFfmpegWasm(\n videoOnly,\n { width, height, loop: 0 },\n config.ffmpegWasm,\n gifAbort.signal,\n );\n } finally {\n if (gifAbortRef.current === gifAbort) gifAbortRef.current = null;\n }\n } else if (useFfmpegAudio && renderedAudio) {\n // Video is finalized; add audio in a single copy-video pass.\n setPhase('Muxing audio…');\n try {\n const wav = audioBufferToWav(renderedAudio);\n const videoOnly =\n outputBytes instanceof Uint8Array ? outputBytes : new Uint8Array(outputBytes);\n outputBytes = await muxAudioWithFfmpegWasm(\n videoOnly,\n wav,\n audioBitrate,\n config.ffmpegWasm,\n );\n audioIncludedLocal = true;\n } catch (audioErr: unknown) {\n audioIncludedLocal = false;\n audioReasonLocal = `Audio muxing failed: ${\n audioErr instanceof Error ? audioErr.message : String(audioErr)\n }`;\n }\n }\n\n if (cancelledRef.current) return;\n\n // ── Step 5: Create download URL ───────────────────────────\n // Normalize to a plain ArrayBuffer-backed view for Blob (ffmpeg.wasm\n // output may be typed over SharedArrayBuffer).\n const finalBytes =\n outputBytes instanceof Uint8Array ? outputBytes.slice() : new Uint8Array(outputBytes);\n const mimeType = effectiveOutputFormat === 'gif' ? 'image/gif' : 'video/mp4';\n const blob = new Blob([finalBytes], { type: mimeType });\n const url = URL.createObjectURL(blob);\n downloadUrlRef.current = url;\n\n setDownloadUrl(url);\n setFileSize(finalBytes.byteLength);\n setAudioIncluded(audioIncludedLocal);\n setAudioSkippedReason(\n effectiveOutputFormat === 'gif' || audioIncludedLocal ? null : audioReasonLocal,\n );\n setState('complete');\n setProgress(100);\n setPhase('Export complete');\n setEstimatedRemaining(0);\n if (elapsedTimerRef.current) clearInterval(elapsedTimerRef.current);\n\n // Clean up\n frameCapture.destroy();\n } catch (err: unknown) {\n if (elapsedTimerRef.current) clearInterval(elapsedTimerRef.current);\n if (cancelledRef.current) return;\n const message = err instanceof Error ? err.message : String(err);\n setState('error');\n setError(message);\n setPhase('Export failed');\n\n // Clean up on error\n if (encoderRef.current) {\n encoderRef.current.close();\n encoderRef.current = null;\n }\n gifAbortRef.current?.abort();\n gifAbortRef.current = null;\n frameCapture.destroy();\n }\n },\n [frameCapture],\n );\n\n return {\n state,\n progress,\n phase,\n duration,\n outputFormat,\n backend,\n downloadUrl,\n fileSize,\n audioIncluded,\n audioSkippedReason,\n error,\n elapsed,\n estimatedRemaining,\n startExport,\n cancel,\n reset,\n };\n}\n","/**\n * Main-thread WebCodecs encoder.\n *\n * Encodes video frames to MP4 using the WebCodecs API and mp4-muxer,\n * running directly on the main thread. This is simpler and avoids\n * worker module-resolution issues with bundlers. Since frame capture\n * via html2canvas (~100-200ms per frame) is the bottleneck — not\n * encoding (~1ms per frame with hardware-accelerated WebCodecs) —\n * worker offloading provides minimal benefit.\n *\n * Requirements: Chrome 94+ / Edge 94+ (WebCodecs support).\n */\n\nimport { bitrateForQuality, validateVideoExportOptions } from '@bendyline/squisq-video';\n\nimport { createMp4Muxer, type Mp4MuxerHandle } from './mp4Mux.js';\n\nexport interface EncoderConfig {\n width: number;\n height: number;\n fps: number;\n quality: 'draft' | 'normal' | 'high';\n /**\n * When present, the underlying muxer is configured with an AAC audio track\n * and {@link MainThreadEncoder.addAudioChunk} becomes usable. Absent → the\n * encoder produces a video-only MP4 exactly as before.\n */\n audio?: {\n numberOfChannels: number;\n sampleRate: number;\n };\n}\n\nexport interface MainThreadEncoder {\n /** Encode a single frame. The bitmap is closed after encoding. */\n encodeFrame(bitmap: ImageBitmap, frameIndex: number): Promise<void>;\n /**\n * Hand an encoded audio chunk (from a WebCodecs `AudioEncoder`) to the muxer.\n * Only valid when the encoder was created with an `audio` config; otherwise a\n * no-op. Must be called before {@link finalize}.\n */\n addAudioChunk?(chunk: EncodedAudioChunk, meta?: EncodedAudioChunkMetadata): void;\n /** Flush pending frames and finalize the MP4. Returns the MP4 ArrayBuffer. */\n finalize(): Promise<ArrayBuffer>;\n /** Close the encoder without producing output (e.g., on cancel). */\n close(): void;\n}\n\n/**\n * Check whether the browser supports WebCodecs video encoding.\n */\nexport function supportsWebCodecs(): boolean {\n return typeof VideoEncoder !== 'undefined' && typeof VideoFrame !== 'undefined';\n}\n\n/**\n * Probe whether the WebCodecs encoder actually supports the H.264 profile\n * we use. The `VideoEncoder` global can exist while the specific codec is\n * unavailable — this is the case on Linux Chromium, which ships without\n * the proprietary H.264 encoder.\n */\nexport async function supportsWebCodecsH264(config: EncoderConfig): Promise<boolean> {\n if (!supportsWebCodecs()) return false;\n try {\n const support = await VideoEncoder.isConfigSupported({\n codec: 'avc1.640028',\n width: config.width,\n height: config.height,\n bitrate: bitrateForQuality(config.quality, config.width, config.height),\n framerate: config.fps,\n });\n return support.supported === true;\n } catch {\n return false;\n }\n}\n\n/**\n * Create a main-thread WebCodecs encoder.\n *\n * Throws if WebCodecs is not available.\n */\nexport function createEncoder(config: EncoderConfig): MainThreadEncoder {\n validateVideoExportOptions(config);\n if (!supportsWebCodecs()) {\n throw new Error(\n 'WebCodecs is not available in this browser. ' +\n 'Video export requires Chrome 94+, Edge 94+, or another Chromium-based browser.',\n );\n }\n\n const muxer: Mp4MuxerHandle = createMp4Muxer({\n width: config.width,\n height: config.height,\n fps: config.fps,\n ...(config.audio ? { audio: config.audio } : {}),\n });\n\n let closed = false;\n const frameDuration = 1_000_000 / config.fps; // microseconds per frame\n\n const encoder = new VideoEncoder({\n output(chunk, meta) {\n if (closed) return;\n muxer.addVideoChunk(chunk, meta ?? undefined);\n },\n error(err) {\n console.error('WebCodecs encoder error:', err.message);\n },\n });\n\n encoder.configure({\n // Deliberate profile split from the fallback worker (avc1.42001f, Baseline):\n // this primary WebCodecs path targets H.264 High@4.0 for better quality up\n // to 1080p; the wasm-fallback worker uses Baseline for max decoder compat.\n codec: 'avc1.640028', // H.264 High profile, level 4.0 (supports up to 1080p)\n width: config.width,\n height: config.height,\n bitrate: bitrateForQuality(config.quality, config.width, config.height),\n framerate: config.fps,\n });\n\n return {\n async encodeFrame(bitmap: ImageBitmap, frameIndex: number): Promise<void> {\n if (closed) {\n bitmap.close();\n throw new Error('Encoder already closed');\n }\n const timestamp = Math.round(frameIndex * frameDuration);\n const frame = new VideoFrame(bitmap, { timestamp });\n const keyFrame = frameIndex % 30 === 0;\n encoder.encode(frame, { keyFrame });\n frame.close();\n bitmap.close();\n },\n\n addAudioChunk(chunk: EncodedAudioChunk, meta?: EncodedAudioChunkMetadata) {\n if (closed || !muxer.hasAudioTrack) return;\n muxer.addAudioChunk(chunk, meta);\n },\n\n async finalize(): Promise<ArrayBuffer> {\n if (closed) throw new Error('Encoder already closed');\n await encoder.flush();\n encoder.close();\n closed = true;\n return muxer.finalize();\n },\n\n close() {\n if (closed) return;\n closed = true;\n if (encoder.state !== 'closed') {\n encoder.close();\n }\n },\n };\n}\n","/**\n * Worker-backed video encoder.\n *\n * Spawns `workers/encode.worker.ts` and exposes the same shape as\n * `MainThreadEncoder` so `useVideoExport` can treat both backends\n * uniformly. Used as a fallback when WebCodecs H.264 isn't supported\n * (typical on Linux Chromium): the worker auto-selects its ffmpeg.wasm\n * path in that case.\n */\n\nimport type { MainThreadEncoder, EncoderConfig } from './mainThreadEncoder.js';\nimport type { MainToWorkerMessage, WorkerToMainMessage } from './workers/workerTypes.js';\nimport type { FfmpegWasmLoadConfig } from '@bendyline/squisq-video';\nimport { validateVideoExportOptions } from '@bendyline/squisq-video';\n\n/** Resolves once the worker has reported which backend it picked. */\nexport interface WorkerEncoder extends MainThreadEncoder {\n /** Backend the worker selected ('webcodecs' or 'ffmpeg-wasm'). */\n readonly ready: Promise<'webcodecs' | 'ffmpeg-wasm'>;\n}\n\nexport function createWorkerEncoder(\n config: EncoderConfig & { ffmpegWasm?: FfmpegWasmLoadConfig },\n): WorkerEncoder {\n validateVideoExportOptions(config);\n\n const worker = new Worker(new URL('./workers/encode.worker.js', import.meta.url), {\n type: 'module',\n });\n\n let state: 'open' | 'finalizing' | 'closed' = 'open';\n let fatalError: Error | null = null;\n let finalizeResolve: ((buffer: ArrayBuffer) => void) | null = null;\n let finalizeReject: ((err: Error) => void) | null = null;\n let readyResolve: ((backend: 'webcodecs' | 'ffmpeg-wasm') => void) | null = null;\n let readyReject: ((err: Error) => void) | null = null;\n let readySettled = false;\n const frameWaiters = new Map<\n number,\n { promise: Promise<void>; resolve: () => void; reject: (err: Error) => void }\n >();\n\n const ready = new Promise<'webcodecs' | 'ffmpeg-wasm'>((resolve, reject) => {\n readyResolve = resolve;\n readyReject = reject;\n });\n\n const frameDuration = 1_000_000 / config.fps; // microseconds per frame\n\n function post(msg: MainToWorkerMessage, transfer?: Transferable[]) {\n worker.postMessage(msg, transfer ?? []);\n }\n\n // Keep async lifecycle checks observable to TypeScript: event handlers can\n // change state while finalize() is awaiting pending frame acknowledgements.\n const currentState = () => state;\n\n worker.onmessage = (event: MessageEvent<WorkerToMainMessage>) => {\n const msg = event.data;\n switch (msg.type) {\n case 'capabilities':\n readySettled = true;\n readyResolve?.(msg.backend);\n readyResolve = readyReject = null;\n break;\n case 'frame-complete': {\n const waiter = frameWaiters.get(msg.frameIndex);\n waiter?.resolve();\n frameWaiters.delete(msg.frameIndex);\n break;\n }\n case 'complete':\n state = 'closed';\n finalizeResolve?.(msg.data);\n finalizeResolve = finalizeReject = null;\n worker.terminate();\n break;\n case 'error': {\n const err = new Error(msg.message);\n fatalError = err;\n state = 'closed';\n readySettled = true;\n readyReject?.(err);\n finalizeReject?.(err);\n for (const waiter of frameWaiters.values()) waiter.reject(err);\n frameWaiters.clear();\n readyResolve = readyReject = null;\n finalizeResolve = finalizeReject = null;\n worker.terminate();\n break;\n }\n // 'progress' is informational; ignored at this layer\n }\n };\n\n worker.onerror = (event) => {\n const err = new Error(event.message || 'Worker error');\n fatalError = err;\n state = 'closed';\n readySettled = true;\n readyReject?.(err);\n finalizeReject?.(err);\n for (const waiter of frameWaiters.values()) waiter.reject(err);\n frameWaiters.clear();\n readyResolve = readyReject = null;\n finalizeResolve = finalizeReject = null;\n worker.terminate();\n };\n\n post({\n type: 'init',\n width: config.width,\n height: config.height,\n fps: config.fps,\n quality: config.quality,\n ...(config.ffmpegWasm ? { ffmpegWasm: config.ffmpegWasm } : {}),\n });\n\n return {\n ready,\n\n encodeFrame(bitmap: ImageBitmap, frameIndex: number): Promise<void> {\n if (state !== 'open' || fatalError) {\n bitmap.close();\n return Promise.reject(fatalError ?? new Error('Encoder is not accepting frames'));\n }\n if (frameWaiters.has(frameIndex)) {\n bitmap.close();\n return Promise.reject(new Error(`Frame ${frameIndex} was submitted more than once`));\n }\n let resolveFrame!: () => void;\n let rejectFrame!: (err: Error) => void;\n const promise = new Promise<void>((resolve, reject) => {\n resolveFrame = resolve;\n rejectFrame = reject;\n });\n frameWaiters.set(frameIndex, { promise, resolve: resolveFrame, reject: rejectFrame });\n const timestamp = Math.round(frameIndex * frameDuration);\n post({ type: 'frame', bitmap, frameIndex, timestamp }, [bitmap]);\n return promise;\n },\n\n async finalize(): Promise<ArrayBuffer> {\n if (state !== 'open') throw new Error('Encoder already closed or finalizing');\n if (fatalError) throw fatalError;\n state = 'finalizing';\n await Promise.all(Array.from(frameWaiters.values(), (waiter) => waiter.promise));\n if (currentState() === 'closed') {\n throw fatalError ?? new Error('Encoder closed during finalization');\n }\n return new Promise<ArrayBuffer>((resolve, reject) => {\n finalizeResolve = resolve;\n finalizeReject = reject;\n post({ type: 'finalize' });\n });\n },\n\n close() {\n if (state === 'closed') return;\n state = 'closed';\n const err = new Error('Encoder closed');\n if (!readySettled) {\n readySettled = true;\n readyReject?.(err);\n }\n finalizeReject?.(err);\n for (const waiter of frameWaiters.values()) waiter.reject(err);\n frameWaiters.clear();\n readyResolve = readyReject = null;\n finalizeResolve = finalizeReject = null;\n post({ type: 'cancel' });\n worker.terminate();\n },\n };\n}\n","/**\n * audioTrack — In-browser audio rendering + AAC encoding for MP4 export.\n *\n * Replaces the CLI's ffmpeg `adelay`/`atrim`/`amix` graph with browser-native\n * primitives:\n *\n * - {@link supportsWebCodecsAac} probes whether the browser can encode AAC\n * (WebCodecs `AudioEncoder`).\n * - {@link renderAudioTimeline} mixes an {@link AudioTimelineClip} list into a\n * single `AudioBuffer` using an `OfflineAudioContext` (decode → schedule →\n * render).\n * - {@link encodeAacTrack} feeds that buffer through a WebCodecs\n * `AudioEncoder` and hands the chunks to an mp4-muxer audio track (tier 1).\n * - {@link audioBufferToWav} + {@link muxAudioWithFfmpegWasm} cover the\n * ffmpeg.wasm fallback (tier 2).\n * - {@link selectAudioTier} is the pure capability→tier decision shared with\n * `useVideoExport` (and unit-tested at the module boundary).\n */\n\nimport {\n ffmpegAudioMuxArgs,\n type AudioTimelineClip,\n type FfmpegWasmLoadConfig,\n} from '@bendyline/squisq-video';\n\n/** Sample rate the export renders + encodes audio at. */\nexport const EXPORT_AUDIO_SAMPLE_RATE = 48_000;\n/** Channel count the export renders + encodes audio at (stereo). */\nexport const EXPORT_AUDIO_CHANNELS = 2;\n\n/** Tier-3 reason used when AAC encoding is impossible and no ffmpeg fallback. */\nexport const REASON_NO_AAC_NO_SAB =\n 'This browser cannot encode AAC audio (WebCodecs AudioEncoder unavailable) ' +\n 'and the ffmpeg.wasm fallback requires cross-origin isolation (SharedArrayBuffer).';\n\n// ── Capability probe ───────────────────────────────────────────────\n\n/**\n * Whether the browser can encode AAC (`mp4a.40.2`) via WebCodecs.\n * Returns false when `AudioEncoder` is undefined (e.g. Node/jsdom, Safari,\n * older Chromium) or the config is unsupported.\n */\nexport async function supportsWebCodecsAac(\n sampleRate: number = EXPORT_AUDIO_SAMPLE_RATE,\n channels: number = EXPORT_AUDIO_CHANNELS,\n): Promise<boolean> {\n if (typeof AudioEncoder === 'undefined') return false;\n try {\n const support = await AudioEncoder.isConfigSupported({\n codec: 'mp4a.40.2',\n sampleRate,\n numberOfChannels: channels,\n bitrate: 128_000,\n });\n return support.supported === true;\n } catch {\n return false;\n }\n}\n\n// ── Tier selection (pure) ──────────────────────────────────────────\n\nexport interface AudioTierInputs {\n /** Whether the doc has any audio timeline clips to include at all. */\n hasClips: boolean;\n /** Whether WebCodecs AAC encoding is available. */\n aacSupported: boolean;\n /** Whether `SharedArrayBuffer` (ffmpeg.wasm path) is available. */\n sharedArrayBufferAvailable: boolean;\n /** Whether the main-thread WebCodecs H.264 encoder is in use (tier-1 host). */\n canUseMainThreadWebCodecs: boolean;\n}\n\nexport interface AudioTierDecision {\n /**\n * 1 → inline AAC into the video muxer.\n * 2 → finalize video-only then ffmpeg.wasm mux pass.\n * 3 → video-only (no audio).\n */\n tier: 1 | 2 | 3;\n /**\n * Reason audio will be absent, when tier 3. `null` means \"there was simply\n * no audio to include\" (not a failure). Non-null for a genuine capability\n * shortfall. Always `null` for tiers 1 and 2 (the attempt is expected to\n * succeed; the caller downgrades with a fresh reason on runtime failure).\n */\n reason: string | null;\n}\n\n/**\n * Decide which audio tier to run from capability booleans. Pure — the single\n * source of truth for tier selection, shared with `useVideoExport`.\n */\nexport function selectAudioTier(inputs: AudioTierInputs): AudioTierDecision {\n if (!inputs.hasClips) {\n return { tier: 3, reason: null };\n }\n if (inputs.aacSupported && inputs.canUseMainThreadWebCodecs) {\n return { tier: 1, reason: null };\n }\n if (inputs.sharedArrayBufferAvailable) {\n return { tier: 2, reason: null };\n }\n return { tier: 3, reason: REASON_NO_AAC_NO_SAB };\n}\n\n// ── OfflineAudioContext rendering ──────────────────────────────────\n\ntype OfflineCtor = new (\n numberOfChannels: number,\n length: number,\n sampleRate: number,\n) => OfflineAudioContext;\n\nfunction resolveOfflineAudioContext(): OfflineCtor {\n const g = globalThis as unknown as {\n OfflineAudioContext?: OfflineCtor;\n webkitOfflineAudioContext?: OfflineCtor;\n };\n const ctor = g.OfflineAudioContext ?? g.webkitOfflineAudioContext;\n if (!ctor) {\n throw new Error('OfflineAudioContext is not available in this environment.');\n }\n return ctor;\n}\n\n/**\n * Mix a list of timeline clips into one `AudioBuffer`.\n *\n * Decodes each unique source once, then schedules an `AudioBufferSourceNode`\n * per clip at its `startSec` with the trim window (`offset = sourceInSec`,\n * `duration = durationSec`). Undecodable sources are skipped rather than\n * failing the whole render.\n */\nexport async function renderAudioTimeline(\n clips: AudioTimelineClip[],\n buffers: Map<string, ArrayBuffer>,\n totalDurationSec: number,\n sampleRate: number = EXPORT_AUDIO_SAMPLE_RATE,\n): Promise<AudioBuffer> {\n const Ctor = resolveOfflineAudioContext();\n const channels = EXPORT_AUDIO_CHANNELS;\n const length = Math.max(1, Math.ceil(totalDurationSec * sampleRate));\n const ctx = new Ctor(channels, length, sampleRate);\n\n // Decode each unique source once. decodeAudioData detaches the buffer, so\n // hand it a copy to keep the caller's ArrayBuffer reusable.\n const decoded = new Map<string, AudioBuffer>();\n for (const [src, data] of buffers) {\n try {\n decoded.set(src, await ctx.decodeAudioData(data.slice(0)));\n } catch {\n // Skip undecodable sources; the clip is simply omitted.\n }\n }\n\n for (const clip of clips) {\n const buffer = decoded.get(clip.src);\n if (!buffer) continue;\n const node = ctx.createBufferSource();\n node.buffer = buffer;\n node.connect(ctx.destination);\n const when = Math.max(0, clip.startSec);\n const offset = Math.max(0, clip.sourceInSec);\n const duration = Math.max(0, clip.durationSec);\n node.start(when, offset, duration);\n }\n\n return ctx.startRendering();\n}\n\n// ── WebCodecs AAC encode (tier 1) ──────────────────────────────────\n\n/** Minimal muxer surface {@link encodeAacTrack} needs. */\nexport interface AudioChunkSink {\n addAudioChunk(chunk: EncodedAudioChunk, meta?: EncodedAudioChunkMetadata): void;\n}\n\n/**\n * Encode an `AudioBuffer` to AAC via WebCodecs and feed the chunks to `sink`.\n * Resolves once every chunk has been flushed to the muxer. Throws only if the\n * `AudioEncoder` errors — the caller wraps this so audio failure never aborts\n * the video export.\n */\nexport async function encodeAacTrack(\n audioBuffer: AudioBuffer,\n sink: AudioChunkSink,\n bitrate: number,\n): Promise<void> {\n if (typeof AudioEncoder === 'undefined' || typeof AudioData === 'undefined') {\n throw new Error('WebCodecs AudioEncoder is not available.');\n }\n\n const sampleRate = audioBuffer.sampleRate;\n const channels = audioBuffer.numberOfChannels;\n\n let encodeError: Error | null = null;\n const encoder = new AudioEncoder({\n output: (chunk, meta) => sink.addAudioChunk(chunk, meta ?? undefined),\n error: (err) => {\n encodeError = err instanceof Error ? err : new Error(String(err));\n },\n });\n\n encoder.configure({ codec: 'mp4a.40.2', sampleRate, numberOfChannels: channels, bitrate });\n\n const FRAME = 1024; // samples per AudioData chunk\n const total = audioBuffer.length;\n const channelData: Float32Array[] = [];\n for (let ch = 0; ch < channels; ch++) {\n channelData.push(audioBuffer.getChannelData(ch));\n }\n\n for (let offset = 0; offset < total; offset += FRAME) {\n if (encodeError) break;\n const count = Math.min(FRAME, total - offset);\n // Planar layout: all of channel 0, then all of channel 1, …\n const planar = new Float32Array(count * channels);\n for (let ch = 0; ch < channels; ch++) {\n planar.set(channelData[ch].subarray(offset, offset + count), ch * count);\n }\n const timestamp = Math.round((offset / sampleRate) * 1_000_000);\n const audioData = new AudioData({\n format: 'f32-planar',\n sampleRate,\n numberOfFrames: count,\n numberOfChannels: channels,\n timestamp,\n data: planar,\n });\n encoder.encode(audioData);\n audioData.close();\n }\n\n await encoder.flush();\n encoder.close();\n if (encodeError) throw encodeError;\n}\n\n// ── WAV serialization (tier 2 input) ───────────────────────────────\n\n/**\n * Serialize an `AudioBuffer` to a 16-bit PCM WAV byte array. Used to hand the\n * rendered audio to the ffmpeg.wasm fallback for muxing.\n */\nexport function audioBufferToWav(buffer: AudioBuffer): Uint8Array {\n const channels = buffer.numberOfChannels;\n const sampleRate = buffer.sampleRate;\n const frames = buffer.length;\n const bytesPerSample = 2;\n const blockAlign = channels * bytesPerSample;\n const dataSize = frames * blockAlign;\n const out = new ArrayBuffer(44 + dataSize);\n const view = new DataView(out);\n\n const writeStr = (offset: number, str: string) => {\n for (let i = 0; i < str.length; i++) view.setUint8(offset + i, str.charCodeAt(i));\n };\n\n writeStr(0, 'RIFF');\n view.setUint32(4, 36 + dataSize, true);\n writeStr(8, 'WAVE');\n writeStr(12, 'fmt ');\n view.setUint32(16, 16, true); // PCM chunk size\n view.setUint16(20, 1, true); // PCM format\n view.setUint16(22, channels, true);\n view.setUint32(24, sampleRate, true);\n view.setUint32(28, sampleRate * blockAlign, true); // byte rate\n view.setUint16(32, blockAlign, true);\n view.setUint16(34, bytesPerSample * 8, true);\n writeStr(36, 'data');\n view.setUint32(40, dataSize, true);\n\n const channelData: Float32Array[] = [];\n for (let ch = 0; ch < channels; ch++) channelData.push(buffer.getChannelData(ch));\n\n let pos = 44;\n for (let i = 0; i < frames; i++) {\n for (let ch = 0; ch < channels; ch++) {\n let sample = channelData[ch][i];\n sample = Math.max(-1, Math.min(1, sample));\n view.setInt16(pos, sample < 0 ? sample * 0x8000 : sample * 0x7fff, true);\n pos += 2;\n }\n }\n\n return new Uint8Array(out);\n}\n\n// ── ffmpeg.wasm mux fallback (tier 2) ──────────────────────────────\n\n/**\n * Mux a pre-encoded video-only MP4 with a WAV audio track in a single\n * ffmpeg.wasm pass (`-c:v copy -c:a aac -af apad -shortest`). Requires\n * `SharedArrayBuffer` (cross-origin isolation). Returns the muxed MP4 bytes.\n */\nexport async function muxAudioWithFfmpegWasm(\n videoMp4: Uint8Array,\n wav: Uint8Array,\n audioBitrate: number,\n loadConfig?: FfmpegWasmLoadConfig,\n): Promise<Uint8Array> {\n const { FFmpeg } = await import('@ffmpeg/ffmpeg');\n const ffmpeg = new FFmpeg();\n try {\n await ffmpeg.load({\n ...loadConfig,\n classWorkerURL:\n loadConfig?.classWorkerURL ??\n new URL('./workers/ffmpeg.class-worker.js', import.meta.url).href,\n });\n await ffmpeg.writeFile('video.mp4', videoMp4);\n await ffmpeg.writeFile('audio.wav', wav);\n const exitCode = await ffmpeg.exec([\n '-i',\n 'video.mp4',\n '-i',\n 'audio.wav',\n '-c:v',\n 'copy',\n ...ffmpegAudioMuxArgs(audioBitrate),\n 'out.mp4',\n ]);\n if (exitCode !== 0) {\n throw new Error(`ffmpeg.wasm audio mux failed with exit code ${exitCode}`);\n }\n const data = await ffmpeg.readFile('out.mp4');\n return data instanceof Uint8Array ? data : new TextEncoder().encode(data as string);\n } finally {\n ffmpeg.terminate();\n }\n}\n","/**\n * Animated GIF finalization for the browser export path.\n *\n * The frame-capture pipeline already produces a compact, video-only MP4. For\n * the GIF MVP we use that MP4 as a bounded-memory intermediate, then run a\n * palette generation/application pass through ffmpeg.wasm.\n */\n\nimport {\n ffmpegGifOutputArgs,\n type FfmpegWasmLoadConfig,\n type GifOutputOptions,\n} from '@bendyline/squisq-video';\n\n/** Build the deterministic ffmpeg arguments used for MP4 -> animated GIF. */\nexport function buildGifFfmpegArgs(options: GifOutputOptions): string[] {\n return ['-y', '-i', 'video.mp4', ...ffmpegGifOutputArgs(options), 'out.gif'];\n}\n\n/**\n * Convert a video-only MP4 into a looping animated GIF with a generated\n * palette. The ffmpeg runtime is always terminated, including on failure.\n */\nexport async function transcodeMp4ToGifWithFfmpegWasm(\n videoMp4: Uint8Array,\n options: GifOutputOptions,\n loadConfig?: FfmpegWasmLoadConfig,\n signal?: AbortSignal,\n): Promise<Uint8Array> {\n if (videoMp4.byteLength === 0) {\n throw new Error('Cannot create an animated GIF from an empty MP4.');\n }\n if (signal?.aborted) {\n throw new DOMException('Animated GIF export was cancelled.', 'AbortError');\n }\n\n const args = buildGifFfmpegArgs(options);\n const { FFmpeg } = await import('@ffmpeg/ffmpeg');\n const ffmpeg = new FFmpeg();\n let terminated = false;\n const terminate = () => {\n if (terminated) return;\n terminated = true;\n ffmpeg.terminate();\n };\n const handleAbort = () => terminate();\n signal?.addEventListener('abort', handleAbort, { once: true });\n try {\n await ffmpeg.load({\n ...loadConfig,\n classWorkerURL:\n loadConfig?.classWorkerURL ??\n new URL('./workers/ffmpeg.class-worker.js', import.meta.url).href,\n });\n if (signal?.aborted) {\n throw new DOMException('Animated GIF export was cancelled.', 'AbortError');\n }\n await ffmpeg.writeFile('video.mp4', videoMp4);\n if (signal?.aborted) {\n throw new DOMException('Animated GIF export was cancelled.', 'AbortError');\n }\n const exitCode = await ffmpeg.exec(args);\n if (signal?.aborted) {\n throw new DOMException('Animated GIF export was cancelled.', 'AbortError');\n }\n if (exitCode !== 0) {\n throw new Error(`ffmpeg.wasm GIF transcode failed with exit code ${exitCode}`);\n }\n const data = await ffmpeg.readFile('out.gif');\n return data instanceof Uint8Array ? data : new TextEncoder().encode(data as string);\n } finally {\n signal?.removeEventListener('abort', handleAbort);\n terminate();\n }\n}\n","/**\n * useFrameCapture — Hidden div + html2canvas frame capture.\n *\n * Mounts a DocPlayer in renderMode inside a hidden div (same document),\n * then captures individual frames by seeking the player and rendering\n * the DOM to a canvas via html2canvas.\n *\n * Uses React directly — no script injection, no iframes, no eval.\n *\n * Returns an ImageBitmap for each frame (transferable to a Worker).\n */\n\nimport { createElement } from 'react';\nimport { createRoot, type Root } from 'react-dom/client';\nimport { useRef, useCallback, useMemo } from 'react';\nimport type { Doc, MediaProvider } from '@bendyline/squisq/schemas';\nimport type { RenderHtmlOptions } from '@bendyline/squisq-video';\nimport { DocPlayer, MediaContext } from '@bendyline/squisq-react';\nimport type { SquisqRenderAPI, CaptionMode, CaptionStyle } from '@bendyline/squisq-react';\nimport html2canvas from 'html2canvas';\n\nexport interface FrameCaptureHandle {\n /** Initialize the hidden player. Returns the video duration in seconds. */\n init: (\n doc: Doc,\n renderOptions: Omit<RenderHtmlOptions, 'playerScript'>,\n captionMode?: CaptionMode,\n ) => Promise<number>;\n /** Capture a single frame at the given time (seconds). Returns an ImageBitmap. */\n captureFrame: (time: number) => Promise<ImageBitmap>;\n /** Clean up resources. */\n destroy: () => void;\n}\n\n/** Extension → MIME type map (hoisted to avoid per-image allocation). */\nconst MIME_MAP: Record<string, string> = {\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n png: 'image/png',\n gif: 'image/gif',\n webp: 'image/webp',\n svg: 'image/svg+xml',\n bmp: 'image/bmp',\n avif: 'image/avif',\n};\n\n/** Convert an ArrayBuffer to a base64 data URI using chunked encoding (O(n)). */\nfunction arrayBufferToDataUrl(buffer: ArrayBuffer, mime: string): string {\n const bytes = new Uint8Array(buffer);\n const chunks: string[] = [];\n const CHUNK = 8192;\n for (let i = 0; i < bytes.length; i += CHUNK) {\n chunks.push(String.fromCharCode(...bytes.subarray(i, i + CHUNK)));\n }\n return `data:${mime};base64,${btoa(chunks.join(''))}`;\n}\n\n/**\n * Create an inline MediaProvider from a map of paths to ArrayBuffers.\n */\nfunction createInlineProvider(images: Map<string, ArrayBuffer>): MediaProvider {\n const dataUrls = new Map<string, string>();\n const mimeTypes = new Map<string, string>();\n for (const [path, buffer] of images) {\n const ext = path.split('.').pop()?.toLowerCase() ?? '';\n const mime = MIME_MAP[ext] ?? 'application/octet-stream';\n dataUrls.set(path, arrayBufferToDataUrl(buffer, mime));\n mimeTypes.set(path, mime);\n }\n\n return {\n async resolveUrl(relativePath: string): Promise<string> {\n return dataUrls.get(relativePath) ?? relativePath;\n },\n async listMedia() {\n return [...dataUrls.keys()].map((name) => ({\n name,\n mimeType: mimeTypes.get(name) ?? 'application/octet-stream',\n size: 0,\n }));\n },\n async addMedia() {\n throw new Error('Read-only');\n },\n async removeMedia() {\n throw new Error('Read-only');\n },\n dispose() {},\n };\n}\n\n/**\n * Hook that manages a hidden div for frame capture.\n */\nexport function useFrameCapture(): FrameCaptureHandle {\n const containerRef = useRef<HTMLDivElement | null>(null);\n const rootRef = useRef<Root | null>(null);\n const renderAPIRef = useRef<SquisqRenderAPI | null>(null);\n const dimensionsRef = useRef<{ width: number; height: number }>({ width: 1920, height: 1080 });\n\n const init = useCallback(\n async (\n doc: Doc,\n renderOptions: Omit<RenderHtmlOptions, 'playerScript'>,\n captionMode?: CaptionMode,\n ): Promise<number> => {\n // Clean up any existing container.\n // Defer unmount to avoid \"synchronously unmount a root while React\n // was already rendering\" when init() is called from a React handler.\n if (rootRef.current || containerRef.current) {\n const oldRoot = rootRef.current;\n const oldContainer = containerRef.current;\n rootRef.current = null;\n containerRef.current = null;\n renderAPIRef.current = null;\n await new Promise<void>((resolve) => {\n setTimeout(() => {\n if (oldRoot) oldRoot.unmount();\n if (oldContainer) oldContainer.remove();\n resolve();\n }, 0);\n });\n }\n\n const width = renderOptions.width ?? 1920;\n const height = renderOptions.height ?? 1080;\n const animationsEnabled = renderOptions.animationsEnabled ?? true;\n dimensionsRef.current = { width, height };\n\n // Create a hidden container\n const container = document.createElement('div');\n container.style.cssText =\n `position:fixed;left:0;top:0;width:${width}px;height:${height}px;` +\n 'opacity:0;pointer-events:none;z-index:-1;overflow:hidden;';\n document.body.appendChild(container);\n containerRef.current = container;\n\n // Create render root\n const renderRoot = document.createElement('div');\n renderRoot.id = 'squisq-capture-root';\n renderRoot.style.cssText = `width:${width}px;height:${height}px;`;\n container.appendChild(renderRoot);\n\n // Build media provider from images\n const mediaProvider = renderOptions.images\n ? createInlineProvider(renderOptions.images)\n : null;\n\n // Mount DocPlayer in renderMode via React\n const root = createRoot(renderRoot);\n rootRef.current = root;\n\n // Derive caption props from captionMode\n const captionsEnabled = captionMode !== undefined && captionMode !== 'off';\n const captionStyle: CaptionStyle = captionMode === 'social' ? 'social' : 'standard';\n let resolveRenderAPI!: (api: SquisqRenderAPI) => void;\n const renderAPIReady = new Promise<SquisqRenderAPI>((resolve) => {\n resolveRenderAPI = resolve;\n });\n\n const playerElement = createElement(DocPlayer, {\n doc,\n basePath: '.',\n renderMode: true,\n animationsEnabled,\n showControls: false,\n autoPlay: false,\n forceViewport: { width, height, name: 'export' },\n captionsEnabled,\n captionStyle,\n onRenderAPIReady: (api: SquisqRenderAPI | null) => {\n if (containerRef.current !== container) return;\n renderAPIRef.current = api;\n if (api) resolveRenderAPI(api);\n },\n });\n\n // Defer rendering to the next microtask to avoid \"synchronously unmount\n // a root while React was already rendering\" when init() is called during\n // a React render cycle (e.g., from startExport in VideoExportModal).\n await new Promise<void>((resolve) => setTimeout(resolve, 0));\n\n if (mediaProvider) {\n root.render(createElement(MediaContext.Provider, { value: mediaProvider }, playerElement));\n } else {\n root.render(playerElement);\n }\n\n // Wait for this exact player's instance API.\n return new Promise<number>((resolve, reject) => {\n const timeout = setTimeout(() => {\n const api = renderAPIRef.current;\n const hasSeek = typeof api?.seekTo === 'function';\n const hasDur = typeof api?.getDuration === 'function';\n const rootEl = containerRef.current?.querySelector('#squisq-capture-root');\n const hasPlayer = rootEl ? rootEl.querySelector('.doc-player') !== null : false;\n reject(\n new Error(\n `Render API did not initialize within 15s. ` +\n `seekTo=${hasSeek}, getDuration=${hasDur}, player=${hasPlayer}, root=${!!rootEl}`,\n ),\n );\n }, 15000);\n\n void renderAPIReady.then((api) => {\n clearTimeout(timeout);\n resolve(api.getDuration());\n });\n });\n },\n [],\n );\n\n const captureFrame = useCallback(async (time: number): Promise<ImageBitmap> => {\n const container = containerRef.current;\n const api = renderAPIRef.current;\n if (!container || !api) {\n throw new Error('Frame capture not initialized — call init() first');\n }\n\n const { width, height } = dimensionsRef.current;\n\n // Seek the player to the target time\n await api.seekTo(time);\n\n // Wait for the DOM to update after seek\n await new Promise<void>((resolve) =>\n requestAnimationFrame(() => requestAnimationFrame(() => resolve())),\n );\n\n const root = container.querySelector('#squisq-capture-root') as HTMLElement;\n if (!root) {\n throw new Error('Capture root element not found');\n }\n\n // Render the DOM to a canvas via html2canvas.\n // We're in the same document (no iframe), so COEP doesn't block cloning.\n const canvas = await html2canvas(root, {\n width,\n height,\n scale: 1,\n useCORS: true,\n allowTaint: true,\n backgroundColor: '#000000',\n logging: false,\n });\n\n // Convert to ImageBitmap (transferable to worker — zero-copy)\n const bitmap = await createImageBitmap(canvas);\n return bitmap;\n }, []);\n\n const destroy = useCallback(() => {\n if (rootRef.current) {\n rootRef.current.unmount();\n rootRef.current = null;\n }\n if (containerRef.current) {\n containerRef.current.remove();\n containerRef.current = null;\n }\n renderAPIRef.current = null;\n }, []);\n\n // Return a stable object to prevent useEffect cleanup loops\n // in consumers that depend on the handle reference.\n return useMemo(() => ({ init, captureFrame, destroy }), [init, captureFrame, destroy]);\n}\n","/**\n * VideoExportButton — Simple button that opens the VideoExportModal in a portal.\n *\n * Convenience wrapper for consumers who want a drop-in button.\n * The modal renders via `createPortal` into `document.body`.\n */\n\nimport { useState, useCallback } from 'react';\nimport { createPortal } from 'react-dom';\nimport type { Doc } from '@bendyline/squisq/schemas';\nimport type { MediaProvider } from '@bendyline/squisq/schemas';\nimport { VideoExportModal } from './VideoExportModal.js';\nimport type { VideoExportConfig } from './hooks/useVideoExport.js';\n\nexport interface VideoExportButtonProps {\n /** The document to export */\n doc: Doc;\n /**\n * Player IIFE bundle source. Unused by the browser export path (frames\n * are captured from a live in-page DocPlayer); only forwarded for\n * CLI/Playwright-style pipelines that render standalone HTML.\n */\n playerScript?: string;\n /** Optional media provider for resolving images/audio */\n mediaProvider?: MediaProvider;\n /** Pre-collected images map */\n images?: Map<string, ArrayBuffer>;\n /** Pre-collected audio map */\n audio?: Map<string, ArrayBuffer>;\n /**\n * Seeds the modal's initial output format and export settings and is merged\n * as a base into the config passed to the export hook. Forwarded to\n * {@link VideoExportModal}.\n */\n defaultConfig?: Partial<VideoExportConfig>;\n /** Visual color scheme forwarded to the portaled modal. Defaults to light. */\n colorScheme?: 'light' | 'dark';\n /** Button label (defaults to \"Export Video\", or \"Export GIF\" for a GIF default config) */\n label?: string;\n /** Additional inline styles for the button */\n style?: React.CSSProperties;\n /** Whether the button is disabled */\n disabled?: boolean;\n}\n\nexport function VideoExportButton({\n doc,\n playerScript,\n mediaProvider,\n images,\n audio,\n defaultConfig,\n colorScheme,\n label,\n style,\n disabled,\n}: VideoExportButtonProps) {\n const [showModal, setShowModal] = useState(false);\n const resolvedLabel =\n label ?? (defaultConfig?.outputFormat === 'gif' ? 'Export GIF' : 'Export Video');\n\n const handleOpen = useCallback(() => setShowModal(true), []);\n const handleClose = useCallback(() => setShowModal(false), []);\n\n return (\n <>\n <button onClick={handleOpen} disabled={disabled} style={style}>\n {resolvedLabel}\n </button>\n\n {showModal &&\n createPortal(\n <VideoExportModal\n doc={doc}\n playerScript={playerScript}\n mediaProvider={mediaProvider}\n images={images}\n audio={audio}\n defaultConfig={defaultConfig}\n colorScheme={colorScheme}\n onClose={handleClose}\n />,\n document.body,\n )}\n </>\n );\n}\n"],"mappings":"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","names":["useState","useCallback","useRef","useCallback","validateVideoExportOptions","useRef","useCallback","url","useState","useCallback","useState","useCallback","Fragment","jsx","jsxs","useState","useCallback"]}