@twick/browser-render 0.15.9 → 0.15.10

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/index.ts","../src/browser-renderer.ts","../src/audio/video-audio-extractor.ts","../src/audio/audio-processor.ts","../src/audio/audio-video-muxer.ts","../src/hooks/use-browser-renderer.ts"],"sourcesContent":["/**\n * @twick/browser-render\n * Browser-native video rendering using WebCodecs API\n */\n\n// Main browser renderer functions\nexport { renderTwickVideoInBrowser, downloadVideoBlob } from './browser-renderer';\n\n// React hook\nexport { useBrowserRenderer } from './hooks/use-browser-renderer';\n\n// Type definitions\nexport type { BrowserRenderConfig } from './browser-renderer';\nexport type { \n UseBrowserRendererOptions, \n UseBrowserRendererReturn \n} from './hooks/use-browser-renderer';\n\n// Set as default export\nexport { renderTwickVideoInBrowser as default } from './browser-renderer';\n","import { Renderer, Vector2 } from \"@twick/core\";\nimport type { Project, RendererSettings } from \"@twick/core\";\nimport defaultProject from \"@twick/visualizer/dist/project.js\";\nimport { hasAudio } from \"@twick/media-utils\";\nimport { BrowserAudioProcessor, getAssetPlacement, type AssetInfo } from './audio/audio-processor';\nimport { muxAudioVideo } from './audio/audio-video-muxer';\n\n/**\n * Browser-native video exporter using WebCodecs API\n * This exporter downloads the video directly in the browser without any server interaction\n */\nclass BrowserWasmExporter {\n public static readonly id = '@twick/core/wasm';\n public static readonly displayName = 'Browser Video (Wasm)';\n\n private encoder: any;\n private videoBlob: Blob | null = null;\n private onProgressCallback?: (progress: number) => void;\n private currentFrame: number = 0;\n private fps: number = 30;\n\n public static async create(settings: RendererSettings) {\n return new BrowserWasmExporter(settings);\n }\n\n public constructor(\n private readonly settings: RendererSettings,\n ) {\n this.fps = settings.fps || 30;\n }\n\n public async start(): Promise<void> {\n try {\n // Import mp4-wasm\n const loadMp4Module = (await import('mp4-wasm')).default;\n \n // Try multiple locations to fetch the WASM file\n const possiblePaths = [\n // Vite dev server virtual path\n '/@mp4-wasm',\n // Common bundled asset paths (Vite uses hashed names)\n '/assets/mp4-wasm.wasm',\n '/assets/mp4-YBRi_559.wasm', // Known Vite hash\n '/mp4-wasm.wasm',\n // Node modules path (for dev)\n '/node_modules/mp4-wasm/dist/mp4-wasm.wasm',\n ];\n \n let buffer: ArrayBuffer | null = null;\n let successPath = '';\n \n for (const path of possiblePaths) {\n try {\n const resp = await fetch(path);\n if (resp.ok) {\n const contentType = resp.headers.get('content-type');\n // Make sure we got a WASM file, not HTML\n if (contentType && contentType.includes('html')) {\n continue;\n }\n buffer = await resp.arrayBuffer();\n successPath = path;\n break;\n }\n } catch (e) {\n continue;\n }\n }\n \n if (!buffer) {\n throw new Error(\n 'Could not load WASM file from any location. ' +\n 'Please copy mp4-wasm.wasm to your public directory or configure Vite to serve it.'\n );\n }\n \n const mp4 = await loadMp4Module({ wasmBinary: buffer });\n\n this.encoder = mp4.createWebCodecsEncoder({\n width: this.settings.size.x,\n height: this.settings.size.y,\n fps: this.fps,\n });\n } catch (error) {\n throw error;\n }\n }\n\n public async handleFrame(canvas: HTMLCanvasElement, frameNumber?: number): Promise<void> {\n const frameIndex = frameNumber !== undefined ? frameNumber : this.currentFrame;\n const timestampMicroseconds = Math.round((frameIndex / this.fps) * 1_000_000);\n \n const frame = new VideoFrame(canvas, { \n timestamp: timestampMicroseconds,\n duration: Math.round((1 / this.fps) * 1_000_000)\n });\n \n await this.encoder.addFrame(frame);\n frame.close();\n \n if (frameNumber === undefined) {\n this.currentFrame++;\n }\n }\n\n public async stop(): Promise<void> {\n const buf = await this.encoder.end();\n this.videoBlob = new Blob([buf], { type: 'video/mp4' });\n }\n\n public async generateAudio(\n assets: AssetInfo[][],\n startFrame: number,\n endFrame: number,\n ): Promise<ArrayBuffer | null> {\n try {\n console.log(`Generating audio from ${assets.length} frames`);\n const processor = new BrowserAudioProcessor();\n const assetPlacements = getAssetPlacement(assets);\n\n if (assetPlacements.length === 0) {\n console.log('No asset placements found');\n return null;\n }\n\n console.log(`Processing ${assetPlacements.length} asset placements`);\n const processedBuffers: AudioBuffer[] = [];\n for (let i = 0; i < assetPlacements.length; i++) {\n const asset = assetPlacements[i];\n console.log(`[${i + 1}/${assetPlacements.length}] Processing asset: ${asset.src} (type: ${asset.type}, volume: ${asset.volume}, playbackRate: ${asset.playbackRate})`);\n \n if (asset.volume > 0 && asset.playbackRate > 0) {\n const startTime = Date.now();\n try {\n // Check if this specific asset has audio before processing\n if (asset.type === 'video') {\n console.log(` → Checking if asset has audio: ${asset.src.substring(0, 50)}...`);\n try {\n const assetHasAudio = await hasAudio(asset.src);\n if (!assetHasAudio) {\n console.log(` ⏭ Skipping asset (no audio detected): ${asset.src.substring(0, 50)}...`);\n continue;\n }\n console.log(` ✓ Asset has audio, proceeding: ${asset.src.substring(0, 50)}...`);\n } catch (audioCheckError) {\n const errorMsg = audioCheckError instanceof Error ? audioCheckError.message : String(audioCheckError);\n const errorStack = audioCheckError instanceof Error ? audioCheckError.stack : undefined;\n console.warn(` ⚠ Audio check failed, proceeding anyway: ${asset.src.substring(0, 50)}...`);\n console.warn(` Error: ${errorMsg}`);\n if (errorStack) {\n console.warn(` Stack: ${errorStack}`);\n }\n // Continue processing if check fails (might have audio)\n }\n }\n \n console.log(` → Starting processAudioAsset for: ${asset.src}`);\n \n // Add timeout wrapper for processAudioAsset (30 seconds max per asset)\n const processPromise = processor.processAudioAsset(\n asset,\n this.settings.fps || 30,\n endFrame - startFrame\n );\n \n const timeoutPromise = new Promise<never>((_, reject) => {\n setTimeout(() => {\n reject(new Error(`Timeout processing audio asset after 20s - video may have no audio track`));\n }, 20000);\n });\n \n const buffer = await Promise.race([processPromise, timeoutPromise]);\n \n const duration = Date.now() - startTime;\n console.log(` ✓ Successfully processed audio asset in ${duration}ms: ${asset.src.substring(0, 50)}...`);\n processedBuffers.push(buffer);\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n const errorStack = error instanceof Error ? error.stack : undefined;\n const duration = Date.now() - startTime;\n console.warn(` ✗ Failed to process audio asset after ${duration}ms: ${asset.src.substring(0, 50)}...`);\n console.warn(` Error: ${errorMsg}`);\n if (errorStack) {\n console.warn(` Stack: ${errorStack}`);\n }\n console.warn(` Asset details: type=${asset.type}, volume=${asset.volume}, playbackRate=${asset.playbackRate}, startFrame=${asset.startInVideo}, endFrame=${asset.endInVideo}`);\n // Continue with other audio assets\n }\n } else {\n console.log(` ⏭ Skipping asset: volume=${asset.volume}, playbackRate=${asset.playbackRate}`);\n }\n }\n\n if (processedBuffers.length === 0) {\n console.warn('No audio buffers were successfully processed');\n console.warn(` Total assets attempted: ${assetPlacements.length}`);\n console.warn(` Assets with volume>0 and playbackRate>0: ${assetPlacements.filter(a => a.volume > 0 && a.playbackRate > 0).length}`);\n return null;\n }\n\n console.log(`Mixing ${processedBuffers.length} audio buffers`);\n const mixStartTime = Date.now();\n const mixedBuffer = processor.mixAudioBuffers(processedBuffers);\n const mixDuration = Date.now() - mixStartTime;\n console.log(`Audio mixing completed in ${mixDuration}ms`);\n \n const wavStartTime = Date.now();\n const wavData = processor.audioBufferToWav(mixedBuffer);\n const wavDuration = Date.now() - wavStartTime;\n console.log(`WAV conversion completed in ${wavDuration}ms`);\n console.log(`Audio generation complete: ${wavData.byteLength} bytes (${(wavData.byteLength / 1024 / 1024).toFixed(2)} MB)`);\n\n await processor.close();\n return wavData;\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n const errorStack = error instanceof Error ? error.stack : undefined;\n console.error('Audio generation error:', errorMsg);\n if (errorStack) {\n console.error('Error stack:', errorStack);\n }\n console.error('Error details:', {\n errorType: error instanceof Error ? error.constructor.name : typeof error,\n errorMessage: errorMsg,\n });\n return null;\n }\n }\n\n public async mergeMedia(): Promise<void> {\n // In browser, we don't need to merge separately\n // The video is already created with audio in the encoder\n }\n\n public async downloadVideos(assets: any[][]): Promise<void> {\n // Browser doesn't need to download source videos\n // They're already accessible via URLs\n }\n\n public getVideoBlob(): Blob | null {\n return this.videoBlob;\n }\n\n public setProgressCallback(callback: (progress: number) => void): void {\n this.onProgressCallback = callback;\n }\n}\n\n/**\n * Browser rendering configuration\n */\nexport interface BrowserRenderConfig {\n /** \n * Custom Project object\n * If not provided, defaults to @twick/visualizer project\n * \n * Note: Must be an imported Project object, not a string path.\n * String paths only work in Node.js environments (server renderer).\n * \n * Example:\n * ```typescript\n * import myProject from './my-custom-project';\n * \n * await renderTwickVideoInBrowser({\n * projectFile: myProject,\n * variables: { input: {...} }\n * });\n * ```\n */\n projectFile?: Project;\n /** Input variables containing project configuration */\n variables: {\n input: any;\n playerId?: string;\n [key: string]: any;\n };\n /** Render settings */\n settings?: {\n width?: number;\n height?: number;\n fps?: number;\n quality?: 'low' | 'medium' | 'high';\n range?: [number, number]; // [start, end] in seconds\n includeAudio?: boolean; // Enable audio processing\n downloadAudioSeparately?: boolean; // Download audio.wav separately\n onAudioReady?: (audioBlob: Blob) => void; // Callback when audio is ready\n onProgress?: (progress: number) => void;\n onComplete?: (videoBlob: Blob) => void;\n onError?: (error: Error) => void;\n };\n}\n\n/**\n * Renders a Twick video directly in the browser without requiring a server.\n * Uses WebCodecs API for encoding video frames into MP4 format.\n * \n * This function uses the same signature as the server renderer for consistency.\n *\n * @param config - Configuration object containing variables and settings\n * @param config.projectFile - Optional project file path or Project object (defaults to visualizer project)\n * @param config.variables - Variables containing input configuration (tracks, elements, etc.)\n * @param config.settings - Optional render settings (width, height, fps, etc.)\n * @returns Promise resolving to a Blob containing the rendered video\n * \n * @example\n * ```js\n * import { renderTwickVideoInBrowser } from '@twick/browser-render';\n * \n * // Using default visualizer project\n * const videoBlob = await renderTwickVideoInBrowser({\n * variables: {\n * input: {\n * properties: { width: 1920, height: 1080, fps: 30 },\n * tracks: [\n * // Your tracks configuration\n * ]\n * }\n * },\n * settings: {\n * width: 1920,\n * height: 1080,\n * fps: 30,\n * quality: 'high',\n * onProgress: (progress) => console.log(`Rendering: ${progress * 100}%`),\n * }\n * });\n * \n * // Using custom project\n * import myProject from './my-custom-project';\n * const videoBlob = await renderTwickVideoInBrowser({\n * projectFile: myProject, // Must be an imported Project object\n * variables: { input: {...} },\n * settings: {...}\n * });\n * \n * // Download the video\n * const url = URL.createObjectURL(videoBlob);\n * const a = document.createElement('a');\n * a.href = url;\n * a.download = 'video.mp4';\n * a.click();\n * URL.revokeObjectURL(url);\n * ```\n */\nexport const renderTwickVideoInBrowser = async (\n config: BrowserRenderConfig\n): Promise<Blob> => {\n // Save original methods to restore later\n const originalVideoPlay = HTMLVideoElement.prototype.play;\n const originalAudioPlay = HTMLAudioElement.prototype.play;\n const originalCreateElement = document.createElement.bind(document);\n \n // Override play methods to force muting\n HTMLVideoElement.prototype.play = function() {\n this.muted = true;\n this.volume = 0;\n return originalVideoPlay.call(this);\n };\n \n HTMLAudioElement.prototype.play = function() {\n this.muted = true;\n this.volume = 0;\n return originalAudioPlay.call(this);\n };\n \n // Override createElement to mute video/audio on creation\n document.createElement = function(tagName: string, options?: any) {\n const element = originalCreateElement(tagName, options);\n if (tagName.toLowerCase() === 'video' || tagName.toLowerCase() === 'audio') {\n (element as any).muted = true;\n (element as any).volume = 0;\n }\n return element;\n } as any;\n\n try {\n const { projectFile, variables, settings = {} } = config;\n\n if (!variables || !variables.input) {\n throw new Error('Invalid configuration. \"variables.input\" is required.');\n }\n\n const width = settings.width || variables.input.properties?.width || 1920;\n const height = settings.height || variables.input.properties?.height || 1080;\n const fps = settings.fps || variables.input.properties?.fps || 30;\n\n const project: Project = !projectFile ? defaultProject : (projectFile as Project);\n project.variables = variables as any;\n\n // Create renderer settings\n const renderSettings: RendererSettings = {\n name: 'browser-render',\n exporter: {\n name: '@twick/core/wasm',\n },\n size: new Vector2(width, height),\n resolutionScale: 1,\n colorSpace: 'srgb',\n fps: fps,\n range: settings.range || [0, Infinity],\n background: variables.input.backgroundColor || '#000000',\n ...(settings.quality && {\n quality: settings.quality,\n }),\n };\n\n const renderer = new Renderer(project);\n const exporter = await BrowserWasmExporter.create(renderSettings);\n await exporter.start();\n \n if (settings.onProgress) {\n exporter.setProgressCallback(settings.onProgress);\n }\n\n await renderer['reloadScenes'](renderSettings);\n (renderer as any).stage.configure(renderSettings);\n (renderer as any).playback.fps = renderSettings.fps;\n \n // Set playback state to Rendering (critical for video elements)\n // PlaybackState: Playing = 0, Rendering = 1, Paused = 2, Presenting = 3\n (renderer as any).playback.state = 1;\n \n const totalFrames = await renderer.getNumberOfFrames(renderSettings);\n\n if (totalFrames === 0 || !isFinite(totalFrames)) {\n throw new Error(\n 'Cannot render: Video has zero duration. ' +\n 'Please ensure your project has valid content with non-zero duration. ' +\n 'Check that all video elements have valid sources and are properly loaded.'\n );\n }\n \n const videoElements: any[] = [];\n if (variables.input.tracks) {\n variables.input.tracks.forEach((track: any) => {\n if (track.elements) {\n track.elements.forEach((el: any) => {\n if (el.type === 'video') videoElements.push(el);\n });\n }\n });\n }\n\n // Load video metadata and check for audio (required for rendering)\n let hasAnyAudio = false;\n console.log(`Found ${videoElements.length} video element(s) to check for audio`);\n if (videoElements.length > 0) {\n for (const videoEl of videoElements) {\n const src = videoEl.props?.src;\n if (!src || src === 'undefined') continue;\n \n // Load video metadata\n const preloadVideo = document.createElement('video');\n preloadVideo.crossOrigin = 'anonymous';\n preloadVideo.preload = 'metadata';\n preloadVideo.src = src;\n await new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(\n () => reject(new Error(`Timeout loading video metadata: ${src.substring(0, 80)}`)),\n 30000\n );\n preloadVideo.addEventListener('loadedmetadata', () => {\n clearTimeout(timeout);\n resolve();\n }, { once: true });\n preloadVideo.addEventListener('error', () => {\n clearTimeout(timeout);\n const err = preloadVideo.error;\n reject(new Error(`Failed to load video: ${err?.message || 'Unknown error'}`));\n }, { once: true });\n });\n \n // Check if video has audio using media-utils hasAudio function\n if (settings.includeAudio) {\n try {\n console.log(`Checking if video has audio: ${src.substring(0, 50)}...`);\n const videoHasAudio = await hasAudio(src);\n console.log(`Audio check result for ${src.substring(0, 50)}...: ${videoHasAudio ? 'HAS AUDIO' : 'NO AUDIO'}`);\n if (videoHasAudio) {\n hasAnyAudio = true;\n console.log(`✓ Video has audio: ${src.substring(0, 50)}...`);\n } else {\n console.log(`✗ Video has no audio: ${src.substring(0, 50)}...`);\n }\n } catch (error) {\n console.warn(`Failed to check audio for ${src.substring(0, 50)}...:`, error);\n // On error, assume it might have audio to be safe\n hasAnyAudio = true;\n console.log(`⚠ Assuming video might have audio due to check error`);\n }\n }\n }\n }\n\n await (renderer as any).playback.recalculate();\n await (renderer as any).playback.reset();\n await (renderer as any).playback.seek(0);\n\n const mediaAssets: AssetInfo[][] = [];\n \n for (let frame = 0; frame < totalFrames; frame++) {\n if (frame > 0) {\n await (renderer as any).playback.progress();\n }\n await (renderer as any).stage.render(\n (renderer as any).playback.currentScene,\n (renderer as any).playback.previousScene,\n );\n const currentAssets = (renderer as any).playback.currentScene.getMediaAssets?.() || [];\n mediaAssets.push(currentAssets);\n const canvas = (renderer as any).stage.finalBuffer;\n await exporter.handleFrame(canvas, frame);\n if (settings.onProgress) settings.onProgress(frame / totalFrames);\n }\n\n await exporter.stop();\n \n let audioData: ArrayBuffer | null = null;\n \n // Process audio only if we detected that videos have audio\n // This avoids unnecessary processing for videos without sound\n console.log(`Audio detection summary: hasAnyAudio=${hasAnyAudio}, includeAudio=${settings.includeAudio}, mediaAssets=${mediaAssets.length}`);\n if (settings.includeAudio && mediaAssets.length > 0 && hasAnyAudio) {\n console.log('Starting audio processing (audio detected in videos)');\n // Update progress to indicate audio processing is starting\n if (settings.onProgress) {\n settings.onProgress(0.98); // 98% - video encoding done, audio processing starting\n }\n \n try {\n console.log('Calling generateAudio...');\n audioData = await exporter.generateAudio(mediaAssets, 0, totalFrames);\n console.log('generateAudio completed');\n \n // Log audio generation result for debugging\n if (audioData) {\n console.log(`✓ Audio generation successful: ${audioData.byteLength} bytes`);\n } else {\n console.log('✗ No audio data generated');\n }\n \n // Update progress after audio generation\n if (settings.onProgress) {\n settings.onProgress(0.99); // 99% - audio generation done\n }\n } catch (audioError) {\n const errorMsg = audioError instanceof Error ? audioError.message : String(audioError);\n const errorStack = audioError instanceof Error ? audioError.stack : undefined;\n console.error('✗ Audio generation failed, continuing without audio');\n console.error(` Error: ${errorMsg}`);\n if (errorStack) {\n console.error(` Stack: ${errorStack}`);\n }\n console.error(' Context:', {\n hasAnyAudio,\n includeAudio: settings.includeAudio,\n mediaAssetsCount: mediaAssets.length,\n totalFrames,\n });\n audioData = null;\n }\n } else if (settings.includeAudio && mediaAssets.length > 0 && !hasAnyAudio) {\n // Videos have no audio - skip audio processing entirely (fast path)\n console.log('⏭ Skipping audio processing: no audio detected in videos');\n } else {\n console.log(`⏭ Skipping audio processing: includeAudio=${settings.includeAudio}, mediaAssets=${mediaAssets.length}, hasAnyAudio=${hasAnyAudio}`);\n }\n\n let finalBlob = exporter.getVideoBlob();\n if (!finalBlob) {\n throw new Error('Failed to create video blob');\n }\n\n // Validate blob has content\n if (finalBlob.size === 0) {\n throw new Error('Video blob is empty. Rendering may have failed.');\n }\n\n // Only attempt muxing if we have audio data\n // If no audio or muxing fails, return the video blob without audio\n if (audioData && settings.includeAudio) {\n console.log(`Attempting to mux audio (${audioData.byteLength} bytes) with video (${finalBlob.size} bytes)`);\n try {\n const muxedBlob = await muxAudioVideo({\n videoBlob: finalBlob,\n audioBuffer: audioData,\n });\n \n // Validate muxed blob has content\n if (!muxedBlob || muxedBlob.size === 0) {\n throw new Error('Muxed video blob is empty');\n }\n \n // Check if muxing actually worked (muxed blob should be different size)\n if (muxedBlob.size === finalBlob.size) {\n console.warn('Muxed blob size unchanged - muxing may have failed silently');\n } else {\n console.log(`Muxing successful: ${finalBlob.size} bytes -> ${muxedBlob.size} bytes`);\n }\n \n finalBlob = muxedBlob;\n } catch (muxError) {\n // If muxing fails, fall back to video without audio\n const errorMsg = muxError instanceof Error ? muxError.message : String(muxError);\n const errorStack = muxError instanceof Error ? muxError.stack : undefined;\n console.error('Audio muxing failed');\n console.error(` Error: ${errorMsg}`);\n if (errorStack) {\n console.error(` Stack: ${errorStack}`);\n }\n console.error(' Context:', {\n videoBlobSize: finalBlob.size,\n audioDataSize: audioData?.byteLength || 0,\n });\n \n // Optionally download audio separately if requested\n if (settings.downloadAudioSeparately && audioData) {\n const audioBlob = new Blob([audioData], { type: 'audio/wav' });\n const audioUrl = URL.createObjectURL(audioBlob);\n const a = document.createElement('a');\n a.href = audioUrl;\n a.download = 'audio.wav';\n a.click();\n URL.revokeObjectURL(audioUrl);\n }\n \n // Continue with original video blob without audio - don't throw error\n // The original blob should still be valid even without audio\n console.warn('Continuing with video without audio due to muxing failure');\n // Re-get the original blob to ensure we have a valid video\n finalBlob = exporter.getVideoBlob();\n if (!finalBlob || finalBlob.size === 0) {\n throw new Error('Video blob is invalid after muxing failure');\n }\n }\n } else if (settings.includeAudio && !audioData) {\n console.warn('Audio processing was enabled but no audio data was generated');\n }\n // If no audio data, finalBlob already contains the video without audio\n // This is valid - videos can exist without audio tracks\n\n // Final validation before returning\n if (!finalBlob || finalBlob.size === 0) {\n throw new Error('Final video blob is empty or invalid');\n }\n\n // Update progress to 100% before completion\n if (settings.onProgress) {\n settings.onProgress(1.0);\n }\n\n if (settings.onComplete) {\n settings.onComplete(finalBlob);\n }\n\n return finalBlob;\n } catch (error) {\n if (config.settings?.onError) {\n config.settings.onError(error as Error);\n }\n throw error;\n } finally {\n // Restore original methods\n HTMLVideoElement.prototype.play = originalVideoPlay;\n HTMLAudioElement.prototype.play = originalAudioPlay;\n document.createElement = originalCreateElement as any;\n }\n};\n\n/**\n * Helper function to download a video blob as a file\n * \n * @param videoBlob - The video blob to download\n * @param filename - The desired filename (default: 'video.mp4')\n * \n * @example\n * ```js\n * const blob = await renderTwickVideoInBrowser(projectData);\n * downloadVideoBlob(blob, 'my-video.mp4');\n * ```\n */\nexport const downloadVideoBlob = (videoBlob: Blob, filename: string = 'video.mp4'): void => {\n const url = URL.createObjectURL(videoBlob);\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n a.style.display = 'none';\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n \n // Clean up the object URL after a delay\n setTimeout(() => URL.revokeObjectURL(url), 1000);\n};\n\nexport default renderTwickVideoInBrowser;\n","/**\n * Alternative audio extraction using MediaElementAudioSourceNode\n * This captures audio directly from a playing video element without decoding\n * Works with ANY audio codec the browser can play!\n */\n\nexport class VideoElementAudioExtractor {\n private audioContext: AudioContext;\n private video: HTMLVideoElement;\n private destination: MediaStreamAudioDestinationNode | null = null;\n private mediaRecorder: MediaRecorder | null = null;\n private audioChunks: Blob[] = [];\n \n constructor(videoSrc: string, sampleRate: number = 48000) {\n this.audioContext = new AudioContext({ sampleRate });\n this.video = document.createElement('video');\n this.video.crossOrigin = 'anonymous';\n this.video.src = videoSrc;\n this.video.muted = true; // Mute playback but audio will still be captured\n }\n \n async initialize(): Promise<void> {\n return new Promise((resolve, reject) => {\n this.video.addEventListener('loadedmetadata', () => resolve(), { once: true });\n \n this.video.addEventListener('error', (e) => {\n reject(new Error(`Failed to load video for audio extraction: ${e}`));\n }, { once: true });\n });\n }\n \n /**\n * Extract audio by playing the video and capturing audio output\n */\n async extractAudio(\n startTime: number,\n duration: number,\n playbackRate: number = 1.0\n ): Promise<AudioBuffer> {\n // Check if video has audio tracks before attempting extraction\n try {\n // Try to create MediaElementSource - this will fail if video has no audio\n const source = this.audioContext.createMediaElementSource(this.video);\n \n // Create destination for recording\n this.destination = this.audioContext.createMediaStreamDestination();\n source.connect(this.destination);\n } catch (err) {\n // If we can't create the source, the video likely has no audio track\n throw new Error('Video has no audio track');\n }\n \n // Create MediaRecorder to capture audio\n this.audioChunks = [];\n let mimeType = 'audio/webm';\n \n // Check if MediaRecorder supports the mime type\n if (!MediaRecorder.isTypeSupported(mimeType)) {\n // Fallback to default\n mimeType = '';\n }\n \n try {\n this.mediaRecorder = new MediaRecorder(this.destination.stream, {\n mimeType: mimeType || undefined,\n });\n } catch (err) {\n throw new Error(`Failed to create MediaRecorder: ${err}. Video may have no audio track.`);\n }\n \n this.mediaRecorder.ondataavailable = (event) => {\n if (event.data && event.data.size > 0) {\n this.audioChunks.push(event.data);\n }\n };\n \n // Set up video playback\n this.video.currentTime = startTime;\n this.video.playbackRate = playbackRate;\n \n // Wait for seek to complete\n await new Promise<void>((resolve, reject) => {\n const seekTimeout = setTimeout(() => {\n reject(new Error('Video seek timeout'));\n }, 5000);\n \n this.video.addEventListener('seeked', () => {\n clearTimeout(seekTimeout);\n resolve();\n }, { once: true });\n \n this.video.addEventListener('error', () => {\n clearTimeout(seekTimeout);\n reject(new Error('Video seek error'));\n }, { once: true });\n });\n \n // Start recording and playing\n return new Promise((resolve, reject) => {\n const recordingTimeout = setTimeout(() => {\n this.video.pause();\n if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {\n this.mediaRecorder.stop();\n }\n reject(new Error('Audio extraction timeout - video may have no audio track'));\n }, (duration / playbackRate + 10) * 1000); // Add 10s buffer\n \n let hasData = false;\n const dataCheckInterval = setInterval(() => {\n if (this.audioChunks.length > 0 && this.audioChunks.some(chunk => chunk.size > 0)) {\n hasData = true;\n }\n }, 1000);\n \n this.mediaRecorder!.onerror = (event) => {\n clearInterval(dataCheckInterval);\n clearTimeout(recordingTimeout);\n this.video.pause();\n reject(new Error(`MediaRecorder error: ${event}. Video may have no audio track.`));\n };\n \n try {\n this.mediaRecorder!.start(100); // Request data every 100ms\n this.video.play().catch((playErr) => {\n clearInterval(dataCheckInterval);\n clearTimeout(recordingTimeout);\n reject(new Error(`Failed to play video: ${playErr}`));\n });\n } catch (startErr) {\n clearInterval(dataCheckInterval);\n clearTimeout(recordingTimeout);\n reject(new Error(`Failed to start recording: ${startErr}`));\n }\n \n // Stop recording after duration\n setTimeout(async () => {\n clearInterval(dataCheckInterval);\n clearTimeout(recordingTimeout);\n this.video.pause();\n \n if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {\n this.mediaRecorder.stop();\n }\n \n // Wait for final data with timeout\n const stopTimeout = setTimeout(() => {\n if (this.audioChunks.length === 0 || !hasData) {\n reject(new Error('No audio data captured - video has no audio track'));\n }\n }, 2000);\n \n await new Promise<void>((res) => {\n if (this.mediaRecorder) {\n this.mediaRecorder.addEventListener('stop', () => {\n clearTimeout(stopTimeout);\n res();\n }, { once: true });\n } else {\n clearTimeout(stopTimeout);\n res();\n }\n });\n \n // Convert recorded audio to AudioBuffer\n try {\n if (this.audioChunks.length === 0 || !this.audioChunks.some(chunk => chunk.size > 0)) {\n throw new Error('No audio data captured - video has no audio track');\n }\n \n const audioBlob = new Blob(this.audioChunks, { type: 'audio/webm' });\n if (audioBlob.size === 0) {\n throw new Error('Audio blob is empty - video has no audio track');\n }\n \n const arrayBuffer = await audioBlob.arrayBuffer();\n const audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer);\n \n // Validate audio buffer\n if (audioBuffer.length === 0 || audioBuffer.duration === 0) {\n throw new Error('Audio buffer is empty - video has no audio track');\n }\n \n resolve(audioBuffer);\n } catch (err) {\n reject(new Error(`Failed to decode recorded audio: ${err}`));\n }\n }, (duration / playbackRate) * 1000);\n });\n }\n \n async close(): Promise<void> {\n if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {\n this.mediaRecorder.stop();\n }\n this.video.pause();\n this.video.src = '';\n if (this.audioContext.state !== 'closed') {\n await this.audioContext.close();\n }\n }\n}\n\n/**\n * Extract audio from video using MediaElementAudioSourceNode\n * This method works with any audio codec the browser can play\n */\nexport async function extractAudioFromVideo(\n videoSrc: string,\n startTime: number,\n duration: number,\n playbackRate: number = 1.0,\n sampleRate: number = 48000\n): Promise<AudioBuffer> {\n const extractor = new VideoElementAudioExtractor(videoSrc, sampleRate);\n \n try {\n await extractor.initialize();\n const audioBuffer = await extractor.extractAudio(startTime, duration, playbackRate);\n return audioBuffer;\n } finally {\n await extractor.close();\n }\n}\n","/**\n * Browser-based audio processing using Web Audio API\n * Mirrors the server's FFmpeg audio generation logic\n */\n\nimport { extractAudioFromVideo } from './video-audio-extractor';\n\nexport interface MediaAsset {\n key: string;\n src: string;\n type: 'video' | 'audio';\n startInVideo: number;\n endInVideo: number;\n duration: number;\n playbackRate: number;\n volume: number;\n trimLeftInSeconds: number;\n durationInSeconds: number;\n}\n\nexport interface AssetInfo {\n key: string;\n src: string;\n type: 'video' | 'audio';\n currentTime: number;\n playbackRate: number;\n volume: number;\n}\n\n/**\n * Get asset placement from frames (similar to server's getAssetPlacement)\n */\nexport function getAssetPlacement(frames: AssetInfo[][]): MediaAsset[] {\n const assets: MediaAsset[] = [];\n const assetTimeMap = new Map<string, { start: number; end: number }>();\n\n for (let frame = 0; frame < frames.length; frame++) {\n for (const asset of frames[frame]) {\n if (!assetTimeMap.has(asset.key)) {\n assetTimeMap.set(asset.key, {\n start: asset.currentTime,\n end: asset.currentTime,\n });\n assets.push({\n key: asset.key,\n src: asset.src,\n type: asset.type,\n startInVideo: frame,\n endInVideo: frame,\n duration: 0,\n durationInSeconds: 0,\n playbackRate: asset.playbackRate,\n volume: asset.volume,\n trimLeftInSeconds: asset.currentTime,\n });\n } else {\n const timeInfo = assetTimeMap.get(asset.key);\n if (timeInfo) {\n timeInfo.end = asset.currentTime;\n }\n const existingAsset = assets.find(a => a.key === asset.key);\n if (existingAsset) {\n existingAsset.endInVideo = frame;\n }\n }\n }\n }\n\n // Calculate durations\n assets.forEach(asset => {\n const timeInfo = assetTimeMap.get(asset.key);\n if (timeInfo) {\n asset.durationInSeconds = (timeInfo.end - timeInfo.start) / asset.playbackRate;\n }\n asset.duration = asset.endInVideo - asset.startInVideo + 1;\n });\n\n return assets;\n}\n\n/**\n * Audio processor using Web Audio API\n */\nexport class BrowserAudioProcessor {\n private audioContext: AudioContext;\n\n constructor(private sampleRate: number = 48000) {\n this.audioContext = new AudioContext({ sampleRate });\n }\n\n /**\n * Fetch and decode audio from a media source\n * Falls back to video element extraction if decodeAudioData fails\n */\n async fetchAndDecodeAudio(src: string): Promise<AudioBuffer> {\n try {\n const response = await fetch(src);\n const arrayBuffer = await response.arrayBuffer();\n return await this.audioContext.decodeAudioData(arrayBuffer);\n } catch (err) {\n try {\n return await extractAudioFromVideo(\n src,\n 0,\n 999999,\n 1.0,\n this.sampleRate\n );\n } catch (fallbackErr) {\n throw new Error(`Failed to extract audio: ${err}. Fallback also failed: ${fallbackErr}`);\n }\n }\n }\n\n /**\n * Process audio asset with playback rate, volume, and timing\n */\n async processAudioAsset(\n asset: MediaAsset,\n fps: number,\n totalFrames: number\n ): Promise<AudioBuffer> {\n const audioBuffer = await this.fetchAndDecodeAudio(asset.src);\n \n const duration = totalFrames / fps;\n const outputLength = Math.ceil(duration * this.sampleRate);\n const outputBuffer = this.audioContext.createBuffer(\n 2, // stereo\n outputLength,\n this.sampleRate\n );\n\n // Calculate timing\n const startTime = asset.startInVideo / fps;\n const trimLeft = asset.trimLeftInSeconds / asset.playbackRate;\n const trimRight = trimLeft + asset.durationInSeconds;\n\n // Process each channel\n for (let channel = 0; channel < 2; channel++) {\n const inputData = audioBuffer.getChannelData(Math.min(channel, audioBuffer.numberOfChannels - 1));\n const outputData = outputBuffer.getChannelData(channel);\n\n // Calculate sample positions\n const startSample = Math.floor(startTime * this.sampleRate);\n const trimLeftSample = Math.floor(trimLeft * this.sampleRate);\n const trimRightSample = Math.floor(trimRight * this.sampleRate);\n\n // Copy and process samples\n for (let i = 0; i < outputData.length; i++) {\n const outputTime = i / this.sampleRate;\n const assetTime = outputTime - startTime;\n \n if (assetTime < 0 || assetTime >= asset.durationInSeconds) {\n outputData[i] = 0; // Silence\n } else {\n // Apply playback rate\n const inputSample = Math.floor((trimLeftSample + assetTime * asset.playbackRate * this.sampleRate));\n if (inputSample >= 0 && inputSample < inputData.length) {\n outputData[i] = inputData[inputSample] * asset.volume;\n } else {\n outputData[i] = 0;\n }\n }\n }\n }\n\n return outputBuffer;\n }\n\n /**\n * Mix multiple audio buffers\n */\n mixAudioBuffers(buffers: AudioBuffer[]): AudioBuffer {\n if (buffers.length === 0) {\n return this.audioContext.createBuffer(2, 1, this.sampleRate);\n }\n\n const maxLength = Math.max(...buffers.map(b => b.length));\n const mixedBuffer = this.audioContext.createBuffer(2, maxLength, this.sampleRate);\n\n for (let channel = 0; channel < 2; channel++) {\n const mixedData = mixedBuffer.getChannelData(channel);\n \n buffers.forEach(buffer => {\n const channelData = buffer.getChannelData(Math.min(channel, buffer.numberOfChannels - 1));\n for (let i = 0; i < channelData.length; i++) {\n mixedData[i] = (mixedData[i] || 0) + channelData[i] / buffers.length;\n }\n });\n }\n\n return mixedBuffer;\n }\n\n /**\n * Convert AudioBuffer to WAV format\n */\n audioBufferToWav(buffer: AudioBuffer): ArrayBuffer {\n const numberOfChannels = buffer.numberOfChannels;\n const sampleRate = buffer.sampleRate;\n const format = 1; // PCM\n const bitDepth = 16;\n\n const bytesPerSample = bitDepth / 8;\n const blockAlign = numberOfChannels * bytesPerSample;\n\n const data = new Float32Array(buffer.length * numberOfChannels);\n for (let channel = 0; channel < numberOfChannels; channel++) {\n const channelData = buffer.getChannelData(channel);\n for (let i = 0; i < buffer.length; i++) {\n data[i * numberOfChannels + channel] = channelData[i];\n }\n }\n\n const dataLength = data.length * bytesPerSample;\n const headerLength = 44;\n const wav = new ArrayBuffer(headerLength + dataLength);\n const view = new DataView(wav);\n\n // Write WAV header\n const writeString = (offset: number, string: string) => {\n for (let i = 0; i < string.length; i++) {\n view.setUint8(offset + i, string.charCodeAt(i));\n }\n };\n\n writeString(0, 'RIFF');\n view.setUint32(4, 36 + dataLength, true);\n writeString(8, 'WAVE');\n writeString(12, 'fmt ');\n view.setUint32(16, 16, true); // fmt chunk size\n view.setUint16(20, format, true);\n view.setUint16(22, numberOfChannels, true);\n view.setUint32(24, sampleRate, true);\n view.setUint32(28, sampleRate * blockAlign, true);\n view.setUint16(32, blockAlign, true);\n view.setUint16(34, bitDepth, true);\n writeString(36, 'data');\n view.setUint32(40, dataLength, true);\n\n // Write audio data\n const volume = 0.8;\n let offset = 44;\n for (let i = 0; i < data.length; i++) {\n const sample = Math.max(-1, Math.min(1, data[i]));\n view.setInt16(offset, sample < 0 ? sample * 0x8000 : sample * 0x7FFF, true);\n offset += 2;\n }\n\n return wav;\n }\n\n async close() {\n await this.audioContext.close();\n }\n}\n","'use client';\n\n/**\n * Browser-based audio/video muxing using FFmpeg.wasm (main thread)\n * Compatible with Next.js 15\n *\n * FFmpeg core files must be served from the app's public folder, e.g.:\n * twick-web/public/ffmpeg/ffmpeg-core.js, ffmpeg-core.wasm\n */\n\nexport interface MuxerOptions {\n videoBlob: Blob;\n audioBuffer: ArrayBuffer;\n}\n\n/** Base URL for FFmpeg assets (twick-web public/ffmpeg). Use same-origin URLs directly; toBlobURL causes \"Cannot find module 'blob:...'\" in some environments. */\nfunction getFFmpegBaseURL(): string {\n if (typeof window !== 'undefined') {\n return `${window.location.origin}/ffmpeg`;\n }\n return '/ffmpeg';\n}\n\nexport async function muxAudioVideo(\n options: MuxerOptions\n): Promise<Blob> {\n const muxStartTime = Date.now();\n try {\n console.log('Starting FFmpeg muxing...');\n console.log(` Video blob size: ${options.videoBlob.size} bytes (${(options.videoBlob.size / 1024 / 1024).toFixed(2)} MB)`);\n console.log(` Audio buffer size: ${options.audioBuffer.byteLength} bytes (${(options.audioBuffer.byteLength / 1024 / 1024).toFixed(2)} MB)`);\n \n const { FFmpeg } = await import('@ffmpeg/ffmpeg');\n const { fetchFile } = await import('@ffmpeg/util');\n\n const ffmpeg = new FFmpeg();\n\n const base = getFFmpegBaseURL();\n const coreURL = `${base}/ffmpeg-core.js`;\n const wasmURL = `${base}/ffmpeg-core.wasm`;\n\n console.log(`Loading FFmpeg from ${base}`);\n const loadStartTime = Date.now();\n // Load from same-origin public folder (twick-web/public/ffmpeg). Do NOT use toBlobURL —\n // it produces blob: URLs that can trigger \"Cannot find module 'blob:...'\".\n await ffmpeg.load({\n coreURL,\n wasmURL,\n });\n const loadDuration = Date.now() - loadStartTime;\n console.log(`FFmpeg loaded successfully in ${loadDuration}ms`);\n\n // Write inputs\n console.log('Writing video and audio files...');\n const writeStartTime = Date.now();\n await ffmpeg.writeFile(\n 'video.mp4',\n await fetchFile(options.videoBlob)\n );\n console.log(` Video file written: ${options.videoBlob.size} bytes`);\n\n await ffmpeg.writeFile(\n 'audio.wav',\n new Uint8Array(options.audioBuffer)\n );\n const writeDuration = Date.now() - writeStartTime;\n console.log(` Audio file written: ${options.audioBuffer.byteLength} bytes`);\n console.log(`Files written successfully in ${writeDuration}ms`);\n\n console.log('Executing FFmpeg muxing command...');\n const execStartTime = Date.now();\n \n // Capture FFmpeg logs for debugging\n const ffmpegLogs: string[] = [];\n ffmpeg.on('log', ({ message }: { message: string }) => {\n ffmpegLogs.push(message);\n console.log(` [FFmpeg] ${message}`);\n });\n \n await ffmpeg.exec([\n '-i', 'video.mp4',\n '-i', 'audio.wav',\n '-c:v', 'copy',\n '-c:a', 'aac',\n '-b:a', '192k',\n '-shortest',\n 'output.mp4',\n ]);\n const execDuration = Date.now() - execStartTime;\n console.log(`FFmpeg muxing completed in ${execDuration}ms`);\n\n const readStartTime = Date.now();\n const data = await ffmpeg.readFile('output.mp4');\n const readDuration = Date.now() - readStartTime;\n console.log(`Output file read successfully in ${readDuration}ms`);\n\n const uint8 =\n typeof data === 'string'\n ? new TextEncoder().encode(data)\n : new Uint8Array(data);\n\n const result = new Blob([uint8], { type: 'video/mp4' });\n const totalDuration = Date.now() - muxStartTime;\n console.log(`Muxing successful: ${result.size} bytes (${(result.size / 1024 / 1024).toFixed(2)} MB) in ${totalDuration}ms`);\n console.log(` Breakdown: load=${loadDuration}ms, write=${writeDuration}ms, exec=${execDuration}ms, read=${readDuration}ms`);\n return result;\n\n } catch (error) {\n const totalDuration = Date.now() - muxStartTime;\n const errorMsg = error instanceof Error ? error.message : String(error);\n const errorStack = error instanceof Error ? error.stack : undefined;\n console.error('FFmpeg muxing failed:', errorMsg);\n if (errorStack) {\n console.error('Error stack:', errorStack);\n }\n console.error('Error details:', {\n errorType: error instanceof Error ? error.constructor.name : typeof error,\n errorMessage: errorMsg,\n duration: `${totalDuration}ms`,\n videoBlobSize: options.videoBlob.size,\n audioBufferSize: options.audioBuffer.byteLength,\n });\n // Re-throw the error so the caller can handle it\n throw error;\n }\n}\n","import { useState, useCallback } from 'react';\nimport { renderTwickVideoInBrowser, downloadVideoBlob } from '../browser-renderer';\nimport type { BrowserRenderConfig } from '../browser-renderer';\n\nexport interface UseBrowserRendererOptions {\n /** \n * Custom Project object\n * If not provided, defaults to @twick/visualizer project\n * \n * Note: Must be an imported Project object, not a string path.\n * String paths only work in Node.js (server renderer).\n * \n * Example:\n * ```typescript\n * import myProject from './my-custom-project';\n * useBrowserRenderer({ projectFile: myProject })\n * ```\n */\n projectFile?: any;\n /** Video width in pixels */\n width?: number;\n /** Video height in pixels */\n height?: number;\n /** Frames per second */\n fps?: number;\n /** Render quality */\n quality?: 'low' | 'medium' | 'high';\n /** Time range to render [start, end] in seconds */\n range?: [number, number];\n /** Include audio in rendered video (experimental) */\n includeAudio?: boolean;\n /** Download audio separately as WAV file */\n downloadAudioSeparately?: boolean;\n /** Callback when audio is ready */\n onAudioReady?: (audioBlob: Blob) => void;\n /** Automatically download the video when rendering completes */\n autoDownload?: boolean;\n /** Default filename for downloads */\n downloadFilename?: string;\n}\n\nexport interface UseBrowserRendererReturn {\n /** Start rendering the video */\n render: (variables: BrowserRenderConfig['variables']) => Promise<Blob | null>;\n /** Current rendering progress (0-1) */\n progress: number;\n /** Whether rendering is in progress */\n isRendering: boolean;\n /** Error if rendering failed */\n error: Error | null;\n /** The rendered video blob (available after rendering completes) */\n videoBlob: Blob | null;\n /** Download the rendered video */\n download: (filename?: string) => void;\n /** Reset the renderer state */\n reset: () => void;\n}\n\n/**\n * React hook for rendering Twick videos in the browser\n * \n * Uses the same pattern as the server renderer for consistency.\n * \n * @param options - Rendering options\n * @returns Renderer state and control functions\n * \n * @example\n * ```tsx\n * import { useBrowserRenderer } from '@twick/browser-render';\n * \n * // Using default visualizer project\n * function MyComponent() {\n * const { render, progress, isRendering, videoBlob, download } = useBrowserRenderer({\n * width: 1920,\n * height: 1080,\n * fps: 30,\n * autoDownload: true,\n * });\n * \n * const handleRender = async () => {\n * await render({\n * input: {\n * properties: { width: 1920, height: 1080, fps: 30 },\n * tracks: [\n * // Your tracks configuration\n * ]\n * }\n * });\n * };\n * \n * return (\n * <div>\n * <button onClick={handleRender} disabled={isRendering}>\n * {isRendering ? `Rendering... ${(progress * 100).toFixed(0)}%` : 'Render Video'}\n * </button>\n * {videoBlob && !autoDownload && (\n * <button onClick={() => download('my-video.mp4')}>Download</button>\n * )}\n * </div>\n * );\n * }\n * \n * // Using custom project (must import it first)\n * import myProject from './my-project';\n * \n * function CustomProjectComponent() {\n * const { render } = useBrowserRenderer({\n * projectFile: myProject, // Pass the imported project object\n * width: 1920,\n * height: 1080,\n * });\n * \n * // ... rest of component\n * }\n * ```\n */\nexport const useBrowserRenderer = (options: UseBrowserRendererOptions = {}): UseBrowserRendererReturn => {\n const [progress, setProgress] = useState(0);\n const [isRendering, setIsRendering] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [videoBlob, setVideoBlob] = useState<Blob | null>(null);\n\n const reset = useCallback(() => {\n setProgress(0);\n setIsRendering(false);\n setError(null);\n setVideoBlob(null);\n }, []);\n\n const download = useCallback((filename?: string) => {\n if (!videoBlob) {\n setError(new Error('No video available to download. Please render the video first.'));\n return;\n }\n try {\n downloadVideoBlob(videoBlob, filename || options.downloadFilename || 'video.mp4');\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to download video'));\n }\n }, [videoBlob, options.downloadFilename]);\n\n const render = useCallback(async (variables: BrowserRenderConfig['variables']): Promise<Blob | null> => {\n reset();\n setIsRendering(true);\n\n try {\n const { projectFile, width, height, fps, quality, range, includeAudio, downloadAudioSeparately, onAudioReady, autoDownload, downloadFilename, ...restOptions } = options;\n \n const blob = await renderTwickVideoInBrowser({\n projectFile,\n variables,\n settings: {\n width,\n height,\n includeAudio,\n downloadAudioSeparately,\n onAudioReady,\n fps,\n quality,\n range,\n ...restOptions,\n onProgress: (p) => {\n setProgress(p);\n },\n onComplete: (blob) => {\n setVideoBlob(blob);\n if (autoDownload) {\n try {\n downloadVideoBlob(blob, downloadFilename || 'video.mp4');\n } catch (downloadErr) {\n setError(downloadErr instanceof Error ? downloadErr : new Error('Failed to auto-download video'));\n }\n }\n },\n onError: (err) => {\n setError(err);\n },\n },\n });\n\n if (!blob) {\n throw new Error('Rendering failed: No video blob was generated');\n }\n\n setVideoBlob(blob);\n setProgress(1);\n return blob;\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n return null;\n } finally {\n setIsRendering(false);\n }\n }, [options, reset]);\n\n return {\n render,\n progress,\n isRendering,\n error,\n videoBlob,\n download,\n reset,\n };\n};\n\nexport default useBrowserRenderer;\n"],"mappings":"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","names":["offset","defaultProject","blob"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/browser-renderer.ts","../src/audio/video-audio-extractor.ts","../src/audio/audio-processor.ts","../src/audio/audio-video-muxer.ts","../src/audio/video-normalizer.ts","../src/hooks/use-browser-renderer.ts"],"sourcesContent":["/**\n * @twick/browser-render\n * Browser-native video rendering using WebCodecs API\n */\n\n// Main browser renderer functions\nexport { renderTwickVideoInBrowser, downloadVideoBlob } from './browser-renderer';\n\n// Helpers\nexport { normalizeVideoBlob } from './audio/video-normalizer';\nexport type { NormalizeVideoOptions, NormalizeVideoResult } from './audio/video-normalizer';\n\n// React hook\nexport { useBrowserRenderer } from './hooks/use-browser-renderer';\n\n// Type definitions\nexport type { BrowserRenderConfig } from './browser-renderer';\nexport type { \n UseBrowserRendererOptions, \n UseBrowserRendererReturn \n} from './hooks/use-browser-renderer';\n\n// Set as default export\nexport { renderTwickVideoInBrowser as default } from './browser-renderer';\n","import { Renderer, Vector2 } from \"@twick/core\";\nimport type { Project, RendererSettings } from \"@twick/core\";\nimport defaultProject from \"@twick/visualizer/dist/project.js\";\nimport { hasAudio } from \"@twick/media-utils\";\nimport { BrowserAudioProcessor, getAssetPlacement, type AssetInfo } from './audio/audio-processor';\nimport { muxAudioVideo } from './audio/audio-video-muxer';\n\n/** Detect Windows so we can apply encoder workarounds (canvas copy for valid VideoFrame input). */\nfunction isWindows(): boolean {\n if (typeof navigator === 'undefined') return false;\n const ua = navigator.userAgent;\n const plat = (navigator as { platform?: string }).platform ?? '';\n return /Win(dows|32|64|CE)/i.test(ua) || /Win/i.test(plat);\n}\n\n/**\n * Browser-native video exporter using WebCodecs API\n * This exporter downloads the video directly in the browser without any server interaction\n */\nclass BrowserWasmExporter {\n public static readonly id = '@twick/core/wasm';\n public static readonly displayName = 'Browser Video (Wasm)';\n\n private encoder: any;\n private videoBlob: Blob | null = null;\n private onProgressCallback?: (progress: number) => void;\n private currentFrame: number = 0;\n private fps: number = 30;\n /** On Windows, copy each frame to this canvas before creating VideoFrame to avoid invalid encoder output. */\n private copyCanvas: HTMLCanvasElement | null = null;\n /** On Windows use native VideoEncoder + Mediabunny with prefer-software so frames are actually encoded. */\n private useNativeEncoder = false;\n private nativeVideoEncoder: VideoEncoder | null = null;\n private nativeOutput: { target: { buffer: ArrayBuffer }; finalize(): Promise<void> } | null = null;\n private nativePacketSource: { add(packet: unknown, meta?: EncodedVideoChunkMetadata): Promise<void>; close(): void } | null = null;\n private nativeAddPromise: Promise<void> = Promise.resolve();\n private nativeFirstChunk = true;\n\n public static async create(settings: RendererSettings) {\n return new BrowserWasmExporter(settings);\n }\n\n public constructor(\n private readonly settings: RendererSettings,\n ) {\n this.fps = settings.fps || 30;\n }\n\n public async start(): Promise<void> {\n const w = this.settings.size.x;\n const h = this.settings.size.y;\n const fps = this.fps;\n\n if (isWindows()) {\n try {\n const { Output, BufferTarget, Mp4OutputFormat, EncodedVideoPacketSource, EncodedPacket } = await import('mediabunny');\n const output = new Output({\n format: new Mp4OutputFormat(),\n target: new BufferTarget(),\n });\n const packetSource = new EncodedVideoPacketSource('avc');\n output.addVideoTrack(packetSource);\n await output.start();\n this.nativeOutput = output as typeof this.nativeOutput;\n this.nativePacketSource = packetSource as typeof this.nativePacketSource;\n this.nativeAddPromise = Promise.resolve();\n this.nativeFirstChunk = true;\n\n const videoEncoder = new VideoEncoder({\n output: (chunk: EncodedVideoChunk, meta?: EncodedVideoChunkMetadata) => {\n const packet = EncodedPacket.fromEncodedChunk(chunk);\n const isFirst = this.nativeFirstChunk;\n const metaArg = isFirst ? meta : undefined;\n this.nativeFirstChunk = false;\n this.nativeAddPromise = this.nativeAddPromise.then(() =>\n this.nativePacketSource!.add(packet, metaArg),\n );\n },\n error: (e: Error) => console.error('[BrowserRender] VideoEncoder error:', e),\n });\n const bitrate = Math.max(500_000, (w * h * fps * 0.1) | 0);\n const config: VideoEncoderConfig = {\n codec: 'avc1.42001f',\n width: w,\n height: h,\n bitrate,\n framerate: fps,\n hardwareAcceleration: 'prefer-software',\n };\n const support = await VideoEncoder.isConfigSupported(config);\n if (!support.supported) {\n delete (config as unknown as Record<string, unknown>).hardwareAcceleration;\n }\n videoEncoder.configure(config);\n this.nativeVideoEncoder = videoEncoder;\n this.useNativeEncoder = true;\n this.copyCanvas = document.createElement('canvas');\n this.copyCanvas.width = w;\n this.copyCanvas.height = h;\n return;\n } catch {\n this.useNativeEncoder = false;\n this.nativeVideoEncoder = null;\n this.nativeOutput = null;\n this.nativePacketSource = null;\n }\n }\n\n try {\n const loadMp4Module = (await import('mp4-wasm')).default;\n const possiblePaths = [\n '/@mp4-wasm',\n '/assets/mp4-wasm.wasm',\n '/assets/mp4-YBRi_559.wasm',\n '/mp4-wasm.wasm',\n '/node_modules/mp4-wasm/dist/mp4-wasm.wasm',\n ];\n let buffer: ArrayBuffer | null = null;\n for (const path of possiblePaths) {\n try {\n const resp = await fetch(path);\n if (resp.ok) {\n const contentType = resp.headers.get('content-type');\n if (contentType && contentType.includes('html')) continue;\n buffer = await resp.arrayBuffer();\n break;\n }\n } catch {\n continue;\n }\n }\n if (!buffer) {\n console.error('[BrowserRender] Exporter start: no WASM buffer from any path');\n throw new Error(\n 'Could not load WASM file from any location. ' +\n 'Please copy mp4-wasm.wasm to your public directory or configure Vite to serve it.'\n );\n }\n const mp4 = await loadMp4Module({ wasmBinary: buffer });\n this.encoder = mp4.createWebCodecsEncoder({ width: w, height: h, fps });\n if (isWindows()) {\n this.copyCanvas = document.createElement('canvas');\n this.copyCanvas.width = w;\n this.copyCanvas.height = h;\n }\n } catch (error) {\n throw error;\n }\n }\n\n public async handleFrame(canvas: HTMLCanvasElement, frameNumber?: number): Promise<void> {\n const frameIndex = frameNumber !== undefined ? frameNumber : this.currentFrame;\n const timestampMicroseconds = Math.round((frameIndex / this.fps) * 1_000_000);\n const durationMicroseconds = Math.round((1 / this.fps) * 1_000_000);\n\n let sourceCanvas: HTMLCanvasElement = canvas;\n if (this.copyCanvas) {\n if (\n this.copyCanvas.width !== canvas.width ||\n this.copyCanvas.height !== canvas.height\n ) {\n this.copyCanvas.width = canvas.width;\n this.copyCanvas.height = canvas.height;\n }\n const ctx = this.copyCanvas.getContext('2d');\n if (ctx) {\n ctx.imageSmoothingEnabled = false;\n ctx.drawImage(canvas, 0, 0);\n sourceCanvas = this.copyCanvas;\n }\n }\n\n if (this.useNativeEncoder && this.nativeVideoEncoder) {\n const bitmap = await createImageBitmap(sourceCanvas);\n const frame = new VideoFrame(bitmap, {\n timestamp: timestampMicroseconds,\n duration: durationMicroseconds,\n });\n this.nativeVideoEncoder.encode(frame, { keyFrame: frameIndex === 0 });\n frame.close();\n bitmap.close();\n } else {\n let frame: VideoFrame;\n if (isWindows() && typeof createImageBitmap === 'function') {\n const bitmap = await createImageBitmap(sourceCanvas);\n frame = new VideoFrame(bitmap, {\n timestamp: timestampMicroseconds,\n duration: durationMicroseconds,\n });\n await this.encoder.addFrame(frame);\n frame.close();\n bitmap.close();\n } else {\n frame = new VideoFrame(sourceCanvas, {\n timestamp: timestampMicroseconds,\n duration: durationMicroseconds,\n });\n await this.encoder.addFrame(frame);\n frame.close();\n }\n }\n\n if (frameNumber === undefined) {\n this.currentFrame++;\n }\n }\n\n public async stop(): Promise<void> {\n if (this.useNativeEncoder && this.nativeVideoEncoder && this.nativeOutput && this.nativePacketSource) {\n await this.nativeVideoEncoder.flush();\n this.nativeVideoEncoder.close();\n this.nativeVideoEncoder = null;\n await this.nativeAddPromise;\n this.nativePacketSource.close();\n await this.nativeOutput.finalize();\n const buf = this.nativeOutput.target.buffer;\n this.nativeOutput = null;\n this.nativePacketSource = null;\n this.videoBlob = new Blob([buf], { type: 'video/mp4' });\n return;\n }\n const buf = await this.encoder.end();\n const copy =\n buf instanceof ArrayBuffer\n ? buf.slice(0)\n : new Uint8Array(buf).slice().buffer;\n this.videoBlob = new Blob([copy], { type: 'video/mp4' });\n }\n\n public async generateAudio(\n assets: AssetInfo[][],\n startFrame: number,\n endFrame: number,\n onProgress?: (progress: number) => void,\n ): Promise<ArrayBuffer | null> {\n try {\n const processor = new BrowserAudioProcessor();\n const assetPlacements = getAssetPlacement(assets);\n\n if (assetPlacements.length === 0) {\n return null;\n }\n\n const processableCount = assetPlacements.filter(a => a.volume > 0 && a.playbackRate > 0).length;\n let processedCount = 0;\n\n const processedBuffers: AudioBuffer[] = [];\n for (let i = 0; i < assetPlacements.length; i++) {\n const asset = assetPlacements[i];\n if (asset.volume > 0 && asset.playbackRate > 0) {\n try {\n if (asset.type === 'video') {\n try {\n const assetHasAudio = await hasAudio(asset.src);\n if (!assetHasAudio) continue;\n } catch {\n // Continue processing if check fails (might have audio)\n }\n }\n\n // Add timeout wrapper for processAudioAsset (30 seconds max per asset)\n const processPromise = processor.processAudioAsset(\n asset,\n this.settings.fps || 30,\n endFrame - startFrame\n );\n \n const timeoutPromise = new Promise<never>((_, reject) => {\n setTimeout(() => {\n reject(new Error(`Timeout processing audio asset after 20s - video may have no audio track`));\n }, 20000);\n });\n \n const buffer = await Promise.race([processPromise, timeoutPromise]);\n processedBuffers.push(buffer);\n processedCount++;\n if (onProgress && processableCount > 0) {\n onProgress(processedCount / processableCount);\n }\n } catch {\n // Continue with other audio assets\n }\n }\n }\n\n if (processedBuffers.length === 0) {\n return null;\n }\n\n if (onProgress) onProgress(0.85);\n const mixedBuffer = processor.mixAudioBuffers(processedBuffers);\n if (onProgress) onProgress(0.92);\n\n const wavData = processor.audioBufferToWav(mixedBuffer);\n if (onProgress) onProgress(1);\n\n await processor.close();\n return wavData;\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n const errorStack = error instanceof Error ? error.stack : undefined;\n console.error('[BrowserRender] Audio generation error:', errorMsg);\n if (errorStack) console.error('[BrowserRender] Stack:', errorStack);\n return null;\n }\n }\n\n public async mergeMedia(): Promise<void> {\n // In browser, we don't need to merge separately\n // The video is already created with audio in the encoder\n }\n\n public async downloadVideos(assets: any[][]): Promise<void> {\n // Browser doesn't need to download source videos\n // They're already accessible via URLs\n }\n\n public getVideoBlob(): Blob | null {\n return this.videoBlob;\n }\n\n public setProgressCallback(callback: (progress: number) => void): void {\n this.onProgressCallback = callback;\n }\n}\n\n/**\n * Browser rendering configuration\n */\nexport interface BrowserRenderConfig {\n /** \n * Custom Project object\n * If not provided, defaults to @twick/visualizer project\n * \n * Note: Must be an imported Project object, not a string path.\n * String paths only work in Node.js environments (server renderer).\n * \n * Example:\n * ```typescript\n * import myProject from './my-custom-project';\n * \n * await renderTwickVideoInBrowser({\n * projectFile: myProject,\n * variables: { input: {...} }\n * });\n * ```\n */\n projectFile?: Project;\n /** Input variables containing project configuration */\n variables: {\n input: any;\n playerId?: string;\n [key: string]: any;\n };\n /** Render settings */\n settings?: {\n width?: number;\n height?: number;\n fps?: number;\n quality?: 'low' | 'medium' | 'high';\n range?: [number, number]; // [start, end] in seconds\n includeAudio?: boolean; // Enable audio processing\n downloadAudioSeparately?: boolean; // Download audio.wav separately\n onAudioReady?: (audioBlob: Blob) => void; // Callback when audio is ready\n onProgress?: (progress: number) => void;\n onComplete?: (videoBlob: Blob) => void;\n onError?: (error: Error) => void;\n };\n}\n\n/**\n * Renders a Twick video directly in the browser without requiring a server.\n * Uses WebCodecs API for encoding video frames into MP4 format.\n * \n * This function uses the same signature as the server renderer for consistency.\n *\n * @param config - Configuration object containing variables and settings\n * @param config.projectFile - Optional project file path or Project object (defaults to visualizer project)\n * @param config.variables - Variables containing input configuration (tracks, elements, etc.)\n * @param config.settings - Optional render settings (width, height, fps, etc.)\n * @returns Promise resolving to a Blob containing the rendered video\n * \n * @example\n * ```js\n * import { renderTwickVideoInBrowser } from '@twick/browser-render';\n * \n * // Using default visualizer project\n * const videoBlob = await renderTwickVideoInBrowser({\n * variables: {\n * input: {\n * properties: { width: 1920, height: 1080, fps: 30 },\n * tracks: [\n * // Your tracks configuration\n * ]\n * }\n * },\n * settings: {\n * width: 1920,\n * height: 1080,\n * fps: 30,\n * quality: 'high',\n * onProgress: (progress) => console.log(`Rendering: ${progress * 100}%`),\n * }\n * });\n * \n * // Using custom project\n * import myProject from './my-custom-project';\n * const videoBlob = await renderTwickVideoInBrowser({\n * projectFile: myProject, // Must be an imported Project object\n * variables: { input: {...} },\n * settings: {...}\n * });\n * \n * // Download the video\n * const url = URL.createObjectURL(videoBlob);\n * const a = document.createElement('a');\n * a.href = url;\n * a.download = 'video.mp4';\n * a.click();\n * URL.revokeObjectURL(url);\n * ```\n */\nexport const renderTwickVideoInBrowser = async (\n config: BrowserRenderConfig\n): Promise<Blob> => {\n // Save original methods to restore later\n const originalVideoPlay = HTMLVideoElement.prototype.play;\n const originalAudioPlay = HTMLAudioElement.prototype.play;\n const originalCreateElement = document.createElement.bind(document);\n \n // Override play methods to force muting\n HTMLVideoElement.prototype.play = function() {\n this.muted = true;\n this.volume = 0;\n return originalVideoPlay.call(this);\n };\n \n HTMLAudioElement.prototype.play = function() {\n this.muted = true;\n this.volume = 0;\n return originalAudioPlay.call(this);\n };\n \n // Override createElement to mute video/audio on creation\n document.createElement = function(tagName: string, options?: any) {\n const element = originalCreateElement(tagName, options);\n if (tagName.toLowerCase() === 'video' || tagName.toLowerCase() === 'audio') {\n (element as any).muted = true;\n (element as any).volume = 0;\n }\n return element;\n } as any;\n\n try {\n const { projectFile, variables, settings = {} } = config;\n\n if (!variables || !variables.input) {\n console.error('[BrowserRender] renderTwickVideoInBrowser: missing variables.input');\n throw new Error('Invalid configuration. \"variables.input\" is required.');\n }\n\n const width = settings.width || variables.input.properties?.width || 1920;\n const height = settings.height || variables.input.properties?.height || 1080;\n const fps = settings.fps || variables.input.properties?.fps || 30;\n\n const project: Project = !projectFile ? defaultProject : (projectFile as Project);\n project.variables = variables as any;\n\n // Create renderer settings\n const renderSettings: RendererSettings = {\n name: 'browser-render',\n exporter: {\n name: '@twick/core/wasm',\n },\n size: new Vector2(width, height),\n resolutionScale: 1,\n colorSpace: 'srgb',\n fps: fps,\n range: settings.range || [0, Infinity],\n background: variables.input.backgroundColor || '#000000',\n ...(settings.quality && {\n quality: settings.quality,\n }),\n };\n\n const renderer = new Renderer(project);\n const exporter = await BrowserWasmExporter.create(renderSettings);\n await exporter.start();\n \n if (settings.onProgress) {\n exporter.setProgressCallback(settings.onProgress);\n }\n\n await renderer['reloadScenes'](renderSettings);\n (renderer as any).stage.configure(renderSettings);\n (renderer as any).playback.fps = renderSettings.fps;\n \n // Set playback state to Rendering (critical for video elements)\n // PlaybackState: Playing = 0, Rendering = 1, Paused = 2, Presenting = 3\n (renderer as any).playback.state = 1;\n \n const totalFrames = await renderer.getNumberOfFrames(renderSettings);\n\n if (totalFrames === 0 || !isFinite(totalFrames)) {\n console.error('[BrowserRender] renderTwickVideoInBrowser: invalid totalFrames', totalFrames);\n throw new Error(\n 'Cannot render: Video has zero duration. ' +\n 'Please ensure your project has valid content with non-zero duration. ' +\n 'Check that all video elements have valid sources and are properly loaded.'\n );\n }\n \n const videoElements: any[] = [];\n const audioElements: any[] = [];\n if (variables.input.tracks) {\n variables.input.tracks.forEach((track: any) => {\n if (track.elements) {\n track.elements.forEach((el: any) => {\n if (el.type === 'video') videoElements.push(el);\n if (el.type === 'audio') audioElements.push(el);\n });\n }\n });\n }\n\n // Load video metadata and check for audio (required for rendering)\n let hasAnyAudio = false;\n if (settings.includeAudio && audioElements.length > 0) {\n hasAnyAudio = true;\n }\n if (videoElements.length > 0) {\n for (const videoEl of videoElements) {\n const src = videoEl.props?.src;\n if (!src || src === 'undefined') continue;\n \n // Load video metadata\n const preloadVideo = document.createElement('video');\n preloadVideo.crossOrigin = 'anonymous';\n preloadVideo.preload = 'metadata';\n preloadVideo.src = src;\n await new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(\n () => reject(new Error(`Timeout loading video metadata: ${src.substring(0, 80)}`)),\n 30000\n );\n preloadVideo.addEventListener('loadedmetadata', () => {\n clearTimeout(timeout);\n resolve();\n }, { once: true });\n preloadVideo.addEventListener('error', () => {\n clearTimeout(timeout);\n const err = preloadVideo.error;\n reject(new Error(`Failed to load video: ${err?.message || 'Unknown error'}`));\n }, { once: true });\n });\n \n if (settings.includeAudio) {\n try {\n const videoHasAudio = await hasAudio(src);\n if (videoHasAudio) hasAnyAudio = true;\n } catch {\n hasAnyAudio = true;\n }\n }\n }\n }\n\n await (renderer as any).playback.recalculate();\n await (renderer as any).playback.reset();\n await (renderer as any).playback.seek(0);\n\n const mediaAssets: AssetInfo[][] = [];\n \n for (let frame = 0; frame < totalFrames; frame++) {\n if (frame > 0) {\n await (renderer as any).playback.progress();\n }\n await (renderer as any).stage.render(\n (renderer as any).playback.currentScene,\n (renderer as any).playback.previousScene,\n );\n const currentAssets = (renderer as any).playback.currentScene.getMediaAssets?.() || [];\n mediaAssets.push(currentAssets);\n const canvas = (renderer as any).stage.finalBuffer;\n await exporter.handleFrame(canvas, frame);\n // Video encoding: 0–90% so audio phase (90–100%) is visible\n if (settings.onProgress) settings.onProgress((frame / totalFrames) * 0.9);\n }\n\n await exporter.stop();\n\n // Inject standalone audio elements into mediaAssets per frame so getAssetPlacement includes them\n if (audioElements.length > 0 && settings.includeAudio) {\n for (let frame = 0; frame < mediaAssets.length; frame++) {\n const timeInSec = frame / fps;\n for (const el of audioElements) {\n const s = typeof el.s === 'number' ? el.s : 0;\n const e = typeof el.e === 'number' ? el.e : Number.MAX_VALUE;\n if (timeInSec >= s && timeInSec < e && el.props?.src) {\n const playbackRate = el.props.playbackRate ?? 1;\n const volume = el.props.volume ?? 1;\n const trimStart = el.props.time ?? 0;\n const currentTime = (timeInSec - s) * playbackRate + trimStart;\n (mediaAssets[frame] as AssetInfo[]).push({\n key: el.id,\n src: el.props.src,\n type: 'audio',\n currentTime,\n playbackRate,\n volume,\n });\n }\n }\n }\n }\n\n let audioData: ArrayBuffer | null = null;\n\n if (settings.includeAudio && mediaAssets.length > 0 && hasAnyAudio) {\n // Audio phase: 90–97%; progress callback maps generateAudio 0–1 to 0.90–0.97\n const reportAudioProgress = (p: number) => {\n if (settings.onProgress) settings.onProgress(0.9 + p * 0.07);\n };\n\n try {\n audioData = await exporter.generateAudio(mediaAssets, 0, totalFrames, reportAudioProgress);\n if (settings.onProgress) settings.onProgress(0.97);\n } catch (audioError) {\n const errorMsg = audioError instanceof Error ? audioError.message : String(audioError);\n const errorStack = audioError instanceof Error ? audioError.stack : undefined;\n console.error('[BrowserRender] Audio generation failed:', errorMsg);\n if (errorStack) console.error('[BrowserRender] Stack:', errorStack);\n audioData = null;\n }\n }\n\n let finalBlob = exporter.getVideoBlob();\n if (!finalBlob) {\n console.error('[BrowserRender] renderTwickVideoInBrowser: getVideoBlob returned null');\n throw new Error('Failed to create video blob');\n }\n\n if (finalBlob.size === 0) {\n console.error('[BrowserRender] renderTwickVideoInBrowser: video blob size is 0');\n throw new Error('Video blob is empty. Rendering may have failed.');\n }\n const MIN_VIDEO_BLOB_BYTES = 1024;\n if (finalBlob.size < MIN_VIDEO_BLOB_BYTES) {\n console.error('[BrowserRender] renderTwickVideoInBrowser: video blob too small', finalBlob.size, 'bytes (expected >=', MIN_VIDEO_BLOB_BYTES, ')');\n throw new Error(\n `Video blob is too small (${finalBlob.size} bytes). No video frames were encoded. ` +\n 'This often happens on Windows when the encoder does not accept the frame format from canvas. ' +\n 'Try using a different browser or updating graphics drivers.'\n );\n }\n\n // Only attempt muxing if we have audio data\n // If no audio or muxing fails, return the video blob without audio\n if (audioData && settings.includeAudio) {\n if (settings.onProgress) settings.onProgress(0.98);\n try {\n const muxedBlob = await muxAudioVideo({\n videoBlob: finalBlob,\n audioBuffer: audioData,\n });\n \n // Validate muxed blob has content\n if (!muxedBlob || muxedBlob.size === 0) {\n throw new Error('Muxed video blob is empty');\n }\n \n finalBlob = muxedBlob;\n } catch (muxError) {\n const errorMsg = muxError instanceof Error ? muxError.message : String(muxError);\n const errorStack = muxError instanceof Error ? muxError.stack : undefined;\n console.error('[BrowserRender] Audio muxing failed:', errorMsg);\n if (errorStack) console.error('[BrowserRender] Stack:', errorStack);\n // Optionally download audio separately if requested\n if (settings.downloadAudioSeparately && audioData) {\n const audioBlob = new Blob([audioData], { type: 'audio/wav' });\n const audioUrl = URL.createObjectURL(audioBlob);\n const a = document.createElement('a');\n a.href = audioUrl;\n a.download = 'audio.wav';\n a.click();\n URL.revokeObjectURL(audioUrl);\n }\n \n // Re-get the original blob to ensure we have a valid video\n finalBlob = exporter.getVideoBlob();\n if (!finalBlob || finalBlob.size === 0) {\n throw new Error('Video blob is invalid after muxing failure');\n }\n }\n }\n\n if (!finalBlob || finalBlob.size === 0) {\n console.error('[BrowserRender] renderTwickVideoInBrowser: final blob invalid', finalBlob?.size);\n throw new Error('Final video blob is empty or invalid');\n }\n\n if (settings.onProgress) {\n settings.onProgress(1.0);\n }\n\n if (settings.onComplete) {\n settings.onComplete(finalBlob);\n }\n\n return finalBlob;\n } catch (error) {\n if (config.settings?.onError) {\n config.settings.onError(error as Error);\n }\n throw error;\n } finally {\n // Restore original methods\n HTMLVideoElement.prototype.play = originalVideoPlay;\n HTMLAudioElement.prototype.play = originalAudioPlay;\n document.createElement = originalCreateElement as any;\n }\n};\n\n/**\n * Helper function to download a video blob as a file\n * \n * @param videoBlob - The video blob to download\n * @param filename - The desired filename (default: 'video.mp4')\n * \n * @example\n * ```js\n * const blob = await renderTwickVideoInBrowser(projectData);\n * downloadVideoBlob(blob, 'my-video.mp4');\n * ```\n */\nexport const downloadVideoBlob = (videoBlob: Blob, filename: string = 'video.mp4'): void => {\n const url = URL.createObjectURL(videoBlob);\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n a.style.display = 'none';\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n // Revoke later so the browser can finish reading the blob (Windows disk I/O can be slower).\n const revokeMs = isWindows() ? 5000 : 1000;\n setTimeout(() => URL.revokeObjectURL(url), revokeMs);\n};\n\nexport default renderTwickVideoInBrowser;\n","/**\n * Alternative audio extraction using MediaElementAudioSourceNode\n * This captures audio directly from a playing video element without decoding\n * Works with ANY audio codec the browser can play!\n */\n\nexport class VideoElementAudioExtractor {\n private audioContext: AudioContext;\n private video: HTMLVideoElement;\n private destination: MediaStreamAudioDestinationNode | null = null;\n private mediaRecorder: MediaRecorder | null = null;\n private audioChunks: Blob[] = [];\n \n constructor(videoSrc: string, sampleRate: number = 48000) {\n this.audioContext = new AudioContext({ sampleRate });\n this.video = document.createElement('video');\n this.video.crossOrigin = 'anonymous';\n this.video.src = videoSrc;\n this.video.muted = true; // Mute playback but audio will still be captured\n }\n \n async initialize(): Promise<void> {\n return new Promise((resolve, reject) => {\n this.video.addEventListener('loadedmetadata', () => resolve(), { once: true });\n \n this.video.addEventListener('error', (e) => {\n reject(new Error(`Failed to load video for audio extraction: ${e}`));\n }, { once: true });\n });\n }\n \n /**\n * Extract audio by playing the video and capturing audio output\n */\n async extractAudio(\n startTime: number,\n duration: number,\n playbackRate: number = 1.0\n ): Promise<AudioBuffer> {\n // Check if video has audio tracks before attempting extraction\n try {\n // Try to create MediaElementSource - this will fail if video has no audio\n const source = this.audioContext.createMediaElementSource(this.video);\n \n // Create destination for recording\n this.destination = this.audioContext.createMediaStreamDestination();\n source.connect(this.destination);\n } catch (err) {\n // If we can't create the source, the video likely has no audio track\n throw new Error('Video has no audio track');\n }\n \n // Create MediaRecorder to capture audio\n this.audioChunks = [];\n let mimeType = 'audio/webm';\n \n // Check if MediaRecorder supports the mime type\n if (!MediaRecorder.isTypeSupported(mimeType)) {\n // Fallback to default\n mimeType = '';\n }\n \n try {\n this.mediaRecorder = new MediaRecorder(this.destination.stream, {\n mimeType: mimeType || undefined,\n });\n } catch (err) {\n throw new Error(`Failed to create MediaRecorder: ${err}. Video may have no audio track.`);\n }\n \n this.mediaRecorder.ondataavailable = (event) => {\n if (event.data && event.data.size > 0) {\n this.audioChunks.push(event.data);\n }\n };\n \n // Set up video playback\n this.video.currentTime = startTime;\n this.video.playbackRate = playbackRate;\n \n // Wait for seek to complete\n await new Promise<void>((resolve, reject) => {\n const seekTimeout = setTimeout(() => {\n reject(new Error('Video seek timeout'));\n }, 5000);\n \n this.video.addEventListener('seeked', () => {\n clearTimeout(seekTimeout);\n resolve();\n }, { once: true });\n \n this.video.addEventListener('error', () => {\n clearTimeout(seekTimeout);\n reject(new Error('Video seek error'));\n }, { once: true });\n });\n \n // Start recording and playing\n return new Promise((resolve, reject) => {\n const recordingTimeout = setTimeout(() => {\n this.video.pause();\n if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {\n this.mediaRecorder.stop();\n }\n reject(new Error('Audio extraction timeout - video may have no audio track'));\n }, (duration / playbackRate + 10) * 1000); // Add 10s buffer\n \n let hasData = false;\n const dataCheckInterval = setInterval(() => {\n if (this.audioChunks.length > 0 && this.audioChunks.some(chunk => chunk.size > 0)) {\n hasData = true;\n }\n }, 1000);\n \n this.mediaRecorder!.onerror = (event) => {\n clearInterval(dataCheckInterval);\n clearTimeout(recordingTimeout);\n this.video.pause();\n reject(new Error(`MediaRecorder error: ${event}. Video may have no audio track.`));\n };\n \n try {\n this.mediaRecorder!.start(100); // Request data every 100ms\n this.video.play().catch((playErr) => {\n clearInterval(dataCheckInterval);\n clearTimeout(recordingTimeout);\n reject(new Error(`Failed to play video: ${playErr}`));\n });\n } catch (startErr) {\n clearInterval(dataCheckInterval);\n clearTimeout(recordingTimeout);\n reject(new Error(`Failed to start recording: ${startErr}`));\n }\n \n // Stop recording after duration\n setTimeout(async () => {\n clearInterval(dataCheckInterval);\n clearTimeout(recordingTimeout);\n this.video.pause();\n \n if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {\n this.mediaRecorder.stop();\n }\n \n // Wait for final data with timeout\n const stopTimeout = setTimeout(() => {\n if (this.audioChunks.length === 0 || !hasData) {\n reject(new Error('No audio data captured - video has no audio track'));\n }\n }, 2000);\n \n await new Promise<void>((res) => {\n if (this.mediaRecorder) {\n this.mediaRecorder.addEventListener('stop', () => {\n clearTimeout(stopTimeout);\n res();\n }, { once: true });\n } else {\n clearTimeout(stopTimeout);\n res();\n }\n });\n \n // Convert recorded audio to AudioBuffer\n try {\n if (this.audioChunks.length === 0 || !this.audioChunks.some(chunk => chunk.size > 0)) {\n throw new Error('No audio data captured - video has no audio track');\n }\n \n const audioBlob = new Blob(this.audioChunks, { type: 'audio/webm' });\n if (audioBlob.size === 0) {\n throw new Error('Audio blob is empty - video has no audio track');\n }\n \n const arrayBuffer = await audioBlob.arrayBuffer();\n const audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer);\n \n // Validate audio buffer\n if (audioBuffer.length === 0 || audioBuffer.duration === 0) {\n throw new Error('Audio buffer is empty - video has no audio track');\n }\n \n resolve(audioBuffer);\n } catch (err) {\n reject(new Error(`Failed to decode recorded audio: ${err}`));\n }\n }, (duration / playbackRate) * 1000);\n });\n }\n \n async close(): Promise<void> {\n if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {\n this.mediaRecorder.stop();\n }\n this.video.pause();\n this.video.src = '';\n if (this.audioContext.state !== 'closed') {\n await this.audioContext.close();\n }\n }\n}\n\n/**\n * Extract audio from video using MediaElementAudioSourceNode\n * This method works with any audio codec the browser can play\n */\nexport async function extractAudioFromVideo(\n videoSrc: string,\n startTime: number,\n duration: number,\n playbackRate: number = 1.0,\n sampleRate: number = 48000\n): Promise<AudioBuffer> {\n const extractor = new VideoElementAudioExtractor(videoSrc, sampleRate);\n \n try {\n await extractor.initialize();\n const audioBuffer = await extractor.extractAudio(startTime, duration, playbackRate);\n return audioBuffer;\n } finally {\n await extractor.close();\n }\n}\n","/**\n * Browser-based audio processing using Web Audio API\n * Mirrors the server's FFmpeg audio generation logic\n */\n\nimport { extractAudioFromVideo } from './video-audio-extractor';\n\nexport interface MediaAsset {\n key: string;\n src: string;\n type: 'video' | 'audio';\n startInVideo: number;\n endInVideo: number;\n duration: number;\n playbackRate: number;\n volume: number;\n trimLeftInSeconds: number;\n durationInSeconds: number;\n}\n\nexport interface AssetInfo {\n key: string;\n src: string;\n type: 'video' | 'audio';\n currentTime: number;\n playbackRate: number;\n volume: number;\n}\n\n/**\n * Get asset placement from frames (similar to server's getAssetPlacement)\n */\nexport function getAssetPlacement(frames: AssetInfo[][]): MediaAsset[] {\n const assets: MediaAsset[] = [];\n const assetTimeMap = new Map<string, { start: number; end: number }>();\n\n for (let frame = 0; frame < frames.length; frame++) {\n for (const asset of frames[frame]) {\n if (!assetTimeMap.has(asset.key)) {\n assetTimeMap.set(asset.key, {\n start: asset.currentTime,\n end: asset.currentTime,\n });\n assets.push({\n key: asset.key,\n src: asset.src,\n type: asset.type,\n startInVideo: frame,\n endInVideo: frame,\n duration: 0,\n durationInSeconds: 0,\n playbackRate: asset.playbackRate,\n volume: asset.volume,\n trimLeftInSeconds: asset.currentTime,\n });\n } else {\n const timeInfo = assetTimeMap.get(asset.key);\n if (timeInfo) {\n timeInfo.end = asset.currentTime;\n }\n const existingAsset = assets.find(a => a.key === asset.key);\n if (existingAsset) {\n existingAsset.endInVideo = frame;\n }\n }\n }\n }\n\n // Calculate durations\n assets.forEach(asset => {\n const timeInfo = assetTimeMap.get(asset.key);\n if (timeInfo) {\n asset.durationInSeconds = (timeInfo.end - timeInfo.start) / asset.playbackRate;\n }\n asset.duration = asset.endInVideo - asset.startInVideo + 1;\n });\n\n return assets;\n}\n\n/**\n * Audio processor using Web Audio API\n */\nexport class BrowserAudioProcessor {\n private audioContext: AudioContext;\n\n constructor(private sampleRate: number = 48000) {\n this.audioContext = new AudioContext({ sampleRate });\n }\n\n /**\n * Fetch and decode audio from a media source\n * Falls back to video element extraction if decodeAudioData fails\n */\n async fetchAndDecodeAudio(src: string): Promise<AudioBuffer> {\n try {\n const response = await fetch(src);\n const arrayBuffer = await response.arrayBuffer();\n return await this.audioContext.decodeAudioData(arrayBuffer);\n } catch (err) {\n try {\n return await extractAudioFromVideo(\n src,\n 0,\n 999999,\n 1.0,\n this.sampleRate\n );\n } catch (fallbackErr) {\n throw new Error(`Failed to extract audio: ${err}. Fallback also failed: ${fallbackErr}`);\n }\n }\n }\n\n /**\n * Process audio asset with playback rate, volume, and timing\n */\n async processAudioAsset(\n asset: MediaAsset,\n fps: number,\n totalFrames: number\n ): Promise<AudioBuffer> {\n const audioBuffer = await this.fetchAndDecodeAudio(asset.src);\n \n const duration = totalFrames / fps;\n const outputLength = Math.ceil(duration * this.sampleRate);\n const outputBuffer = this.audioContext.createBuffer(\n 2, // stereo\n outputLength,\n this.sampleRate\n );\n\n // Calculate timing\n const startTime = asset.startInVideo / fps;\n const trimLeft = asset.trimLeftInSeconds / asset.playbackRate;\n const trimRight = trimLeft + asset.durationInSeconds;\n\n // Process each channel\n for (let channel = 0; channel < 2; channel++) {\n const inputData = audioBuffer.getChannelData(Math.min(channel, audioBuffer.numberOfChannels - 1));\n const outputData = outputBuffer.getChannelData(channel);\n\n // Calculate sample positions\n const startSample = Math.floor(startTime * this.sampleRate);\n const trimLeftSample = Math.floor(trimLeft * this.sampleRate);\n const trimRightSample = Math.floor(trimRight * this.sampleRate);\n\n // Copy and process samples\n for (let i = 0; i < outputData.length; i++) {\n const outputTime = i / this.sampleRate;\n const assetTime = outputTime - startTime;\n \n if (assetTime < 0 || assetTime >= asset.durationInSeconds) {\n outputData[i] = 0; // Silence\n } else {\n // Apply playback rate\n const inputSample = Math.floor((trimLeftSample + assetTime * asset.playbackRate * this.sampleRate));\n if (inputSample >= 0 && inputSample < inputData.length) {\n outputData[i] = inputData[inputSample] * asset.volume;\n } else {\n outputData[i] = 0;\n }\n }\n }\n }\n\n return outputBuffer;\n }\n\n /**\n * Mix multiple audio buffers\n */\n mixAudioBuffers(buffers: AudioBuffer[]): AudioBuffer {\n if (buffers.length === 0) {\n return this.audioContext.createBuffer(2, 1, this.sampleRate);\n }\n\n const maxLength = Math.max(...buffers.map(b => b.length));\n const mixedBuffer = this.audioContext.createBuffer(2, maxLength, this.sampleRate);\n\n for (let channel = 0; channel < 2; channel++) {\n const mixedData = mixedBuffer.getChannelData(channel);\n \n buffers.forEach(buffer => {\n const channelData = buffer.getChannelData(Math.min(channel, buffer.numberOfChannels - 1));\n for (let i = 0; i < channelData.length; i++) {\n mixedData[i] = (mixedData[i] || 0) + channelData[i] / buffers.length;\n }\n });\n }\n\n return mixedBuffer;\n }\n\n /**\n * Convert AudioBuffer to WAV format\n */\n audioBufferToWav(buffer: AudioBuffer): ArrayBuffer {\n const numberOfChannels = buffer.numberOfChannels;\n const sampleRate = buffer.sampleRate;\n const format = 1; // PCM\n const bitDepth = 16;\n\n const bytesPerSample = bitDepth / 8;\n const blockAlign = numberOfChannels * bytesPerSample;\n\n const data = new Float32Array(buffer.length * numberOfChannels);\n for (let channel = 0; channel < numberOfChannels; channel++) {\n const channelData = buffer.getChannelData(channel);\n for (let i = 0; i < buffer.length; i++) {\n data[i * numberOfChannels + channel] = channelData[i];\n }\n }\n\n const dataLength = data.length * bytesPerSample;\n const headerLength = 44;\n const wav = new ArrayBuffer(headerLength + dataLength);\n const view = new DataView(wav);\n\n // Write WAV header\n const writeString = (offset: number, string: string) => {\n for (let i = 0; i < string.length; i++) {\n view.setUint8(offset + i, string.charCodeAt(i));\n }\n };\n\n writeString(0, 'RIFF');\n view.setUint32(4, 36 + dataLength, true);\n writeString(8, 'WAVE');\n writeString(12, 'fmt ');\n view.setUint32(16, 16, true); // fmt chunk size\n view.setUint16(20, format, true);\n view.setUint16(22, numberOfChannels, true);\n view.setUint32(24, sampleRate, true);\n view.setUint32(28, sampleRate * blockAlign, true);\n view.setUint16(32, blockAlign, true);\n view.setUint16(34, bitDepth, true);\n writeString(36, 'data');\n view.setUint32(40, dataLength, true);\n\n // Write audio data\n const volume = 0.8;\n let offset = 44;\n for (let i = 0; i < data.length; i++) {\n const sample = Math.max(-1, Math.min(1, data[i]));\n view.setInt16(offset, sample < 0 ? sample * 0x8000 : sample * 0x7FFF, true);\n offset += 2;\n }\n\n return wav;\n }\n\n async close() {\n await this.audioContext.close();\n }\n}\n","'use client';\n\n/**\n * Browser-based audio/video muxing using FFmpeg.wasm (main thread)\n * Compatible with Next.js 15\n *\n * FFmpeg core files must be served from the app's public folder, e.g.:\n * twick-web/public/ffmpeg/ffmpeg-core.js, ffmpeg-core.wasm\n */\n\nexport interface MuxerOptions {\n videoBlob: Blob;\n audioBuffer: ArrayBuffer;\n}\n\n/** Base URL for FFmpeg assets (twick-web public/ffmpeg). Use same-origin URLs directly; toBlobURL causes \"Cannot find module 'blob:...'\" in some environments. */\nfunction getFFmpegBaseURL(): string {\n if (typeof window !== 'undefined') {\n return `${window.location.origin}/ffmpeg`;\n }\n return '/ffmpeg';\n}\n\nexport async function muxAudioVideo(\n options: MuxerOptions\n): Promise<Blob> {\n const muxStartTime = Date.now();\n try {\n console.log('Starting FFmpeg muxing...');\n console.log(` Video blob size: ${options.videoBlob.size} bytes (${(options.videoBlob.size / 1024 / 1024).toFixed(2)} MB)`);\n console.log(` Audio buffer size: ${options.audioBuffer.byteLength} bytes (${(options.audioBuffer.byteLength / 1024 / 1024).toFixed(2)} MB)`);\n \n const { FFmpeg } = await import('@ffmpeg/ffmpeg');\n const { fetchFile } = await import('@ffmpeg/util');\n\n const ffmpeg = new FFmpeg();\n\n const base = getFFmpegBaseURL();\n const coreURL = `${base}/ffmpeg-core.js`;\n const wasmURL = `${base}/ffmpeg-core.wasm`;\n\n console.log(`Loading FFmpeg from ${base}`);\n const loadStartTime = Date.now();\n // Load from same-origin public folder (twick-web/public/ffmpeg). Do NOT use toBlobURL —\n // it produces blob: URLs that can trigger \"Cannot find module 'blob:...'\".\n await ffmpeg.load({\n coreURL,\n wasmURL,\n });\n const loadDuration = Date.now() - loadStartTime;\n console.log(`FFmpeg loaded successfully in ${loadDuration}ms`);\n\n // Write inputs\n console.log('Writing video and audio files...');\n const writeStartTime = Date.now();\n await ffmpeg.writeFile(\n 'video.mp4',\n await fetchFile(options.videoBlob)\n );\n console.log(` Video file written: ${options.videoBlob.size} bytes`);\n\n await ffmpeg.writeFile(\n 'audio.wav',\n new Uint8Array(options.audioBuffer)\n );\n const writeDuration = Date.now() - writeStartTime;\n console.log(` Audio file written: ${options.audioBuffer.byteLength} bytes`);\n console.log(`Files written successfully in ${writeDuration}ms`);\n\n console.log('Executing FFmpeg muxing command...');\n const execStartTime = Date.now();\n \n // Capture FFmpeg logs for debugging\n const ffmpegLogs: string[] = [];\n ffmpeg.on('log', ({ message }: { message: string }) => {\n ffmpegLogs.push(message);\n });\n \n await ffmpeg.exec([\n // Inputs\n '-i', 'video.mp4',\n '-i', 'audio.wav',\n\n // Explicit stream mapping\n '-map', '0:v:0',\n '-map', '1:a:0',\n\n // Re-encode video to a very standard H.264 stream.\n // Copying the WebCodecs/mp4-wasm bitstream can sometimes\n // lead to timing issues where only the first second renders.\n '-c:v', 'libx264',\n '-preset', 'veryfast',\n '-crf', '20',\n\n // AAC audio\n '-c:a', 'aac',\n '-b:a', '192k',\n\n // Make MP4 more web‑friendly\n '-movflags', '+faststart',\n\n // Stop at the shortest of the two streams\n '-shortest',\n\n 'output.mp4',\n ]);\n const execDuration = Date.now() - execStartTime;\n console.log(`FFmpeg muxing completed in ${execDuration}ms`);\n\n const readStartTime = Date.now();\n const data = await ffmpeg.readFile('output.mp4');\n const readDuration = Date.now() - readStartTime;\n console.log(`Output file read successfully in ${readDuration}ms`);\n\n const uint8 =\n typeof data === 'string'\n ? new TextEncoder().encode(data)\n : new Uint8Array(data);\n\n const result = new Blob([uint8], { type: 'video/mp4' });\n const totalDuration = Date.now() - muxStartTime;\n console.log(`Muxing successful: ${result.size} bytes (${(result.size / 1024 / 1024).toFixed(2)} MB) in ${totalDuration}ms`);\n console.log(` Breakdown: load=${loadDuration}ms, write=${writeDuration}ms, exec=${execDuration}ms, read=${readDuration}ms`);\n return result;\n\n } catch (error) {\n const totalDuration = Date.now() - muxStartTime;\n const errorMsg = error instanceof Error ? error.message : String(error);\n const errorStack = error instanceof Error ? error.stack : undefined;\n console.error('FFmpeg muxing failed:', errorMsg);\n if (errorStack) {\n console.error('Error stack:', errorStack);\n }\n console.error('Error details:', {\n errorType: error instanceof Error ? error.constructor.name : typeof error,\n errorMessage: errorMsg,\n duration: `${totalDuration}ms`,\n videoBlobSize: options.videoBlob.size,\n audioBufferSize: options.audioBuffer.byteLength,\n });\n // Re-throw the error so the caller can handle it\n throw error;\n }\n}\n","'use client';\n\n/**\n * Browser-based video normalization using FFmpeg.wasm (main thread)\n * Compatible with Next.js 15\n *\n * This helper converts \"problematic\" videos (e.g. WhatsApp exports) into a\n * very standard H.264 + AAC, 30fps, yuv420p MP4 that behaves well with\n * WebCodecs and <video> playback, reducing the chance of frozen-first-frame\n * issues during browser rendering.\n *\n * FFmpeg core files must be served from the app's public folder, e.g.:\n * twick-web/public/ffmpeg/ffmpeg-core.js, ffmpeg-core.wasm\n */\n\n/** Normalization options */\nexport interface NormalizeVideoOptions {\n /** Target width in pixels (default: 720). Height is derived to preserve aspect ratio. */\n width?: number;\n /** Target frames per second (default: 30). */\n fps?: number;\n}\n\n/** Result of normalization */\nexport interface NormalizeVideoResult {\n /** Normalized video Blob (MP4, H.264 + AAC). */\n blob: Blob;\n /** Size in bytes of the normalized video. */\n size: number;\n /** Optional debug information (durations, etc.). */\n debug?: {\n loadMs: number;\n writeMs: number;\n execMs: number;\n readMs: number;\n totalMs: number;\n };\n}\n\n/** Base URL for FFmpeg assets (twick-web public/ffmpeg). */\nfunction getFFmpegBaseURL(): string {\n if (typeof window !== 'undefined') {\n return `${window.location.origin}/ffmpeg`;\n }\n return '/ffmpeg';\n}\n\n/**\n * Normalize a video Blob into a browser- and WebCodecs-friendly MP4.\n *\n * Typical usage:\n * - Call this after file upload (e.g. from an <input type=\"file\" />)\n * - Upload the returned Blob to your storage (S3, etc.)\n * - Use that URL in your Twick project instead of the raw source\n */\nexport async function normalizeVideoBlob(\n input: Blob,\n options: NormalizeVideoOptions = {}\n): Promise<NormalizeVideoResult> {\n const startTime = Date.now();\n const targetWidth = options.width ?? 720;\n const targetFps = options.fps ?? 30;\n\n try {\n console.log('[VideoNormalizer] Starting normalization...');\n console.log(`[VideoNormalizer] Input size: ${input.size} bytes (${(input.size / 1024 / 1024).toFixed(2)} MB)`);\n\n const { FFmpeg } = await import('@ffmpeg/ffmpeg');\n const { fetchFile } = await import('@ffmpeg/util');\n\n const ffmpeg = new FFmpeg();\n const base = getFFmpegBaseURL();\n const coreURL = `${base}/ffmpeg-core.js`;\n const wasmURL = `${base}/ffmpeg-core.wasm`;\n\n console.log(`[VideoNormalizer] Loading FFmpeg from ${base}`);\n const loadStart = Date.now();\n await ffmpeg.load({ coreURL, wasmURL });\n const loadMs = Date.now() - loadStart;\n console.log(`[VideoNormalizer] FFmpeg loaded in ${loadMs}ms`);\n\n // Write input file\n console.log('[VideoNormalizer] Writing input file...');\n const writeStart = Date.now();\n await ffmpeg.writeFile('in.mp4', await fetchFile(input));\n const writeMs = Date.now() - writeStart;\n console.log(`[VideoNormalizer] Input file written in ${writeMs}ms`);\n\n // Normalize: scale, fps, H.264, AAC, yuv420p, faststart\n console.log('[VideoNormalizer] Executing normalization command...');\n const execStart = Date.now();\n\n // Capture logs for debugging\n ffmpeg.on('log', ({ message }: { message: string }) => {\n console.log(`[VideoNormalizer:FFmpeg] ${message}`);\n });\n\n await ffmpeg.exec([\n '-i', 'in.mp4',\n // Normalize geometry & frame rate\n '-vf', `scale=${targetWidth}:-2,fps=${targetFps},format=yuv420p`,\n // Standard H.264 video\n '-c:v', 'libx264',\n '-preset', 'veryfast',\n '-crf', '20',\n '-pix_fmt', 'yuv420p',\n '-profile:v', 'main',\n '-r', String(targetFps),\n // AAC audio, stereo, 48kHz\n '-c:a', 'aac',\n '-b:a', '128k',\n '-ar', '48000',\n '-ac', '2',\n // Web-friendly MP4\n '-movflags', '+faststart',\n 'out.mp4',\n ]);\n\n const execMs = Date.now() - execStart;\n console.log(`[VideoNormalizer] Normalization completed in ${execMs}ms`);\n\n // Read back normalized file\n const readStart = Date.now();\n const data = await ffmpeg.readFile('out.mp4');\n const readMs = Date.now() - readStart;\n console.log(`[VideoNormalizer] Output file read in ${readMs}ms`);\n\n const uint8 =\n typeof data === 'string'\n ? new TextEncoder().encode(data)\n : new Uint8Array(data);\n\n const blob = new Blob([uint8], { type: 'video/mp4' });\n const totalMs = Date.now() - startTime;\n\n console.log(`[VideoNormalizer] Normalization successful: ${blob.size} bytes (${(blob.size / 1024 / 1024).toFixed(2)} MB) in ${totalMs}ms`);\n\n return {\n blob,\n size: blob.size,\n debug: {\n loadMs,\n writeMs,\n execMs,\n readMs,\n totalMs,\n },\n };\n } catch (error) {\n const totalMs = Date.now() - startTime;\n const msg = error instanceof Error ? error.message : String(error);\n const stack = error instanceof Error ? error.stack : undefined;\n\n console.error('[VideoNormalizer] Normalization failed:', msg);\n if (stack) {\n console.error('[VideoNormalizer] Stack:', stack);\n }\n console.error('[VideoNormalizer] Duration:', `${totalMs}ms`);\n\n throw error;\n }\n}\n\n","import { useState, useCallback } from 'react';\nimport { renderTwickVideoInBrowser, downloadVideoBlob } from '../browser-renderer';\nimport type { BrowserRenderConfig } from '../browser-renderer';\n\nexport interface UseBrowserRendererOptions {\n /** \n * Custom Project object\n * If not provided, defaults to @twick/visualizer project\n * \n * Note: Must be an imported Project object, not a string path.\n * String paths only work in Node.js (server renderer).\n * \n * Example:\n * ```typescript\n * import myProject from './my-custom-project';\n * useBrowserRenderer({ projectFile: myProject })\n * ```\n */\n projectFile?: any;\n /** Video width in pixels */\n width?: number;\n /** Video height in pixels */\n height?: number;\n /** Frames per second */\n fps?: number;\n /** Render quality */\n quality?: 'low' | 'medium' | 'high';\n /** Time range to render [start, end] in seconds */\n range?: [number, number];\n /** Include audio in rendered video (experimental) */\n includeAudio?: boolean;\n /** Download audio separately as WAV file */\n downloadAudioSeparately?: boolean;\n /** Callback when audio is ready */\n onAudioReady?: (audioBlob: Blob) => void;\n /** Automatically download the video when rendering completes */\n autoDownload?: boolean;\n /** Default filename for downloads */\n downloadFilename?: string;\n}\n\nexport interface UseBrowserRendererReturn {\n /** Start rendering the video */\n render: (variables: BrowserRenderConfig['variables']) => Promise<Blob | null>;\n /** Current rendering progress (0-1) */\n progress: number;\n /** Whether rendering is in progress */\n isRendering: boolean;\n /** Error if rendering failed */\n error: Error | null;\n /** The rendered video blob (available after rendering completes) */\n videoBlob: Blob | null;\n /** Download the rendered video */\n download: (filename?: string) => void;\n /** Reset the renderer state */\n reset: () => void;\n}\n\n/**\n * React hook for rendering Twick videos in the browser\n * \n * Uses the same pattern as the server renderer for consistency.\n * \n * @param options - Rendering options\n * @returns Renderer state and control functions\n * \n * @example\n * ```tsx\n * import { useBrowserRenderer } from '@twick/browser-render';\n * \n * // Using default visualizer project\n * function MyComponent() {\n * const { render, progress, isRendering, videoBlob, download } = useBrowserRenderer({\n * width: 1920,\n * height: 1080,\n * fps: 30,\n * autoDownload: true,\n * });\n * \n * const handleRender = async () => {\n * await render({\n * input: {\n * properties: { width: 1920, height: 1080, fps: 30 },\n * tracks: [\n * // Your tracks configuration\n * ]\n * }\n * });\n * };\n * \n * return (\n * <div>\n * <button onClick={handleRender} disabled={isRendering}>\n * {isRendering ? `Rendering... ${(progress * 100).toFixed(0)}%` : 'Render Video'}\n * </button>\n * {videoBlob && !autoDownload && (\n * <button onClick={() => download('my-video.mp4')}>Download</button>\n * )}\n * </div>\n * );\n * }\n * \n * // Using custom project (must import it first)\n * import myProject from './my-project';\n * \n * function CustomProjectComponent() {\n * const { render } = useBrowserRenderer({\n * projectFile: myProject, // Pass the imported project object\n * width: 1920,\n * height: 1080,\n * });\n * \n * // ... rest of component\n * }\n * ```\n */\nexport const useBrowserRenderer = (options: UseBrowserRendererOptions = {}): UseBrowserRendererReturn => {\n const [progress, setProgress] = useState(0);\n const [isRendering, setIsRendering] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [videoBlob, setVideoBlob] = useState<Blob | null>(null);\n\n const reset = useCallback(() => {\n setProgress(0);\n setIsRendering(false);\n setError(null);\n setVideoBlob(null);\n }, []);\n\n const download = useCallback((filename?: string) => {\n if (!videoBlob) {\n setError(new Error('No video available to download. Please render the video first.'));\n return;\n }\n try {\n downloadVideoBlob(videoBlob, filename || options.downloadFilename || 'video.mp4');\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to download video'));\n }\n }, [videoBlob, options.downloadFilename]);\n\n const render = useCallback(async (variables: BrowserRenderConfig['variables']): Promise<Blob | null> => {\n reset();\n setIsRendering(true);\n\n try {\n const { projectFile, width, height, fps, quality, range, includeAudio, downloadAudioSeparately, onAudioReady, autoDownload, downloadFilename, ...restOptions } = options;\n \n const blob = await renderTwickVideoInBrowser({\n projectFile,\n variables,\n settings: {\n width,\n height,\n includeAudio,\n downloadAudioSeparately,\n onAudioReady,\n fps,\n quality,\n range,\n ...restOptions,\n onProgress: (p) => {\n setProgress(p);\n },\n onComplete: (blob) => {\n console.log('[BrowserRender] useBrowserRenderer: onComplete received blob', blob ? `size=${blob.size} type=${blob.type}` : 'null');\n setVideoBlob(blob);\n if (autoDownload) {\n try {\n downloadVideoBlob(blob, downloadFilename || 'video.mp4');\n } catch (downloadErr) {\n setError(downloadErr instanceof Error ? downloadErr : new Error('Failed to auto-download video'));\n }\n }\n },\n onError: (err) => {\n console.error('[BrowserRender] useBrowserRenderer: onError', err?.message);\n setError(err);\n },\n },\n });\n\n if (!blob) {\n throw new Error('Rendering failed: No video blob was generated');\n }\n\n setVideoBlob(blob);\n setProgress(1);\n return blob;\n } catch (err) {\n const errorMsg = err instanceof Error ? err.message : String(err);\n console.error('[BrowserRender] useBrowserRenderer: render failed', errorMsg);\n setError(err instanceof Error ? err : new Error(String(err)));\n return null;\n } finally {\n setIsRendering(false);\n }\n }, [options, reset]);\n\n return {\n render,\n progress,\n isRendering,\n error,\n videoBlob,\n download,\n reset,\n };\n};\n\nexport default useBrowserRenderer;\n"],"mappings":"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","names":["offset","buf","defaultProject","getFFmpegBaseURL","blob"]}