@meframe/core 0.2.4 → 0.2.5

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.
Files changed (50) hide show
  1. package/dist/Meframe.d.ts +7 -1
  2. package/dist/Meframe.d.ts.map +1 -1
  3. package/dist/Meframe.js +7 -27
  4. package/dist/Meframe.js.map +1 -1
  5. package/dist/controllers/ExportController.d.ts +7 -1
  6. package/dist/controllers/ExportController.d.ts.map +1 -1
  7. package/dist/controllers/ExportController.js.map +1 -1
  8. package/dist/node_modules/.pnpm/mp4-muxer@5.2.2/node_modules/mp4-muxer/build/mp4-muxer.js.map +1 -0
  9. package/dist/{medeo-fe/node_modules → node_modules}/.pnpm/mp4box@0.5.4/node_modules/mp4box/dist/mp4box.all.js +2 -2
  10. package/dist/node_modules/.pnpm/mp4box@0.5.4/node_modules/mp4box/dist/mp4box.all.js.map +1 -0
  11. package/dist/orchestrator/AudioExportSession.d.ts +3 -1
  12. package/dist/orchestrator/AudioExportSession.d.ts.map +1 -1
  13. package/dist/orchestrator/AudioExportSession.js +28 -20
  14. package/dist/orchestrator/AudioExportSession.js.map +1 -1
  15. package/dist/orchestrator/ExportScheduler.d.ts +5 -1
  16. package/dist/orchestrator/ExportScheduler.d.ts.map +1 -1
  17. package/dist/orchestrator/ExportScheduler.js +20 -2
  18. package/dist/orchestrator/ExportScheduler.js.map +1 -1
  19. package/dist/orchestrator/OnDemandVideoSession.js +2 -2
  20. package/dist/orchestrator/OnDemandVideoSession.js.map +1 -1
  21. package/dist/orchestrator/Orchestrator.d.ts +1 -1
  22. package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
  23. package/dist/orchestrator/Orchestrator.js.map +1 -1
  24. package/dist/orchestrator/types.d.ts +1 -1
  25. package/dist/orchestrator/types.d.ts.map +1 -1
  26. package/dist/stages/compose/FrameRateConverter.d.ts +6 -2
  27. package/dist/stages/compose/FrameRateConverter.d.ts.map +1 -1
  28. package/dist/stages/compose/OfflineAudioMixer.d.ts.map +1 -1
  29. package/dist/stages/compose/OfflineAudioMixer.js +8 -0
  30. package/dist/stages/compose/OfflineAudioMixer.js.map +1 -1
  31. package/dist/stages/mux/MP4Muxer.d.ts +11 -2
  32. package/dist/stages/mux/MP4Muxer.d.ts.map +1 -1
  33. package/dist/stages/mux/MP4Muxer.js +32 -6
  34. package/dist/stages/mux/MP4Muxer.js.map +1 -1
  35. package/dist/stages/mux/MuxManager.d.ts +5 -1
  36. package/dist/stages/mux/MuxManager.d.ts.map +1 -1
  37. package/dist/stages/mux/MuxManager.js +15 -7
  38. package/dist/stages/mux/MuxManager.js.map +1 -1
  39. package/dist/types.d.ts +8 -0
  40. package/dist/types.d.ts.map +1 -1
  41. package/dist/utils/mp4box.js +1 -1
  42. package/dist/workers/MP4Demuxer.DfWiwyjB.js.map +1 -1
  43. package/dist/workers/stages/compose/{video-compose.worker.CA2_Kpg-.js → video-compose.worker.KMZjuJuY.js} +47 -4
  44. package/dist/workers/stages/compose/video-compose.worker.KMZjuJuY.js.map +1 -0
  45. package/dist/workers/worker-manifest.json +1 -1
  46. package/package.json +1 -1
  47. package/dist/medeo-fe/node_modules/.pnpm/mp4-muxer@5.2.2/node_modules/mp4-muxer/build/mp4-muxer.js.map +0 -1
  48. package/dist/medeo-fe/node_modules/.pnpm/mp4box@0.5.4/node_modules/mp4box/dist/mp4box.all.js.map +0 -1
  49. package/dist/workers/stages/compose/video-compose.worker.CA2_Kpg-.js.map +0 -1
  50. /package/dist/{medeo-fe/node_modules → node_modules}/.pnpm/mp4-muxer@5.2.2/node_modules/mp4-muxer/build/mp4-muxer.js +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"Orchestrator.js","sources":["../../src/orchestrator/Orchestrator.ts"],"sourcesContent":["import { EventBus } from '../event/EventBus';\nimport { WorkerPool } from '../worker/WorkerPool';\nimport { applyPatch as applyModelPatch } from '../model/patch';\nimport { ResourceLoader } from '../stages/load/ResourceLoader';\nimport { CacheManager } from '../cache/CacheManager';\nimport { ConfigLoader } from '../config/ConfigLoader';\nimport type { IOrchestrator, OrchestratorConfig, RenderFrameOptions } from './types';\nimport { WorkerType } from '../worker/types';\nimport { CompositionModel, CompositionPatch, Resource, TimeUs, RcFrame, Clip } from '../model';\nimport { hasResourceId } from '../model/types';\nimport { MeframeEvent, type EventPayloadMap } from '../event/events';\nimport { CompositionPlanner } from './CompositionPlanner';\nimport { AudioWindowPreparer } from './AudioWindowPreparer';\nimport { AudioPreviewSession } from './AudioPreviewSession';\nimport { AudioExportSession } from './AudioExportSession';\nimport { MuxManager } from '../stages/mux/MuxManager';\nimport { ExportOptions } from '../types';\nimport { OnDemandVideoSession } from './OnDemandVideoSession';\nimport { ExportScheduler } from './ExportScheduler';\nimport { filterRenderConfig } from '../utils/object-utils';\n\nexport class Orchestrator implements IOrchestrator {\n workers: WorkerPool;\n eventBus: EventBus<EventPayloadMap>;\n compositionModel: CompositionModel | null = null;\n resourceLoader: ResourceLoader;\n cacheManager: CacheManager;\n planner: CompositionPlanner;\n audio: {\n prepare: AudioWindowPreparer;\n preview: AudioPreviewSession;\n export: AudioExportSession;\n };\n muxManager: MuxManager;\n exportScheduler: ExportScheduler;\n\n private isInitialized = false;\n private config = ConfigLoader.getInstance().getConfig();\n private ensureCacheDebounceTimer: number | null = null;\n private activeOnDemandSession: OnDemandVideoSession | null = null;\n private modelToken = 0;\n readonly events: Pick<EventBus<EventPayloadMap>, 'on' | 'off' | 'once'>;\n\n constructor(config: OrchestratorConfig) {\n // Use provided eventBus or create a new one\n this.eventBus = config.eventBus || new EventBus<EventPayloadMap>();\n this.events = this.eventBus.asReadonly();\n\n // Initialize config first\n this.config = ConfigLoader.getInstance().getConfig();\n\n const workerConfigs = this.buildWorkerConfigs();\n\n // Initialize WorkerPool with worker path from config\n this.workers = new WorkerPool({\n eventBus: this.eventBus,\n workerConfigs,\n workerPath: config.workerPath,\n workerExtension: config.workerExtension,\n });\n\n const maxMemoryMB = config.cacheConfig?.l1Size || this.config.cache?.l1?.maxMemoryMB || 1024;\n const maxGOPs = this.config.decode?.video?.maxGOPs || 4;\n\n this.cacheManager = new CacheManager(\n {\n l1: {\n maxMemoryMB,\n maxGOPs,\n },\n resource: {\n projectId: config.projectId || this.config.global.projectId || 'default',\n },\n },\n this.eventBus\n );\n\n this.resourceLoader = new ResourceLoader({\n cacheManager: this.cacheManager,\n eventBus: this.eventBus,\n config: {\n maxConcurrent: this.config.load.maxConcurrent,\n preloadConcurrency: 2, // Fixed preload concurrency for idle background loading\n },\n onStateChange: (resourceId, state) => this.handleResourceStateChange(resourceId, state),\n });\n\n this.planner = new CompositionPlanner();\n\n const audioPreparer = new AudioWindowPreparer({\n cacheManager: this.cacheManager,\n resourceLoader: this.resourceLoader,\n eventBus: this.eventBus,\n });\n\n const audioPreview = new AudioPreviewSession({\n cacheManager: this.cacheManager,\n preparer: audioPreparer,\n });\n\n const audioExport = new AudioExportSession({\n cacheManager: this.cacheManager,\n preparer: audioPreparer,\n });\n\n this.audio = { prepare: audioPreparer, preview: audioPreview, export: audioExport };\n\n this.muxManager = new MuxManager();\n\n this.exportScheduler = new ExportScheduler({\n workerPool: this.workers,\n planner: this.planner,\n cacheManager: this.cacheManager,\n resourceLoader: this.resourceLoader,\n muxManager: this.muxManager,\n audioSession: this.audio.export,\n workerConfigsProvider: () => this.buildWorkerConfigs(),\n eventBus: this.eventBus,\n });\n\n this.setupResourceFirstFrameHandler();\n }\n\n private setupResourceFirstFrameHandler(): void {\n this.eventBus.on(MeframeEvent.ResourceFirstFrameReady, async (payload) => {\n const { resourceId, clipId, index, chunks } = payload;\n\n if (!this.compositionModel) return;\n\n // Find the specific clip\n const clip = this.compositionModel.findClip(clipId);\n if (!clip || !clip.trackId) return;\n\n // Only decode first frame for clips that start at composition time 0\n // (these clips need the resource's first frame as cover)\n if (clip.startUs === 0) {\n const fps = this.compositionModel.fps ?? 30;\n await OnDemandVideoSession.decodeAndCacheFirstFrame(\n resourceId,\n chunks,\n index,\n clip,\n this.cacheManager,\n fps\n );\n }\n });\n }\n\n async initialize(): Promise<void> {\n if (this.isInitialized) return;\n\n await this.cacheManager.init();\n\n this.isInitialized = true;\n }\n\n // Event methods - forward to eventBus\n on<K extends keyof EventPayloadMap>(\n event: K,\n handler: (payload: EventPayloadMap[K]) => void\n ): void {\n this.eventBus.on(event, handler);\n }\n\n off<K extends keyof EventPayloadMap>(\n event: K,\n handler: (payload: EventPayloadMap[K]) => void\n ): void {\n this.eventBus.off(event, handler);\n }\n\n once<K extends keyof EventPayloadMap>(\n event: K,\n handler: (payload: EventPayloadMap[K]) => void\n ): void {\n this.eventBus.once(event, handler);\n }\n\n cancelActiveDecoding(): void {\n if (this.activeOnDemandSession) {\n void this.activeOnDemandSession.dispose();\n this.activeOnDemandSession = null;\n }\n }\n\n /**\n * Decode and render nearest keyframe for fast seek preview\n * Returns the keyframe timestamp if successfully decoded, null otherwise\n */\n async tryRenderKeyframe(timeUs: TimeUs): Promise<TimeUs | null> {\n if (!this.compositionModel) return null;\n\n const clip = this.compositionModel.getClipsAtTime(timeUs, this.compositionModel.mainTrackId)[0];\n if (!clip || !hasResourceId(clip)) return null;\n\n // Use resource-relative time (same as frame.timestamp from decoder)\n const resourceTimeUs = timeUs - clip.startUs + (clip.trimStartUs ?? 0);\n const resourceId = clip.resourceId;\n\n // Check if keyframe is already in L1 cache\n const keyframeSample = this.cacheManager.mp4IndexCache.getNearestKeyframe(\n resourceId,\n resourceTimeUs\n );\n\n if (!keyframeSample) return null;\n\n const cachedKeyframe = this.cacheManager.getFrame(keyframeSample.timestamp, clip.id);\n if (cachedKeyframe) {\n return keyframeSample.timestamp;\n }\n\n // Resource must be ready to decode keyframe\n const resource = this.compositionModel.getResource(resourceId);\n if (resource?.state !== 'ready') return null;\n\n // Create temporary session to decode keyframe\n const session = await OnDemandVideoSession.create({\n clipId: clip.id,\n resourceId,\n targetTimeUs: resourceTimeUs,\n globalTimeUs: timeUs,\n resourceLoader: this.resourceLoader,\n mp4IndexCache: this.cacheManager.mp4IndexCache,\n cacheManager: this.cacheManager,\n compositionModel: this.compositionModel,\n fps: this.compositionModel.fps ?? 30,\n });\n\n try {\n const keyframeTimeUs = await session.decodeKeyframe(resourceTimeUs);\n return keyframeTimeUs;\n } catch (error) {\n console.warn('[Orchestrator] Fast keyframe decode failed:', error);\n return null;\n } finally {\n await session.dispose();\n }\n }\n\n async setCompositionModel(model: CompositionModel): Promise<void> {\n this.cacheManager.clear();\n this.cancelActiveDecoding();\n this.audio.preview.stopPlayback();\n this.audio.preview.invalidatePreviewMixCache();\n this.modelToken++;\n this.compositionModel = model;\n this.planner.setModel(model);\n\n // IMPORTANT: avoid a window where audio.prepare still references the old model while\n // resourceLoader already switched (resourceLoader.setModel sets this.model before awaiting).\n // This matters when the host app triggers play/seek without awaiting setCompositionModel().\n const resourceLoaderSetModelPromise = this.resourceLoader.setModel(model);\n this.audio.prepare.setModel(model);\n // ensure the cover resource is preloaded before preview starts\n await resourceLoaderSetModelPromise;\n\n this.eventBus.emit(MeframeEvent.ModelSet, model);\n\n this.eventBus.emit(MeframeEvent.CompositionUpdated, {\n trackCount: model.tracks.length,\n clipCount: model.tracks.reduce((acc: number, track: any) => acc + track.clips.length, 0),\n durationUs: model.durationUs,\n });\n }\n\n async applyPatch(patch: CompositionPatch): Promise<void> {\n if (!this.compositionModel) {\n throw new Error('No composition model set');\n }\n\n // Apply patch and get affected clip IDs (simplified for 2-Clip strategy)\n // Note: addTrack/removeTrack already call buildIndexes() in patch.ts\n const affectedClipIds = applyModelPatch(this.compositionModel, patch);\n this.planner.applyPatch(patch, affectedClipIds);\n this.eventBus.emit(MeframeEvent.PatchApplied, {\n operations: patch.operations.length,\n affectedClips: Array.from(affectedClipIds),\n });\n\n // Process clip updates\n for (const clipId of affectedClipIds) {\n this.cacheManager.invalidateClip(clipId);\n }\n\n // Reactivate updated audio clips\n const reactivatedAudioClips: string[] = [];\n const reactivatedVideoClips: string[] = [];\n for (const clipId of affectedClipIds) {\n const clip = this.compositionModel.findClip(clipId);\n if (clip?.trackKind === 'audio') {\n await this.audio.prepare.deactivateClip(clipId);\n reactivatedAudioClips.push(clipId);\n } else if (clip?.trackKind === 'video') {\n reactivatedVideoClips.push(clipId);\n }\n }\n\n // Activate all audio clips (including reactivated ones)\n await this.audio.prepare.activateAllAudioClips();\n\n // Note: No need to restart per-clip playback in new architecture\n // scheduleAudio() uses OfflineAudioMixer which automatically includes all active clips\n }\n\n private handleResourceStateChange(resourceId: string, state: Resource['state']): void {\n if (!this.compositionModel) {\n return;\n }\n\n this.compositionModel.updateResourceState(resourceId, state ?? 'pending');\n\n if (state !== 'ready') {\n return;\n }\n\n // For preview, simple cache invalidation or triggering re-render might be enough\n // if we were caching instructions. But PlaybackController pulls instructions every frame.\n // So just updating Model state is enough.\n }\n\n async getFrame(timeUs: TimeUs, options?: RenderFrameOptions): Promise<RcFrame | null> {\n const signal = options?.signal;\n const preheat = options?.preheat ?? false;\n const mode = options?.mode ?? 'blocking';\n\n if (!this.compositionModel) {\n throw new Error('No composition model set');\n }\n\n const clip = this.compositionModel.getClipsAtTime(timeUs, this.compositionModel.mainTrackId)[0];\n if (!clip) {\n return null;\n }\n\n const trimStartUs = clip.trimStartUs ?? 0;\n\n // Calculate resource-relative time (same as frame.timestamp from decoder)\n // resourceTimeUs = clipRelativeTime + trimStartUs\n let resourceTimeUs = options?.relativeTimeUs\n ? options.relativeTimeUs + trimStartUs\n : timeUs - clip.startUs + trimStartUs;\n\n // Clamp to valid range: [trimStartUs, trimStartUs + durationUs)\n resourceTimeUs = Math.min(resourceTimeUs, trimStartUs + clip.durationUs - 1);\n\n // 1. Check L1 window cache\n // Note: preheat mode skips cache check to force decoding the entire window\n // Why: Video cache is frame-level (point query), not window-level (range query)\n // A single cached frame doesn't guarantee the entire window is cached\n // Without preheat flag, preheating would return early on first frame hit\n if (!preheat) {\n const cachedFrame = this.cacheManager.getFrame(resourceTimeUs, clip.id);\n if (cachedFrame) {\n return cachedFrame;\n }\n }\n\n if (signal?.aborted) {\n return null;\n }\n\n // 2. Try decode from OPFS resource (on-demand path)\n const resourceFrame = await this.decodeFromResource(clip, resourceTimeUs, timeUs, {\n ...options,\n mode,\n });\n return resourceFrame;\n }\n\n /**\n * Compose frame from OPFS resource (on-demand decoding)\n * This is the new path for long clips with window caching\n *\n * @param clip - The clip to decode\n * @param resourceTimeUs - Time in resource-relative coordinates (same as frame.timestamp)\n * @param globalTimeUs - Time in composition timeline\n */\n private async decodeFromResource(\n clip: Clip,\n resourceTimeUs: TimeUs,\n globalTimeUs: TimeUs,\n options?: RenderFrameOptions\n ): Promise<RcFrame | null> {\n if (!hasResourceId(clip)) return null;\n\n const resourceId = clip.resourceId;\n const trimStartUs = clip.trimStartUs ?? 0;\n const startModelToken = this.modelToken;\n const startModel = this.compositionModel;\n\n // Check resource state\n const resource = this.compositionModel?.getResource(resourceId);\n const isReady = resource?.state === 'ready';\n\n const fetchOptions = {\n isPreload: false,\n clipId: clip.id,\n trackId: clip.trackId,\n };\n\n const mode = options?.mode ?? 'blocking';\n\n // In probe mode, if not ready, trigger fetch in background and return null.\n // Note: If resource is already ready, probe mode behaves the same as blocking mode\n // (it may still decode from OPFS). The only guaranteed non-blocking part is resource loading.\n if (mode === 'probe' && !isReady) {\n void this.resourceLoader.load(resourceId, fetchOptions);\n return null;\n }\n\n // Normal mode: wait for download\n await this.resourceLoader.load(resourceId, fetchOptions);\n\n if (this.modelToken !== startModelToken || this.compositionModel !== startModel) {\n console.warn('[Orchestrator] Model switched during decodeFromResource:', {\n startModelToken,\n currentModelToken: this.modelToken,\n clipId: clip.id,\n resourceId,\n globalTimeUs,\n resourceTimeUs,\n mode,\n });\n return null;\n }\n\n this.cancelActiveDecoding();\n\n // Create temporary on-demand video session\n const session = await OnDemandVideoSession.create({\n clipId: clip.id,\n resourceId,\n targetTimeUs: resourceTimeUs,\n globalTimeUs,\n resourceLoader: this.resourceLoader,\n mp4IndexCache: this.cacheManager.mp4IndexCache,\n cacheManager: this.cacheManager,\n compositionModel: this.compositionModel!,\n fps: this.compositionModel?.fps ?? 30,\n });\n\n this.activeOnDemandSession = session;\n\n try {\n // Decode window: from target position to target + 3s\n // Window bounds are in resource time: [trimStartUs, trimStartUs + durationUs)\n const DECODE_WINDOW_SIZE = 3_000_000;\n\n const windowStart = resourceTimeUs;\n const windowEnd = Math.min(\n trimStartUs + clip.durationUs,\n resourceTimeUs + DECODE_WINDOW_SIZE\n );\n\n await session.decodeWindow(windowStart, windowEnd);\n // Return target frame from L1 cache\n return this.cacheManager.getFrame(resourceTimeUs, clip.id);\n } catch (error) {\n if (session.isDisposed) {\n return null;\n }\n console.error('[Orchestrator] Error composing from resource:', error);\n return null;\n } finally {\n if (this.activeOnDemandSession === session) {\n this.activeOnDemandSession = null;\n }\n await session.dispose();\n }\n }\n\n /**\n * Wait for clip cache to be ready for playback\n * Returns true if minimum cache is ready, false if timeout\n */\n async waitForClipReady(\n timeUs: TimeUs,\n options?: { minFrameCount?: number; timeoutMs?: number }\n ): Promise<boolean> {\n if (!this.compositionModel) {\n return false;\n }\n\n const clips = this.compositionModel.getClipsAtTime(timeUs, this.compositionModel.mainTrackId);\n if (clips.length === 0) {\n return true;\n }\n\n const currentClip = clips[0];\n if (!currentClip) {\n return true;\n }\n\n return this.cacheManager.waitForClipReady(currentClip.id, {\n minFrameCount: options?.minFrameCount ?? 5,\n timeoutMs: options?.timeoutMs ?? 5_000,\n });\n }\n\n async dispose(): Promise<void> {\n if (this.ensureCacheDebounceTimer !== null) {\n clearTimeout(this.ensureCacheDebounceTimer);\n this.ensureCacheDebounceTimer = null;\n }\n\n this.resourceLoader.dispose();\n await this.cacheManager.clear();\n\n this.workers.terminateAll();\n this.compositionModel = null;\n this.eventBus.dispose();\n }\n\n private buildWorkerConfigs(): Record<WorkerType, any> {\n const config = this.config;\n const defaultCanvasWidth = config.global.defaultCanvasWidth;\n const defaultCanvasHeight = config.global.defaultCanvasHeight;\n const defaultFps = config.global.defaultFps;\n\n const targetFps = this.compositionModel?.fps ?? defaultFps;\n\n return {\n // videoDemux: { // DEPRECATED: Removed - replaced by IndexedVideoSource\n // highWaterMark: config.demux.backpressure.highWaterMark,\n // },\n audioDemux: {\n highWaterMark: config.demux.backpressure.highWaterMark,\n },\n // videoDecode: config.decode.video, // DEPRECATED: Removed - replaced by OnDemandVideoSession\n audioDecode: config.decode.audio,\n videoCompose: {\n width: defaultCanvasWidth,\n height: defaultCanvasHeight,\n fps: targetFps,\n backgroundColor: '#000000',\n enableSmoothing: true,\n enableHardwareAcceleration: true,\n fonts: config.global.fonts,\n },\n audioCompose: {\n enableDucking: config.compose.audio.enableDucking,\n },\n videoEncode: {\n // Main Profile Level 4.1 - better compression efficiency for social media\n codec: 'avc1.4D0029',\n width: defaultCanvasWidth,\n height: defaultCanvasHeight,\n bitrate: config.encode.video.bitrateKbps\n ? config.encode.video.bitrateKbps * 1000\n : this.calculateDefaultBitrate(defaultCanvasWidth, defaultCanvasHeight),\n framerate: targetFps,\n latencyMode: 'quality',\n bitrateMode: 'variable',\n hardwareAcceleration: 'no-preference',\n // Default 1 second keyframe interval for better social media compatibility\n keyFrameInterval: config.encode.video.keyIntervalS\n ? Math.round(config.encode.video.keyIntervalS * targetFps)\n : targetFps,\n ...config.encode.video,\n },\n };\n }\n\n /**\n * Calculate default video bitrate based on resolution\n * Optimized for social media platforms (YouTube, TikTok, WeChat, etc.)\n */\n private calculateDefaultBitrate(width: number, height: number): number {\n const pixels = width * height;\n\n // Bitrate recommendations for H.264 Main Profile VBR:\n // - 720p (921,600 px): 5 Mbps\n // - 1080p (2,073,600 px): 8 Mbps\n // - 4K (8,294,400 px): 25 Mbps\n if (pixels <= 921_600) {\n // 720p and below\n return 5_000_000;\n } else if (pixels <= 2_073_600) {\n // 1080p\n return 8_000_000;\n } else if (pixels <= 3_686_400) {\n // 1440p (2K)\n return 16_000_000;\n } else {\n // 4K and above\n return 25_000_000;\n }\n }\n\n async export(model: CompositionModel, options: ExportOptions): Promise<Blob> {\n return this.exportScheduler.execute(model, options);\n }\n\n /**\n * Preheat a specific clip's window range\n * Used by PlaybackController for cross-clip window preheating\n *\n * @param clipId - Clip identifier\n * @param clipRelativeStart - Start time relative to clip (microseconds), 0 = clip start\n * @param clipRelativeEnd - End time relative to clip (microseconds)\n * @param globalTimeUs - Global timeline position (for globalTimeUs in cache)\n */\n async preheatClipWindow(\n clipId: string,\n clipRelativeStart: TimeUs,\n clipRelativeEnd: TimeUs,\n globalTimeUs: TimeUs\n ): Promise<void> {\n if (!this.compositionModel) return;\n\n const clip = this.compositionModel.findClip(clipId);\n if (!clip || !hasResourceId(clip)) return;\n\n const resourceId = clip.resourceId;\n const trimStartUs = clip.trimStartUs ?? 0;\n\n // Convert clip-relative time to resource time\n const resourceStart = clipRelativeStart + trimStartUs;\n const resourceEnd = clipRelativeEnd + trimStartUs;\n\n // Ensure resource is downloaded\n await this.resourceLoader.load(resourceId, {\n isPreload: false,\n clipId: clip.id,\n trackId: clip.trackId,\n });\n\n // Create temporary on-demand session for this window\n const session = await OnDemandVideoSession.create({\n clipId: clip.id,\n resourceId,\n targetTimeUs: resourceStart,\n globalTimeUs,\n resourceLoader: this.resourceLoader,\n mp4IndexCache: this.cacheManager.mp4IndexCache,\n cacheManager: this.cacheManager,\n compositionModel: this.compositionModel,\n fps: this.compositionModel.fps ?? 30,\n });\n\n try {\n // Decode the entire window range for this clip (using resource time)\n await session.decodeWindow(resourceStart, resourceEnd);\n } catch (error) {\n console.warn(`[Orchestrator] Preheat clip ${clipId} window failed:`, error);\n // Non-critical, don't throw\n } finally {\n await session.dispose();\n }\n }\n\n /**\n * Get render state for real-time composition\n * Returns layers ready for VideoComposer\n */\n async getRenderState(\n timeUs: TimeUs,\n options?: RenderFrameOptions\n ): Promise<{ layers: any[]; transition?: any } | null> {\n const startModelToken = this.modelToken;\n const startModel = this.compositionModel;\n if (!startModel) {\n return null;\n }\n\n // Ensure frame/resource is ready (this populates L1 if needed)\n const frame = await this.getFrame(timeUs, options);\n\n // Model may switch while awaiting; bail out to avoid mixing old clip/instructions with new model.\n if (this.modelToken !== startModelToken || this.compositionModel !== startModel) {\n return null;\n }\n\n // In probe mode, return null to trigger buffering on the caller side.\n if (!frame) {\n return null;\n }\n\n const clip = startModel.getClipsAtTime(timeUs, startModel.mainTrackId)[0];\n if (!clip) {\n return null;\n }\n\n const clipRelativeTimeUs = timeUs - clip.startUs;\n const trimStartUs = clip.trimStartUs ?? 0;\n let resourceTimeUs = clipRelativeTimeUs + trimStartUs;\n // Align with getFrame()'s clamping to keep time queries inside the clip's valid range.\n if (clip.durationUs > 0) {\n resourceTimeUs = Math.min(resourceTimeUs, trimStartUs + clip.durationUs - 1);\n }\n\n // Get instructions from planner\n const instructions = this.planner.getInstructions(clip.id);\n if (!instructions) {\n return null;\n }\n\n // Build layers array\n const layers: any[] = [];\n\n // 1. Filter active layers at this timestamp (uses clip-relative time for activeRanges)\n const activeLayers = instructions.layers.filter((layer: any) => {\n if (!layer.payload.attachmentId) {\n // Main track layer is always active\n return true;\n }\n if (layer.status !== 'ready') {\n return false;\n }\n // Check if layer is active at current timestamp\n return layer.activeRanges.some(\n (range: any) => clipRelativeTimeUs >= range.startUs && clipRelativeTimeUs < range.endUs\n );\n });\n\n // 2. Materialize layers\n for (const layerPlan of activeLayers) {\n if (layerPlan.type === 'video' && !layerPlan.payload.attachmentId) {\n layers.push({\n id: layerPlan.layerId,\n type: 'video',\n zIndex: layerPlan.zIndex ?? 0,\n visible: true,\n opacity: layerPlan.opacity ?? 1,\n rcFrame: frame,\n });\n continue;\n }\n\n const layer = await this.materializeLayer(layerPlan, clip, resourceTimeUs, timeUs);\n if (layer) {\n layers.push(layer);\n }\n }\n\n return { layers };\n }\n\n /**\n * Materialize a serialized layer plan into concrete Layer\n */\n private async materializeLayer(\n layerPlan: any,\n clip: Clip,\n resourceTimeUs: TimeUs,\n globalTimeUs: TimeUs\n ): Promise<any | null> {\n const baseLayer: any = {\n id: layerPlan.layerId,\n type: layerPlan.type,\n zIndex: layerPlan.zIndex ?? 0,\n visible: true,\n opacity: layerPlan.opacity ?? 1,\n };\n\n // Video layer - fetch raw VideoFrame from L1 (RcFrame wrapper)\n if (layerPlan.type === 'video' && !layerPlan.payload.attachmentId) {\n const rcFrame = this.cacheManager.getFrame(resourceTimeUs, clip.id);\n if (!rcFrame) {\n console.warn('[Orchestrator] Video frame not found in L1:', clip.id, resourceTimeUs);\n return null;\n }\n\n return {\n ...baseLayer,\n type: 'video',\n rcFrame: rcFrame,\n };\n }\n\n // Text layer\n if (layerPlan.type === 'text') {\n const payload = layerPlan.payload;\n return {\n ...baseLayer,\n type: 'text',\n text: payload.text,\n localeCode: payload.localeCode,\n fontConfig: payload.fontConfig,\n animation: payload.animation,\n wordTimings: payload.wordTimings,\n letterCase: payload.letterCase,\n };\n }\n\n // Image layer\n if (layerPlan.type === 'image') {\n const payload = layerPlan.payload;\n const resource = this.compositionModel?.getResource(payload.resourceId);\n if (!resource) {\n return null;\n }\n\n const source = await this.resourceLoader.loadImage(resource);\n const imageLayer: any = {\n ...baseLayer,\n type: 'image',\n source,\n attachmentId: payload.attachmentId,\n };\n\n // Add renderConfig if valid\n const filteredRenderConfig = filterRenderConfig(\n payload.renderConfig,\n `image layer ${payload.attachmentId || layerPlan.layerId}`\n );\n if (filteredRenderConfig) {\n imageLayer.renderConfig = filteredRenderConfig;\n }\n\n // Handle animation (overlays)\n if (payload.animation) {\n const { position, keyframes, overlayClipStartUs } = payload.animation;\n\n // Calculate time relative to overlay clip start\n const relativeTimeUs = globalTimeUs - overlayClipStartUs;\n\n // If outside keyframe range, hide\n if (relativeTimeUs < 0 || relativeTimeUs > keyframes[keyframes.length - 1].time) {\n return null; // Not visible at this time\n }\n\n const rotationRad = 0; // TODO: interpolate from keyframes\n\n imageLayer.transform = {\n x: position.x,\n y: position.y,\n scaleX: 1,\n scaleY: 1,\n rotation: rotationRad,\n anchorX: 0.5,\n anchorY: 0.5,\n };\n }\n\n return imageLayer;\n }\n\n return baseLayer;\n }\n}\n"],"names":["applyModelPatch"],"mappings":"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"}
1
+ {"version":3,"file":"Orchestrator.js","sources":["../../src/orchestrator/Orchestrator.ts"],"sourcesContent":["import { EventBus } from '../event/EventBus';\nimport { WorkerPool } from '../worker/WorkerPool';\nimport { applyPatch as applyModelPatch } from '../model/patch';\nimport { ResourceLoader } from '../stages/load/ResourceLoader';\nimport { CacheManager } from '../cache/CacheManager';\nimport { ConfigLoader } from '../config/ConfigLoader';\nimport type { IOrchestrator, OrchestratorConfig, RenderFrameOptions } from './types';\nimport { WorkerType } from '../worker/types';\nimport { CompositionModel, CompositionPatch, Resource, TimeUs, RcFrame, Clip } from '../model';\nimport { hasResourceId } from '../model/types';\nimport { MeframeEvent, type EventPayloadMap } from '../event/events';\nimport { CompositionPlanner } from './CompositionPlanner';\nimport { AudioWindowPreparer } from './AudioWindowPreparer';\nimport { AudioPreviewSession } from './AudioPreviewSession';\nimport { AudioExportSession } from './AudioExportSession';\nimport { MuxManager } from '../stages/mux/MuxManager';\nimport { ExportOptions } from '../types';\nimport { OnDemandVideoSession } from './OnDemandVideoSession';\nimport { ExportScheduler } from './ExportScheduler';\nimport { filterRenderConfig } from '../utils/object-utils';\n\nexport class Orchestrator implements IOrchestrator {\n workers: WorkerPool;\n eventBus: EventBus<EventPayloadMap>;\n compositionModel: CompositionModel | null = null;\n resourceLoader: ResourceLoader;\n cacheManager: CacheManager;\n planner: CompositionPlanner;\n audio: {\n prepare: AudioWindowPreparer;\n preview: AudioPreviewSession;\n export: AudioExportSession;\n };\n muxManager: MuxManager;\n exportScheduler: ExportScheduler;\n\n private isInitialized = false;\n private config = ConfigLoader.getInstance().getConfig();\n private ensureCacheDebounceTimer: number | null = null;\n private activeOnDemandSession: OnDemandVideoSession | null = null;\n private modelToken = 0;\n readonly events: Pick<EventBus<EventPayloadMap>, 'on' | 'off' | 'once'>;\n\n constructor(config: OrchestratorConfig) {\n // Use provided eventBus or create a new one\n this.eventBus = config.eventBus || new EventBus<EventPayloadMap>();\n this.events = this.eventBus.asReadonly();\n\n // Initialize config first\n this.config = ConfigLoader.getInstance().getConfig();\n\n const workerConfigs = this.buildWorkerConfigs();\n\n // Initialize WorkerPool with worker path from config\n this.workers = new WorkerPool({\n eventBus: this.eventBus,\n workerConfigs,\n workerPath: config.workerPath,\n workerExtension: config.workerExtension,\n });\n\n const maxMemoryMB = config.cacheConfig?.l1Size || this.config.cache?.l1?.maxMemoryMB || 1024;\n const maxGOPs = this.config.decode?.video?.maxGOPs || 4;\n\n this.cacheManager = new CacheManager(\n {\n l1: {\n maxMemoryMB,\n maxGOPs,\n },\n resource: {\n projectId: config.projectId || this.config.global.projectId || 'default',\n },\n },\n this.eventBus\n );\n\n this.resourceLoader = new ResourceLoader({\n cacheManager: this.cacheManager,\n eventBus: this.eventBus,\n config: {\n maxConcurrent: this.config.load.maxConcurrent,\n preloadConcurrency: 2, // Fixed preload concurrency for idle background loading\n },\n onStateChange: (resourceId, state) => this.handleResourceStateChange(resourceId, state),\n });\n\n this.planner = new CompositionPlanner();\n\n const audioPreparer = new AudioWindowPreparer({\n cacheManager: this.cacheManager,\n resourceLoader: this.resourceLoader,\n eventBus: this.eventBus,\n });\n\n const audioPreview = new AudioPreviewSession({\n cacheManager: this.cacheManager,\n preparer: audioPreparer,\n });\n\n const audioExport = new AudioExportSession({\n cacheManager: this.cacheManager,\n preparer: audioPreparer,\n });\n\n this.audio = { prepare: audioPreparer, preview: audioPreview, export: audioExport };\n\n this.muxManager = new MuxManager();\n\n this.exportScheduler = new ExportScheduler({\n workerPool: this.workers,\n planner: this.planner,\n cacheManager: this.cacheManager,\n resourceLoader: this.resourceLoader,\n muxManager: this.muxManager,\n audioSession: this.audio.export,\n workerConfigsProvider: () => this.buildWorkerConfigs(),\n eventBus: this.eventBus,\n });\n\n this.setupResourceFirstFrameHandler();\n }\n\n private setupResourceFirstFrameHandler(): void {\n this.eventBus.on(MeframeEvent.ResourceFirstFrameReady, async (payload) => {\n const { resourceId, clipId, index, chunks } = payload;\n\n if (!this.compositionModel) return;\n\n // Find the specific clip\n const clip = this.compositionModel.findClip(clipId);\n if (!clip || !clip.trackId) return;\n\n // Only decode first frame for clips that start at composition time 0\n // (these clips need the resource's first frame as cover)\n if (clip.startUs === 0) {\n const fps = this.compositionModel.fps ?? 30;\n await OnDemandVideoSession.decodeAndCacheFirstFrame(\n resourceId,\n chunks,\n index,\n clip,\n this.cacheManager,\n fps\n );\n }\n });\n }\n\n async initialize(): Promise<void> {\n if (this.isInitialized) return;\n\n await this.cacheManager.init();\n\n this.isInitialized = true;\n }\n\n // Event methods - forward to eventBus\n on<K extends keyof EventPayloadMap>(\n event: K,\n handler: (payload: EventPayloadMap[K]) => void\n ): void {\n this.eventBus.on(event, handler);\n }\n\n off<K extends keyof EventPayloadMap>(\n event: K,\n handler: (payload: EventPayloadMap[K]) => void\n ): void {\n this.eventBus.off(event, handler);\n }\n\n once<K extends keyof EventPayloadMap>(\n event: K,\n handler: (payload: EventPayloadMap[K]) => void\n ): void {\n this.eventBus.once(event, handler);\n }\n\n cancelActiveDecoding(): void {\n if (this.activeOnDemandSession) {\n void this.activeOnDemandSession.dispose();\n this.activeOnDemandSession = null;\n }\n }\n\n /**\n * Decode and render nearest keyframe for fast seek preview\n * Returns the keyframe timestamp if successfully decoded, null otherwise\n */\n async tryRenderKeyframe(timeUs: TimeUs): Promise<TimeUs | null> {\n if (!this.compositionModel) return null;\n\n const clip = this.compositionModel.getClipsAtTime(timeUs, this.compositionModel.mainTrackId)[0];\n if (!clip || !hasResourceId(clip)) return null;\n\n // Use resource-relative time (same as frame.timestamp from decoder)\n const resourceTimeUs = timeUs - clip.startUs + (clip.trimStartUs ?? 0);\n const resourceId = clip.resourceId;\n\n // Check if keyframe is already in L1 cache\n const keyframeSample = this.cacheManager.mp4IndexCache.getNearestKeyframe(\n resourceId,\n resourceTimeUs\n );\n\n if (!keyframeSample) return null;\n\n const cachedKeyframe = this.cacheManager.getFrame(keyframeSample.timestamp, clip.id);\n if (cachedKeyframe) {\n return keyframeSample.timestamp;\n }\n\n // Resource must be ready to decode keyframe\n const resource = this.compositionModel.getResource(resourceId);\n if (resource?.state !== 'ready') return null;\n\n // Create temporary session to decode keyframe\n const session = await OnDemandVideoSession.create({\n clipId: clip.id,\n resourceId,\n targetTimeUs: resourceTimeUs,\n globalTimeUs: timeUs,\n resourceLoader: this.resourceLoader,\n mp4IndexCache: this.cacheManager.mp4IndexCache,\n cacheManager: this.cacheManager,\n compositionModel: this.compositionModel,\n fps: this.compositionModel.fps ?? 30,\n });\n\n try {\n const keyframeTimeUs = await session.decodeKeyframe(resourceTimeUs);\n return keyframeTimeUs;\n } catch (error) {\n console.warn('[Orchestrator] Fast keyframe decode failed:', error);\n return null;\n } finally {\n await session.dispose();\n }\n }\n\n async setCompositionModel(model: CompositionModel): Promise<void> {\n this.cacheManager.clear();\n this.cancelActiveDecoding();\n this.audio.preview.stopPlayback();\n this.audio.preview.invalidatePreviewMixCache();\n this.modelToken++;\n this.compositionModel = model;\n this.planner.setModel(model);\n\n // IMPORTANT: avoid a window where audio.prepare still references the old model while\n // resourceLoader already switched (resourceLoader.setModel sets this.model before awaiting).\n // This matters when the host app triggers play/seek without awaiting setCompositionModel().\n const resourceLoaderSetModelPromise = this.resourceLoader.setModel(model);\n this.audio.prepare.setModel(model);\n // ensure the cover resource is preloaded before preview starts\n await resourceLoaderSetModelPromise;\n\n this.eventBus.emit(MeframeEvent.ModelSet, model);\n\n this.eventBus.emit(MeframeEvent.CompositionUpdated, {\n trackCount: model.tracks.length,\n clipCount: model.tracks.reduce((acc: number, track: any) => acc + track.clips.length, 0),\n durationUs: model.durationUs,\n });\n }\n\n async applyPatch(patch: CompositionPatch): Promise<void> {\n if (!this.compositionModel) {\n throw new Error('No composition model set');\n }\n\n // Apply patch and get affected clip IDs (simplified for 2-Clip strategy)\n // Note: addTrack/removeTrack already call buildIndexes() in patch.ts\n const affectedClipIds = applyModelPatch(this.compositionModel, patch);\n this.planner.applyPatch(patch, affectedClipIds);\n this.eventBus.emit(MeframeEvent.PatchApplied, {\n operations: patch.operations.length,\n affectedClips: Array.from(affectedClipIds),\n });\n\n // Process clip updates\n for (const clipId of affectedClipIds) {\n this.cacheManager.invalidateClip(clipId);\n }\n\n // Reactivate updated audio clips\n const reactivatedAudioClips: string[] = [];\n const reactivatedVideoClips: string[] = [];\n for (const clipId of affectedClipIds) {\n const clip = this.compositionModel.findClip(clipId);\n if (clip?.trackKind === 'audio') {\n await this.audio.prepare.deactivateClip(clipId);\n reactivatedAudioClips.push(clipId);\n } else if (clip?.trackKind === 'video') {\n reactivatedVideoClips.push(clipId);\n }\n }\n\n // Activate all audio clips (including reactivated ones)\n await this.audio.prepare.activateAllAudioClips();\n\n // Note: No need to restart per-clip playback in new architecture\n // scheduleAudio() uses OfflineAudioMixer which automatically includes all active clips\n }\n\n private handleResourceStateChange(resourceId: string, state: Resource['state']): void {\n if (!this.compositionModel) {\n return;\n }\n\n this.compositionModel.updateResourceState(resourceId, state ?? 'pending');\n\n if (state !== 'ready') {\n return;\n }\n\n // For preview, simple cache invalidation or triggering re-render might be enough\n // if we were caching instructions. But PlaybackController pulls instructions every frame.\n // So just updating Model state is enough.\n }\n\n async getFrame(timeUs: TimeUs, options?: RenderFrameOptions): Promise<RcFrame | null> {\n const signal = options?.signal;\n const preheat = options?.preheat ?? false;\n const mode = options?.mode ?? 'blocking';\n\n if (!this.compositionModel) {\n throw new Error('No composition model set');\n }\n\n const clip = this.compositionModel.getClipsAtTime(timeUs, this.compositionModel.mainTrackId)[0];\n if (!clip) {\n return null;\n }\n\n const trimStartUs = clip.trimStartUs ?? 0;\n\n // Calculate resource-relative time (same as frame.timestamp from decoder)\n // resourceTimeUs = clipRelativeTime + trimStartUs\n let resourceTimeUs = options?.relativeTimeUs\n ? options.relativeTimeUs + trimStartUs\n : timeUs - clip.startUs + trimStartUs;\n\n // Clamp to valid range: [trimStartUs, trimStartUs + durationUs)\n resourceTimeUs = Math.min(resourceTimeUs, trimStartUs + clip.durationUs - 1);\n\n // 1. Check L1 window cache\n // Note: preheat mode skips cache check to force decoding the entire window\n // Why: Video cache is frame-level (point query), not window-level (range query)\n // A single cached frame doesn't guarantee the entire window is cached\n // Without preheat flag, preheating would return early on first frame hit\n if (!preheat) {\n const cachedFrame = this.cacheManager.getFrame(resourceTimeUs, clip.id);\n if (cachedFrame) {\n return cachedFrame;\n }\n }\n\n if (signal?.aborted) {\n return null;\n }\n\n // 2. Try decode from OPFS resource (on-demand path)\n const resourceFrame = await this.decodeFromResource(clip, resourceTimeUs, timeUs, {\n ...options,\n mode,\n });\n return resourceFrame;\n }\n\n /**\n * Compose frame from OPFS resource (on-demand decoding)\n * This is the new path for long clips with window caching\n *\n * @param clip - The clip to decode\n * @param resourceTimeUs - Time in resource-relative coordinates (same as frame.timestamp)\n * @param globalTimeUs - Time in composition timeline\n */\n private async decodeFromResource(\n clip: Clip,\n resourceTimeUs: TimeUs,\n globalTimeUs: TimeUs,\n options?: RenderFrameOptions\n ): Promise<RcFrame | null> {\n if (!hasResourceId(clip)) return null;\n\n const resourceId = clip.resourceId;\n const trimStartUs = clip.trimStartUs ?? 0;\n const startModelToken = this.modelToken;\n const startModel = this.compositionModel;\n\n // Check resource state\n const resource = this.compositionModel?.getResource(resourceId);\n const isReady = resource?.state === 'ready';\n\n const fetchOptions = {\n isPreload: false,\n clipId: clip.id,\n trackId: clip.trackId,\n };\n\n const mode = options?.mode ?? 'blocking';\n\n // In probe mode, if not ready, trigger fetch in background and return null.\n // Note: If resource is already ready, probe mode behaves the same as blocking mode\n // (it may still decode from OPFS). The only guaranteed non-blocking part is resource loading.\n if (mode === 'probe' && !isReady) {\n void this.resourceLoader.load(resourceId, fetchOptions);\n return null;\n }\n\n // Normal mode: wait for download\n await this.resourceLoader.load(resourceId, fetchOptions);\n\n if (this.modelToken !== startModelToken || this.compositionModel !== startModel) {\n console.warn('[Orchestrator] Model switched during decodeFromResource:', {\n startModelToken,\n currentModelToken: this.modelToken,\n clipId: clip.id,\n resourceId,\n globalTimeUs,\n resourceTimeUs,\n mode,\n });\n return null;\n }\n\n this.cancelActiveDecoding();\n\n // Create temporary on-demand video session\n const session = await OnDemandVideoSession.create({\n clipId: clip.id,\n resourceId,\n targetTimeUs: resourceTimeUs,\n globalTimeUs,\n resourceLoader: this.resourceLoader,\n mp4IndexCache: this.cacheManager.mp4IndexCache,\n cacheManager: this.cacheManager,\n compositionModel: this.compositionModel!,\n fps: this.compositionModel?.fps ?? 30,\n });\n\n this.activeOnDemandSession = session;\n\n try {\n // Decode window: from target position to target + 3s\n // Window bounds are in resource time: [trimStartUs, trimStartUs + durationUs)\n const DECODE_WINDOW_SIZE = 3_000_000;\n\n const windowStart = resourceTimeUs;\n const windowEnd = Math.min(\n trimStartUs + clip.durationUs,\n resourceTimeUs + DECODE_WINDOW_SIZE\n );\n\n await session.decodeWindow(windowStart, windowEnd);\n // Return target frame from L1 cache\n return this.cacheManager.getFrame(resourceTimeUs, clip.id);\n } catch (error) {\n if (session.isDisposed) {\n return null;\n }\n console.error('[Orchestrator] Error composing from resource:', error);\n return null;\n } finally {\n if (this.activeOnDemandSession === session) {\n this.activeOnDemandSession = null;\n }\n await session.dispose();\n }\n }\n\n /**\n * Wait for clip cache to be ready for playback\n * Returns true if minimum cache is ready, false if timeout\n */\n async waitForClipReady(\n timeUs: TimeUs,\n options?: { minFrameCount?: number; timeoutMs?: number }\n ): Promise<boolean> {\n if (!this.compositionModel) {\n return false;\n }\n\n const clips = this.compositionModel.getClipsAtTime(timeUs, this.compositionModel.mainTrackId);\n if (clips.length === 0) {\n return true;\n }\n\n const currentClip = clips[0];\n if (!currentClip) {\n return true;\n }\n\n return this.cacheManager.waitForClipReady(currentClip.id, {\n minFrameCount: options?.minFrameCount ?? 5,\n timeoutMs: options?.timeoutMs ?? 5_000,\n });\n }\n\n async dispose(): Promise<void> {\n if (this.ensureCacheDebounceTimer !== null) {\n clearTimeout(this.ensureCacheDebounceTimer);\n this.ensureCacheDebounceTimer = null;\n }\n\n this.resourceLoader.dispose();\n await this.cacheManager.clear();\n\n this.workers.terminateAll();\n this.compositionModel = null;\n this.eventBus.dispose();\n }\n\n private buildWorkerConfigs(): Record<WorkerType, any> {\n const config = this.config;\n const defaultCanvasWidth = config.global.defaultCanvasWidth;\n const defaultCanvasHeight = config.global.defaultCanvasHeight;\n const defaultFps = config.global.defaultFps;\n\n const targetFps = this.compositionModel?.fps ?? defaultFps;\n\n return {\n // videoDemux: { // DEPRECATED: Removed - replaced by IndexedVideoSource\n // highWaterMark: config.demux.backpressure.highWaterMark,\n // },\n audioDemux: {\n highWaterMark: config.demux.backpressure.highWaterMark,\n },\n // videoDecode: config.decode.video, // DEPRECATED: Removed - replaced by OnDemandVideoSession\n audioDecode: config.decode.audio,\n videoCompose: {\n width: defaultCanvasWidth,\n height: defaultCanvasHeight,\n fps: targetFps,\n backgroundColor: '#000000',\n enableSmoothing: true,\n enableHardwareAcceleration: true,\n fonts: config.global.fonts,\n },\n audioCompose: {\n enableDucking: config.compose.audio.enableDucking,\n },\n videoEncode: {\n // Main Profile Level 4.1 - better compression efficiency for social media\n codec: 'avc1.4D0029',\n width: defaultCanvasWidth,\n height: defaultCanvasHeight,\n bitrate: config.encode.video.bitrateKbps\n ? config.encode.video.bitrateKbps * 1000\n : this.calculateDefaultBitrate(defaultCanvasWidth, defaultCanvasHeight),\n framerate: targetFps,\n latencyMode: 'quality',\n bitrateMode: 'variable',\n hardwareAcceleration: 'no-preference',\n // Default 1 second keyframe interval for better social media compatibility\n keyFrameInterval: config.encode.video.keyIntervalS\n ? Math.round(config.encode.video.keyIntervalS * targetFps)\n : targetFps,\n ...config.encode.video,\n },\n };\n }\n\n /**\n * Calculate default video bitrate based on resolution\n * Optimized for social media platforms (YouTube, TikTok, WeChat, etc.)\n */\n private calculateDefaultBitrate(width: number, height: number): number {\n const pixels = width * height;\n\n // Bitrate recommendations for H.264 Main Profile VBR:\n // - 720p (921,600 px): 5 Mbps\n // - 1080p (2,073,600 px): 8 Mbps\n // - 4K (8,294,400 px): 25 Mbps\n if (pixels <= 921_600) {\n // 720p and below\n return 5_000_000;\n } else if (pixels <= 2_073_600) {\n // 1080p\n return 8_000_000;\n } else if (pixels <= 3_686_400) {\n // 1440p (2K)\n return 16_000_000;\n } else {\n // 4K and above\n return 25_000_000;\n }\n }\n\n async export(model: CompositionModel, options: ExportOptions): Promise<Blob | null> {\n return this.exportScheduler.execute(model, options);\n }\n\n /**\n * Preheat a specific clip's window range\n * Used by PlaybackController for cross-clip window preheating\n *\n * @param clipId - Clip identifier\n * @param clipRelativeStart - Start time relative to clip (microseconds), 0 = clip start\n * @param clipRelativeEnd - End time relative to clip (microseconds)\n * @param globalTimeUs - Global timeline position (for globalTimeUs in cache)\n */\n async preheatClipWindow(\n clipId: string,\n clipRelativeStart: TimeUs,\n clipRelativeEnd: TimeUs,\n globalTimeUs: TimeUs\n ): Promise<void> {\n if (!this.compositionModel) return;\n\n const clip = this.compositionModel.findClip(clipId);\n if (!clip || !hasResourceId(clip)) return;\n\n const resourceId = clip.resourceId;\n const trimStartUs = clip.trimStartUs ?? 0;\n\n // Convert clip-relative time to resource time\n const resourceStart = clipRelativeStart + trimStartUs;\n const resourceEnd = clipRelativeEnd + trimStartUs;\n\n // Ensure resource is downloaded\n await this.resourceLoader.load(resourceId, {\n isPreload: false,\n clipId: clip.id,\n trackId: clip.trackId,\n });\n\n // Create temporary on-demand session for this window\n const session = await OnDemandVideoSession.create({\n clipId: clip.id,\n resourceId,\n targetTimeUs: resourceStart,\n globalTimeUs,\n resourceLoader: this.resourceLoader,\n mp4IndexCache: this.cacheManager.mp4IndexCache,\n cacheManager: this.cacheManager,\n compositionModel: this.compositionModel,\n fps: this.compositionModel.fps ?? 30,\n });\n\n try {\n // Decode the entire window range for this clip (using resource time)\n await session.decodeWindow(resourceStart, resourceEnd);\n } catch (error) {\n console.warn(`[Orchestrator] Preheat clip ${clipId} window failed:`, error);\n // Non-critical, don't throw\n } finally {\n await session.dispose();\n }\n }\n\n /**\n * Get render state for real-time composition\n * Returns layers ready for VideoComposer\n */\n async getRenderState(\n timeUs: TimeUs,\n options?: RenderFrameOptions\n ): Promise<{ layers: any[]; transition?: any } | null> {\n const startModelToken = this.modelToken;\n const startModel = this.compositionModel;\n if (!startModel) {\n return null;\n }\n\n // Ensure frame/resource is ready (this populates L1 if needed)\n const frame = await this.getFrame(timeUs, options);\n\n // Model may switch while awaiting; bail out to avoid mixing old clip/instructions with new model.\n if (this.modelToken !== startModelToken || this.compositionModel !== startModel) {\n return null;\n }\n\n // In probe mode, return null to trigger buffering on the caller side.\n if (!frame) {\n return null;\n }\n\n const clip = startModel.getClipsAtTime(timeUs, startModel.mainTrackId)[0];\n if (!clip) {\n return null;\n }\n\n const clipRelativeTimeUs = timeUs - clip.startUs;\n const trimStartUs = clip.trimStartUs ?? 0;\n let resourceTimeUs = clipRelativeTimeUs + trimStartUs;\n // Align with getFrame()'s clamping to keep time queries inside the clip's valid range.\n if (clip.durationUs > 0) {\n resourceTimeUs = Math.min(resourceTimeUs, trimStartUs + clip.durationUs - 1);\n }\n\n // Get instructions from planner\n const instructions = this.planner.getInstructions(clip.id);\n if (!instructions) {\n return null;\n }\n\n // Build layers array\n const layers: any[] = [];\n\n // 1. Filter active layers at this timestamp (uses clip-relative time for activeRanges)\n const activeLayers = instructions.layers.filter((layer: any) => {\n if (!layer.payload.attachmentId) {\n // Main track layer is always active\n return true;\n }\n if (layer.status !== 'ready') {\n return false;\n }\n // Check if layer is active at current timestamp\n return layer.activeRanges.some(\n (range: any) => clipRelativeTimeUs >= range.startUs && clipRelativeTimeUs < range.endUs\n );\n });\n\n // 2. Materialize layers\n for (const layerPlan of activeLayers) {\n if (layerPlan.type === 'video' && !layerPlan.payload.attachmentId) {\n layers.push({\n id: layerPlan.layerId,\n type: 'video',\n zIndex: layerPlan.zIndex ?? 0,\n visible: true,\n opacity: layerPlan.opacity ?? 1,\n rcFrame: frame,\n });\n continue;\n }\n\n const layer = await this.materializeLayer(layerPlan, clip, resourceTimeUs, timeUs);\n if (layer) {\n layers.push(layer);\n }\n }\n\n return { layers };\n }\n\n /**\n * Materialize a serialized layer plan into concrete Layer\n */\n private async materializeLayer(\n layerPlan: any,\n clip: Clip,\n resourceTimeUs: TimeUs,\n globalTimeUs: TimeUs\n ): Promise<any | null> {\n const baseLayer: any = {\n id: layerPlan.layerId,\n type: layerPlan.type,\n zIndex: layerPlan.zIndex ?? 0,\n visible: true,\n opacity: layerPlan.opacity ?? 1,\n };\n\n // Video layer - fetch raw VideoFrame from L1 (RcFrame wrapper)\n if (layerPlan.type === 'video' && !layerPlan.payload.attachmentId) {\n const rcFrame = this.cacheManager.getFrame(resourceTimeUs, clip.id);\n if (!rcFrame) {\n console.warn('[Orchestrator] Video frame not found in L1:', clip.id, resourceTimeUs);\n return null;\n }\n\n return {\n ...baseLayer,\n type: 'video',\n rcFrame: rcFrame,\n };\n }\n\n // Text layer\n if (layerPlan.type === 'text') {\n const payload = layerPlan.payload;\n return {\n ...baseLayer,\n type: 'text',\n text: payload.text,\n localeCode: payload.localeCode,\n fontConfig: payload.fontConfig,\n animation: payload.animation,\n wordTimings: payload.wordTimings,\n letterCase: payload.letterCase,\n };\n }\n\n // Image layer\n if (layerPlan.type === 'image') {\n const payload = layerPlan.payload;\n const resource = this.compositionModel?.getResource(payload.resourceId);\n if (!resource) {\n return null;\n }\n\n const source = await this.resourceLoader.loadImage(resource);\n const imageLayer: any = {\n ...baseLayer,\n type: 'image',\n source,\n attachmentId: payload.attachmentId,\n };\n\n // Add renderConfig if valid\n const filteredRenderConfig = filterRenderConfig(\n payload.renderConfig,\n `image layer ${payload.attachmentId || layerPlan.layerId}`\n );\n if (filteredRenderConfig) {\n imageLayer.renderConfig = filteredRenderConfig;\n }\n\n // Handle animation (overlays)\n if (payload.animation) {\n const { position, keyframes, overlayClipStartUs } = payload.animation;\n\n // Calculate time relative to overlay clip start\n const relativeTimeUs = globalTimeUs - overlayClipStartUs;\n\n // If outside keyframe range, hide\n if (relativeTimeUs < 0 || relativeTimeUs > keyframes[keyframes.length - 1].time) {\n return null; // Not visible at this time\n }\n\n const rotationRad = 0; // TODO: interpolate from keyframes\n\n imageLayer.transform = {\n x: position.x,\n y: position.y,\n scaleX: 1,\n scaleY: 1,\n rotation: rotationRad,\n anchorX: 0.5,\n anchorY: 0.5,\n };\n }\n\n return imageLayer;\n }\n\n return baseLayer;\n }\n}\n"],"names":["applyModelPatch"],"mappings":"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"}
@@ -46,7 +46,7 @@ export interface IOrchestrator {
46
46
  applyPatch(patch: CompositionPatch): Promise<void>;
47
47
  getFrame(timeUs: TimeUs, options?: RenderFrameOptions): Promise<RcFrame | null>;
48
48
  tryRenderKeyframe(timeUs: TimeUs): Promise<TimeUs | null>;
49
- export(model: CompositionModel, options: any): Promise<Blob>;
49
+ export(model: CompositionModel, options: any): Promise<Blob | null>;
50
50
  cancelActiveDecoding(): void;
51
51
  dispose(): Promise<void>;
52
52
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/orchestrator/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/orchestrator/types.ts"],"names":[],"mappings":"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"}
@@ -17,8 +17,8 @@ import { TimeUs } from '../../model/types';
17
17
  * - Remaining frame timestamps are adjusted: adjustedTimestamp = timestamp - trimStartUs
18
18
  *
19
19
  * Duration Handling:
20
- * - Output stops when source frames are exhausted (no frame padding)
21
- * - If source is shorter than clipDurationUs, output ends early
20
+ * - Output is paced by source timestamps: never emit target frames ahead of the latest observed source time
21
+ * - If source ends early, fill the tail to clipDurationUs by repeating the last frame (stable duration)
22
22
  *
23
23
  * Scenarios:
24
24
  * - Downsampling (60fps→30fps): Skip every other frame
@@ -29,9 +29,11 @@ export declare class FrameRateConverter {
29
29
  private readonly clipDurationUs;
30
30
  private readonly frameDurationUs;
31
31
  private readonly trimStartUs;
32
+ private readonly totalFrameCount;
32
33
  private targetFrameIndex;
33
34
  private targetFrameTimeUs;
34
35
  private sourceFrameBuffer;
36
+ private maxSourceTimestampUs;
35
37
  constructor(targetFps: number, clipDurationUs: TimeUs, trimStartUs?: TimeUs);
36
38
  /**
37
39
  * Create a TransformStream that converts VFR frames to CFR frames
@@ -51,6 +53,8 @@ export declare class FrameRateConverter {
51
53
  * Output a single target frame
52
54
  */
53
55
  private outputTargetFrame;
56
+ private getPadFrame;
57
+ private shouldContinueOutput;
54
58
  /**
55
59
  * Clean up all buffered frames (except the specified frame to keep)
56
60
  */
@@ -1 +1 @@
1
- {"version":3,"file":"FrameRateConverter.d.ts","sourceRoot":"","sources":["../../../src/stages/compose/FrameRateConverter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IAGrC,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,iBAAiB,CAAa;IACtC,OAAO,CAAC,iBAAiB,CAAoB;gBAEjC,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,WAAW,GAAE,MAAU;IAa9E;;OAEG;IACH,YAAY,IAAI,eAAe,CAAC,UAAU,EAAE,UAAU,CAAC;IAqBvD,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,gBAAgB,CAAK;IAE7B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAuE1B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAa5B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAoBzB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAaxB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAmBxB;;;;;;OAMG;IACH,OAAO,CAAC,sBAAsB;IAa9B;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAyCxB;;OAEG;IACH,QAAQ,IAAI;QACV,gBAAgB,EAAE,MAAM,CAAC;QACzB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,UAAU,EAAE,MAAM,CAAC;QACnB,eAAe,EAAE,MAAM,CAAC;KACzB;CAQF"}
1
+ {"version":3,"file":"FrameRateConverter.d.ts","sourceRoot":"","sources":["../../../src/stages/compose/FrameRateConverter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAgB;IAGhD,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,iBAAiB,CAAa;IACtC,OAAO,CAAC,iBAAiB,CAAoB;IAC7C,OAAO,CAAC,oBAAoB,CAAuB;gBAEvC,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,WAAW,GAAE,MAAU;IAgB9E;;OAEG;IACH,YAAY,IAAI,eAAe,CAAC,UAAU,EAAE,UAAU,CAAC;IAsBvD,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,gBAAgB,CAAK;IAE7B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAsF1B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAyC5B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAoBzB,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,oBAAoB;IAO5B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAaxB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAmBxB;;;;;;OAMG;IACH,OAAO,CAAC,sBAAsB;IAa9B;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAyCxB;;OAEG;IACH,QAAQ,IAAI;QACV,gBAAgB,EAAE,MAAM,CAAC;QACzB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,UAAU,EAAE,MAAM,CAAC;QACnB,eAAe,EAAE,MAAM,CAAC;KACzB;CAQF"}
@@ -1 +1 @@
1
- {"version":3,"file":"OfflineAudioMixer.d.ts","sourceRoot":"","sources":["../../../src/stages/compose/OfflineAudioMixer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAS7D,qBAAa,iBAAiB;IAK1B,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,QAAQ;IALlB,OAAO,CAAC,UAAU,CAAU;IAC5B,OAAO,CAAC,gBAAgB,CAAK;gBAGnB,YAAY,EAAE,YAAY,EAC1B,QAAQ,EAAE,MAAM,gBAAgB,GAAG,IAAI;IAG3C,GAAG,CAAC,aAAa,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IA6E3E,OAAO,CAAC,gBAAgB;CA2CzB"}
1
+ {"version":3,"file":"OfflineAudioMixer.d.ts","sourceRoot":"","sources":["../../../src/stages/compose/OfflineAudioMixer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAS7D,qBAAa,iBAAiB;IAK1B,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,QAAQ;IALlB,OAAO,CAAC,UAAU,CAAU;IAC5B,OAAO,CAAC,gBAAgB,CAAK;gBAGnB,YAAY,EAAE,YAAY,EAC1B,QAAQ,EAAE,MAAM,gBAAgB,GAAG,IAAI;IAG3C,GAAG,CAAC,aAAa,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAyF3E,OAAO,CAAC,gBAAgB;CA2CzB"}
@@ -13,6 +13,14 @@ class OfflineAudioMixer {
13
13
  Math.ceil(Math.max(0, durationUs) / 1e6 * this.sampleRate)
14
14
  );
15
15
  const ctx = new OfflineAudioContext(this.numberOfChannels, frameCount, this.sampleRate);
16
+ const silent = ctx.createBuffer(1, frameCount, this.sampleRate);
17
+ const silentSource = ctx.createBufferSource();
18
+ silentSource.buffer = silent;
19
+ const silentGain = ctx.createGain();
20
+ silentGain.gain.value = 0;
21
+ silentSource.connect(silentGain);
22
+ silentGain.connect(ctx.destination);
23
+ silentSource.start(0);
16
24
  const clips = this.getClipsInWindow(windowStartUs, windowEndUs);
17
25
  for (const clip of clips) {
18
26
  const clipIntersectStartUs = Math.max(windowStartUs, clip.startUs);
@@ -1 +1 @@
1
- {"version":3,"file":"OfflineAudioMixer.js","sources":["../../../src/stages/compose/OfflineAudioMixer.ts"],"sourcesContent":["import type { TimeUs } from '../../model/types';\nimport { hasAudioConfig } from '../../model/types';\nimport type { CompositionModel } from '../../model';\nimport type { CacheManager } from '../../cache/CacheManager';\n\ninterface MixClipInfo {\n clipId: string;\n startUs: TimeUs;\n durationUs: TimeUs;\n volume: number;\n}\n\nexport class OfflineAudioMixer {\n private sampleRate = 48_000;\n private numberOfChannels = 2;\n\n constructor(\n private cacheManager: CacheManager,\n private getModel: () => CompositionModel | null\n ) {}\n\n async mix(windowStartUs: TimeUs, windowEndUs: TimeUs): Promise<AudioBuffer> {\n const durationUs = windowEndUs - windowStartUs;\n // Guard against invalid/empty ranges (can happen near timeline end or after clamping).\n // OfflineAudioContext requires length >= 1.\n const frameCount = Math.max(\n 1,\n Math.ceil((Math.max(0, durationUs) / 1_000_000) * this.sampleRate)\n );\n\n const ctx = new OfflineAudioContext(this.numberOfChannels, frameCount, this.sampleRate);\n\n const clips = this.getClipsInWindow(windowStartUs, windowEndUs);\n\n for (const clip of clips) {\n // Calculate clip-relative time range\n const clipIntersectStartUs = Math.max(windowStartUs, clip.startUs);\n const clipIntersectEndUs = Math.min(windowEndUs, clip.startUs + clip.durationUs);\n const clipRelativeStartUs = clipIntersectStartUs - clip.startUs;\n const clipRelativeEndUs = clipIntersectEndUs - clip.startUs;\n\n // Convert to resource time (aligned with video architecture)\n const clipModel = this.getModel()?.findClip(clip.clipId);\n const trimStartUs = clipModel?.trimStartUs ?? 0;\n const resourceStartUs = clipRelativeStartUs + trimStartUs;\n const resourceEndUs = clipRelativeEndUs + trimStartUs;\n\n // Get PCM data using resource time coordinates\n const pcmData = this.cacheManager.getClipPCMWithMetadata(\n clip.clipId,\n resourceStartUs,\n resourceEndUs\n );\n\n if (!pcmData || pcmData.planes.length === 0) {\n // console.warn(\n // `[OfflineAudioMixer] No PCM data for clip ${clip.clipId} at ${(clipRelativeStartUs / 1000).toFixed(1)}-${(clipRelativeEndUs / 1000).toFixed(1)}ms`\n // );\n continue;\n }\n\n const intersectFrames = pcmData.planes[0]?.length ?? 0;\n if (intersectFrames === 0) {\n // console.warn(\n // `[OfflineAudioMixer] Empty PCM data for clip ${clip.clipId} at ${(clipRelativeStartUs / 1000).toFixed(1)}-${(clipRelativeEndUs / 1000).toFixed(1)}ms`\n // );\n continue;\n }\n\n // Create AudioBuffer\n const buffer = ctx.createBuffer(pcmData.planes.length, intersectFrames, pcmData.sampleRate);\n\n for (let channel = 0; channel < pcmData.planes.length; channel++) {\n const plane = pcmData.planes[channel];\n if (plane) {\n // Create new Float32Array to ensure correct type (ArrayBuffer, not SharedArrayBuffer)\n buffer.copyToChannel(new Float32Array(plane), channel);\n }\n }\n\n const source = ctx.createBufferSource();\n source.buffer = buffer;\n\n const gainNode = ctx.createGain();\n gainNode.gain.value = clip.volume;\n\n source.connect(gainNode);\n gainNode.connect(ctx.destination);\n\n const relativeStartUs = clipIntersectStartUs - windowStartUs;\n const startTime = relativeStartUs / 1_000_000;\n source.start(startTime);\n }\n\n const mixedBuffer = await ctx.startRendering();\n return mixedBuffer;\n }\n\n private getClipsInWindow(windowStartUs: TimeUs, windowEndUs: TimeUs): MixClipInfo[] {\n const clips: MixClipInfo[] = [];\n const model = this.getModel();\n if (!model) {\n return clips;\n }\n\n for (const track of model.tracks) {\n for (const clip of track.clips) {\n const clipEndUs = clip.startUs + clip.durationUs;\n if (clip.startUs < windowEndUs && clipEndUs > windowStartUs) {\n // Read audio config (only video/audio clips have audioConfig)\n if (hasAudioConfig(clip)) {\n const muted = clip.audioConfig?.muted ?? false;\n\n // Skip muted clips in export (performance optimization)\n if (muted) {\n continue;\n }\n\n const volume = clip.audioConfig?.volume ?? 1.0;\n\n clips.push({\n clipId: clip.id,\n startUs: clip.startUs,\n durationUs: clip.durationUs,\n volume,\n });\n } else {\n // Caption/Fx clips in audio track should not happen, but handle gracefully\n clips.push({\n clipId: clip.id,\n startUs: clip.startUs,\n durationUs: clip.durationUs,\n volume: 1.0,\n });\n }\n }\n }\n }\n\n return clips;\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"OfflineAudioMixer.js","sources":["../../../src/stages/compose/OfflineAudioMixer.ts"],"sourcesContent":["import type { TimeUs } from '../../model/types';\nimport { hasAudioConfig } from '../../model/types';\nimport type { CompositionModel } from '../../model';\nimport type { CacheManager } from '../../cache/CacheManager';\n\ninterface MixClipInfo {\n clipId: string;\n startUs: TimeUs;\n durationUs: TimeUs;\n volume: number;\n}\n\nexport class OfflineAudioMixer {\n private sampleRate = 48_000;\n private numberOfChannels = 2;\n\n constructor(\n private cacheManager: CacheManager,\n private getModel: () => CompositionModel | null\n ) {}\n\n async mix(windowStartUs: TimeUs, windowEndUs: TimeUs): Promise<AudioBuffer> {\n const durationUs = windowEndUs - windowStartUs;\n // Guard against invalid/empty ranges (can happen near timeline end or after clamping).\n // OfflineAudioContext requires length >= 1.\n const frameCount = Math.max(\n 1,\n Math.ceil((Math.max(0, durationUs) / 1_000_000) * this.sampleRate)\n );\n\n const ctx = new OfflineAudioContext(this.numberOfChannels, frameCount, this.sampleRate);\n\n // Ensure the OfflineAudioContext renders the full requested length.\n // Some implementations may stop early if no sources are scheduled near the tail,\n // which would truncate trailing silence and make export audio shorter than video.\n const silent = ctx.createBuffer(1, frameCount, this.sampleRate);\n const silentSource = ctx.createBufferSource();\n silentSource.buffer = silent;\n const silentGain = ctx.createGain();\n silentGain.gain.value = 0;\n silentSource.connect(silentGain);\n silentGain.connect(ctx.destination);\n silentSource.start(0);\n\n const clips = this.getClipsInWindow(windowStartUs, windowEndUs);\n\n for (const clip of clips) {\n // Calculate clip-relative time range\n const clipIntersectStartUs = Math.max(windowStartUs, clip.startUs);\n const clipIntersectEndUs = Math.min(windowEndUs, clip.startUs + clip.durationUs);\n const clipRelativeStartUs = clipIntersectStartUs - clip.startUs;\n const clipRelativeEndUs = clipIntersectEndUs - clip.startUs;\n\n // Convert to resource time (aligned with video architecture)\n const clipModel = this.getModel()?.findClip(clip.clipId);\n const trimStartUs = clipModel?.trimStartUs ?? 0;\n const resourceStartUs = clipRelativeStartUs + trimStartUs;\n const resourceEndUs = clipRelativeEndUs + trimStartUs;\n\n // Get PCM data using resource time coordinates\n const pcmData = this.cacheManager.getClipPCMWithMetadata(\n clip.clipId,\n resourceStartUs,\n resourceEndUs\n );\n\n if (!pcmData || pcmData.planes.length === 0) {\n // console.warn(\n // `[OfflineAudioMixer] No PCM data for clip ${clip.clipId} at ${(clipRelativeStartUs / 1000).toFixed(1)}-${(clipRelativeEndUs / 1000).toFixed(1)}ms`\n // );\n continue;\n }\n\n const intersectFrames = pcmData.planes[0]?.length ?? 0;\n if (intersectFrames === 0) {\n // console.warn(\n // `[OfflineAudioMixer] Empty PCM data for clip ${clip.clipId} at ${(clipRelativeStartUs / 1000).toFixed(1)}-${(clipRelativeEndUs / 1000).toFixed(1)}ms`\n // );\n continue;\n }\n\n // Create AudioBuffer\n const buffer = ctx.createBuffer(pcmData.planes.length, intersectFrames, pcmData.sampleRate);\n\n for (let channel = 0; channel < pcmData.planes.length; channel++) {\n const plane = pcmData.planes[channel];\n if (plane) {\n // Create new Float32Array to ensure correct type (ArrayBuffer, not SharedArrayBuffer)\n buffer.copyToChannel(new Float32Array(plane), channel);\n }\n }\n\n const source = ctx.createBufferSource();\n source.buffer = buffer;\n\n const gainNode = ctx.createGain();\n gainNode.gain.value = clip.volume;\n\n source.connect(gainNode);\n gainNode.connect(ctx.destination);\n\n const relativeStartUs = clipIntersectStartUs - windowStartUs;\n const startTime = relativeStartUs / 1_000_000;\n source.start(startTime);\n }\n\n const mixedBuffer = await ctx.startRendering();\n return mixedBuffer;\n }\n\n private getClipsInWindow(windowStartUs: TimeUs, windowEndUs: TimeUs): MixClipInfo[] {\n const clips: MixClipInfo[] = [];\n const model = this.getModel();\n if (!model) {\n return clips;\n }\n\n for (const track of model.tracks) {\n for (const clip of track.clips) {\n const clipEndUs = clip.startUs + clip.durationUs;\n if (clip.startUs < windowEndUs && clipEndUs > windowStartUs) {\n // Read audio config (only video/audio clips have audioConfig)\n if (hasAudioConfig(clip)) {\n const muted = clip.audioConfig?.muted ?? false;\n\n // Skip muted clips in export (performance optimization)\n if (muted) {\n continue;\n }\n\n const volume = clip.audioConfig?.volume ?? 1.0;\n\n clips.push({\n clipId: clip.id,\n startUs: clip.startUs,\n durationUs: clip.durationUs,\n volume,\n });\n } else {\n // Caption/Fx clips in audio track should not happen, but handle gracefully\n clips.push({\n clipId: clip.id,\n startUs: clip.startUs,\n durationUs: clip.durationUs,\n volume: 1.0,\n });\n }\n }\n }\n }\n\n return clips;\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1,3 +1,11 @@
1
+ export type MuxOutputConfig = {
2
+ kind: 'blob';
3
+ } | {
4
+ kind: 'stream';
5
+ onData: (data: Uint8Array, position: number) => void;
6
+ chunked?: boolean;
7
+ chunkSize?: number;
8
+ };
1
9
  /**
2
10
  * MP4Muxer - MP4 container multiplexer using mp4-muxer library
3
11
  * Supports video and audio track export
@@ -8,6 +16,7 @@ export declare class MP4Muxer {
8
16
  private firstAudioChunk;
9
17
  private videoChunkMeta;
10
18
  private audioChunkMeta;
19
+ private lastAudioDurationUs;
11
20
  constructor(config: {
12
21
  width: number;
13
22
  height: number;
@@ -15,11 +24,11 @@ export declare class MP4Muxer {
15
24
  fastStart?: false | 'in-memory' | 'fragmented';
16
25
  videoChunkMeta?: any;
17
26
  audioChunkMeta?: any;
18
- });
27
+ }, output?: MuxOutputConfig);
19
28
  private videoChunkCount;
20
29
  writeVideoChunk(chunk: EncodedVideoChunk, metadata?: EncodedVideoChunkMetadata): void;
21
30
  private audioChunkCount;
22
31
  writeAudioChunk(chunk: EncodedAudioChunk, metadata?: EncodedAudioChunkMetadata): void;
23
- finalize(): Blob;
32
+ finalize(): Blob | null;
24
33
  }
25
34
  //# sourceMappingURL=MP4Muxer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"MP4Muxer.d.ts","sourceRoot":"","sources":["../../../src/stages/mux/MP4Muxer.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,KAAK,CAA2B;IACxC,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,cAAc,CAAa;gBAEvB,MAAM,EAAE;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;QACZ,SAAS,CAAC,EAAE,KAAK,GAAG,WAAW,GAAG,YAAY,CAAC;QAC/C,cAAc,CAAC,EAAE,GAAG,CAAC;QACrB,cAAc,CAAC,EAAE,GAAG,CAAC;KACtB;IA2CD,OAAO,CAAC,eAAe,CAAK;IAE5B,eAAe,CAAC,KAAK,EAAE,iBAAiB,EAAE,QAAQ,CAAC,EAAE,yBAAyB,GAAG,IAAI;IAwBrF,OAAO,CAAC,eAAe,CAAK;IAE5B,eAAe,CAAC,KAAK,EAAE,iBAAiB,EAAE,QAAQ,CAAC,EAAE,yBAAyB,GAAG,IAAI;IA0BrF,QAAQ,IAAI,IAAI;CAKjB"}
1
+ {"version":3,"file":"MP4Muxer.d.ts","sourceRoot":"","sources":["../../../src/stages/mux/MP4Muxer.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACrD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEN;;;GAGG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,KAAK,CAA0C;IACvD,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,mBAAmB,CAAuB;gBAGhD,MAAM,EAAE;QACN,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;QACZ,SAAS,CAAC,EAAE,KAAK,GAAG,WAAW,GAAG,YAAY,CAAC;QAC/C,cAAc,CAAC,EAAE,GAAG,CAAC;QACrB,cAAc,CAAC,EAAE,GAAG,CAAC;KACtB,EACD,MAAM,GAAE,eAAkC;IAqD5C,OAAO,CAAC,eAAe,CAAK;IAE5B,eAAe,CAAC,KAAK,EAAE,iBAAiB,EAAE,QAAQ,CAAC,EAAE,yBAAyB,GAAG,IAAI;IAwBrF,OAAO,CAAC,eAAe,CAAK;IAE5B,eAAe,CAAC,KAAK,EAAE,iBAAiB,EAAE,QAAQ,CAAC,EAAE,yBAAyB,GAAG,IAAI;IAmDrF,QAAQ,IAAI,IAAI,GAAG,IAAI;CASxB"}
@@ -1,15 +1,22 @@
1
- import { ArrayBufferTarget, Muxer } from "../../medeo-fe/node_modules/.pnpm/mp4-muxer@5.2.2/node_modules/mp4-muxer/build/mp4-muxer.js";
1
+ import { StreamTarget, ArrayBufferTarget, Muxer } from "../../node_modules/.pnpm/mp4-muxer@5.2.2/node_modules/mp4-muxer/build/mp4-muxer.js";
2
2
  class MP4Muxer {
3
3
  muxer;
4
4
  firstVideoChunk = true;
5
5
  firstAudioChunk = true;
6
6
  videoChunkMeta = null;
7
7
  audioChunkMeta = null;
8
- constructor(config) {
8
+ lastAudioDurationUs = null;
9
+ constructor(config, output = { kind: "blob" }) {
9
10
  this.videoChunkMeta = config.videoChunkMeta;
10
11
  this.audioChunkMeta = config.audioChunkMeta;
12
+ const target = output.kind === "stream" ? new StreamTarget({
13
+ onData: output.onData,
14
+ chunked: output.chunked ?? true,
15
+ chunkSize: output.chunkSize ?? 16 * 1024 * 1024
16
+ // 16 MiB default to reduce writes
17
+ }) : new ArrayBufferTarget();
11
18
  const muxerConfig = {
12
- target: new ArrayBufferTarget(),
19
+ target,
13
20
  video: {
14
21
  codec: "avc",
15
22
  width: config.width,
@@ -46,7 +53,7 @@ class MP4Muxer {
46
53
  }
47
54
  audioChunkCount = 0;
48
55
  writeAudioChunk(chunk, metadata) {
49
- if (chunk.byteLength <= 16) {
56
+ if (chunk.byteLength <= 16 && (!chunk.duration || chunk.duration <= 0)) {
50
57
  return;
51
58
  }
52
59
  let meta;
@@ -59,13 +66,32 @@ class MP4Muxer {
59
66
  }
60
67
  this.firstAudioChunk = false;
61
68
  }
69
+ const hasValidDuration = !!chunk.duration && chunk.duration > 0;
70
+ if (!hasValidDuration) {
71
+ const sampleRate = metadata?.decoderConfig?.sampleRate ?? this.audioChunkMeta?.sampleRate ?? 48e3;
72
+ const inferredDurationUs = this.lastAudioDurationUs ?? Math.max(1, Math.round(1024 / sampleRate * 1e6));
73
+ const buffer = new ArrayBuffer(chunk.byteLength);
74
+ chunk.copyTo(buffer);
75
+ chunk = new EncodedAudioChunk({
76
+ type: chunk.type,
77
+ timestamp: chunk.timestamp,
78
+ duration: inferredDurationUs,
79
+ data: buffer
80
+ });
81
+ this.lastAudioDurationUs = inferredDurationUs;
82
+ } else {
83
+ this.lastAudioDurationUs = chunk.duration;
84
+ }
62
85
  this.audioChunkCount++;
63
86
  this.muxer.addAudioChunk(chunk, meta);
64
87
  }
65
88
  finalize() {
66
89
  this.muxer.finalize();
67
- const buffer = this.muxer.target.buffer;
68
- return new Blob([buffer], { type: "video/mp4" });
90
+ if (this.muxer.target instanceof ArrayBufferTarget) {
91
+ const buffer = this.muxer.target.buffer;
92
+ return new Blob([buffer], { type: "video/mp4" });
93
+ }
94
+ return null;
69
95
  }
70
96
  }
71
97
  export {
@@ -1 +1 @@
1
- {"version":3,"file":"MP4Muxer.js","sources":["../../../src/stages/mux/MP4Muxer.ts"],"sourcesContent":["import { Muxer, ArrayBufferTarget } from 'mp4-muxer';\n\n/**\n * MP4Muxer - MP4 container multiplexer using mp4-muxer library\n * Supports video and audio track export\n */\nexport class MP4Muxer {\n private muxer: Muxer<ArrayBufferTarget>;\n private firstVideoChunk = true;\n private firstAudioChunk = true;\n private videoChunkMeta: any = null;\n private audioChunkMeta: any = null;\n\n constructor(config: {\n width: number;\n height: number;\n fps: number;\n fastStart?: false | 'in-memory' | 'fragmented';\n videoChunkMeta?: any;\n audioChunkMeta?: any;\n }) {\n this.videoChunkMeta = config.videoChunkMeta;\n this.audioChunkMeta = config.audioChunkMeta;\n\n const muxerConfig: any = {\n target: new ArrayBufferTarget(),\n video: {\n codec: 'avc',\n width: config.width,\n height: config.height,\n frameRate: config.fps,\n },\n fastStart: config.fastStart ?? 'in-memory',\n firstTimestampBehavior: 'offset',\n };\n\n // Add audio configuration if provided\n // If not provided initially, we assume AAC (standard for web)\n // but mp4-muxer might need it.\n // Actually mp4-muxer allows adding track configuration later?\n // No, it requires it in constructor or inferred?\n // If audioChunkMeta is missing, we can't configure audio fully here.\n // But mp4-muxer docs say: \"If you don't provide options.audio, no audio track will be created.\"\n // So we MUST provide options.audio if we want audio.\n // If we don't have meta yet, we guess?\n // Or we rely on audio export always providing AAC 48k?\n // Let's assume AAC 48k 2ch as default if we want audio support.\n // Or better: if audioChunkMeta is null, we enable audio with defaults.\n\n // However, for robustness, we should probably wait for first chunk to configure muxer?\n // But MuxManager calls `start` then `writeVideoChunk` then `writeAudioChunk`.\n // Audio might come later.\n //\n // Let's assume we always want audio track capability.\n muxerConfig.audio = {\n codec: 'aac',\n sampleRate: this.audioChunkMeta?.sampleRate || 48000,\n numberOfChannels: this.audioChunkMeta?.numberOfChannels || 2,\n };\n\n this.muxer = new Muxer(muxerConfig);\n }\n\n private videoChunkCount = 0;\n\n writeVideoChunk(chunk: EncodedVideoChunk, metadata?: EncodedVideoChunkMetadata): void {\n let meta: EncodedVideoChunkMetadata | undefined;\n\n if (this.firstVideoChunk) {\n if (metadata && metadata.decoderConfig) {\n this.videoChunkMeta = metadata.decoderConfig;\n }\n\n if (this.videoChunkMeta) {\n meta = { decoderConfig: this.videoChunkMeta };\n }\n\n // Ensure we have metadata for first chunk if it's a keyframe\n if (chunk.type === 'key' && !meta) {\n console.warn('[MP4Muxer] First video chunk is keyframe but missing decoderConfig');\n }\n\n this.firstVideoChunk = false;\n }\n\n this.videoChunkCount++;\n this.muxer.addVideoChunk(chunk, meta);\n }\n\n private audioChunkCount = 0;\n\n writeAudioChunk(chunk: EncodedAudioChunk, metadata?: EncodedAudioChunkMetadata): void {\n // Safari (and sometimes Chromium) may emit tiny (e.g. 6 bytes) \"audio\" chunks that are not valid AAC frames.\n // Keeping them may cause players to treat the whole audio track as undecodable/silent.\n // Drop tiny chunks and DO NOT trust their metadata.\n if (chunk.byteLength <= 16) {\n return;\n }\n\n let meta: EncodedAudioChunkMetadata | undefined;\n\n if (this.firstAudioChunk) {\n if (metadata && metadata.decoderConfig) {\n this.audioChunkMeta = metadata.decoderConfig;\n }\n\n if (this.audioChunkMeta) {\n meta = { decoderConfig: this.audioChunkMeta };\n }\n\n this.firstAudioChunk = false;\n }\n\n this.audioChunkCount++;\n this.muxer.addAudioChunk(chunk, meta);\n }\n\n finalize(): Blob {\n this.muxer.finalize();\n const buffer = (this.muxer.target as ArrayBufferTarget).buffer;\n return new Blob([buffer], { type: 'video/mp4' });\n }\n}\n"],"names":[],"mappings":";AAMO,MAAM,SAAS;AAAA,EACZ;AAAA,EACA,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB,iBAAsB;AAAA,EACtB,iBAAsB;AAAA,EAE9B,YAAY,QAOT;AACD,SAAK,iBAAiB,OAAO;AAC7B,SAAK,iBAAiB,OAAO;AAE7B,UAAM,cAAmB;AAAA,MACvB,QAAQ,IAAI,kBAAA;AAAA,MACZ,OAAO;AAAA,QACL,OAAO;AAAA,QACP,OAAO,OAAO;AAAA,QACd,QAAQ,OAAO;AAAA,QACf,WAAW,OAAO;AAAA,MAAA;AAAA,MAEpB,WAAW,OAAO,aAAa;AAAA,MAC/B,wBAAwB;AAAA,IAAA;AAqB1B,gBAAY,QAAQ;AAAA,MAClB,OAAO;AAAA,MACP,YAAY,KAAK,gBAAgB,cAAc;AAAA,MAC/C,kBAAkB,KAAK,gBAAgB,oBAAoB;AAAA,IAAA;AAG7D,SAAK,QAAQ,IAAI,MAAM,WAAW;AAAA,EACpC;AAAA,EAEQ,kBAAkB;AAAA,EAE1B,gBAAgB,OAA0B,UAA4C;AACpF,QAAI;AAEJ,QAAI,KAAK,iBAAiB;AACxB,UAAI,YAAY,SAAS,eAAe;AACtC,aAAK,iBAAiB,SAAS;AAAA,MACjC;AAEA,UAAI,KAAK,gBAAgB;AACvB,eAAO,EAAE,eAAe,KAAK,eAAA;AAAA,MAC/B;AAGA,UAAI,MAAM,SAAS,SAAS,CAAC,MAAM;AACjC,gBAAQ,KAAK,oEAAoE;AAAA,MACnF;AAEA,WAAK,kBAAkB;AAAA,IACzB;AAEA,SAAK;AACL,SAAK,MAAM,cAAc,OAAO,IAAI;AAAA,EACtC;AAAA,EAEQ,kBAAkB;AAAA,EAE1B,gBAAgB,OAA0B,UAA4C;AAIpF,QAAI,MAAM,cAAc,IAAI;AAC1B;AAAA,IACF;AAEA,QAAI;AAEJ,QAAI,KAAK,iBAAiB;AACxB,UAAI,YAAY,SAAS,eAAe;AACtC,aAAK,iBAAiB,SAAS;AAAA,MACjC;AAEA,UAAI,KAAK,gBAAgB;AACvB,eAAO,EAAE,eAAe,KAAK,eAAA;AAAA,MAC/B;AAEA,WAAK,kBAAkB;AAAA,IACzB;AAEA,SAAK;AACL,SAAK,MAAM,cAAc,OAAO,IAAI;AAAA,EACtC;AAAA,EAEA,WAAiB;AACf,SAAK,MAAM,SAAA;AACX,UAAM,SAAU,KAAK,MAAM,OAA6B;AACxD,WAAO,IAAI,KAAK,CAAC,MAAM,GAAG,EAAE,MAAM,aAAa;AAAA,EACjD;AACF;"}
1
+ {"version":3,"file":"MP4Muxer.js","sources":["../../../src/stages/mux/MP4Muxer.ts"],"sourcesContent":["import { Muxer, ArrayBufferTarget, StreamTarget } from 'mp4-muxer';\n\nexport type MuxOutputConfig =\n | { kind: 'blob' }\n | {\n kind: 'stream';\n onData: (data: Uint8Array, position: number) => void;\n chunked?: boolean;\n chunkSize?: number;\n };\n\n/**\n * MP4Muxer - MP4 container multiplexer using mp4-muxer library\n * Supports video and audio track export\n */\nexport class MP4Muxer {\n private muxer: Muxer<ArrayBufferTarget | StreamTarget>;\n private firstVideoChunk = true;\n private firstAudioChunk = true;\n private videoChunkMeta: any = null;\n private audioChunkMeta: any = null;\n private lastAudioDurationUs: number | null = null;\n\n constructor(\n config: {\n width: number;\n height: number;\n fps: number;\n fastStart?: false | 'in-memory' | 'fragmented';\n videoChunkMeta?: any;\n audioChunkMeta?: any;\n },\n output: MuxOutputConfig = { kind: 'blob' }\n ) {\n this.videoChunkMeta = config.videoChunkMeta;\n this.audioChunkMeta = config.audioChunkMeta;\n\n const target =\n output.kind === 'stream'\n ? new StreamTarget({\n onData: output.onData,\n chunked: output.chunked ?? true,\n chunkSize: output.chunkSize ?? 16 * 1024 * 1024, // 16 MiB default to reduce writes\n })\n : new ArrayBufferTarget();\n\n const muxerConfig: any = {\n target,\n video: {\n codec: 'avc',\n width: config.width,\n height: config.height,\n frameRate: config.fps,\n },\n fastStart: config.fastStart ?? 'in-memory',\n firstTimestampBehavior: 'offset',\n };\n\n // Add audio configuration if provided\n // If not provided initially, we assume AAC (standard for web)\n // but mp4-muxer might need it.\n // Actually mp4-muxer allows adding track configuration later?\n // No, it requires it in constructor or inferred?\n // If audioChunkMeta is missing, we can't configure audio fully here.\n // But mp4-muxer docs say: \"If you don't provide options.audio, no audio track will be created.\"\n // So we MUST provide options.audio if we want audio.\n // If we don't have meta yet, we guess?\n // Or we rely on audio export always providing AAC 48k?\n // Let's assume AAC 48k 2ch as default if we want audio support.\n // Or better: if audioChunkMeta is null, we enable audio with defaults.\n\n // However, for robustness, we should probably wait for first chunk to configure muxer?\n // But MuxManager calls `start` then `writeVideoChunk` then `writeAudioChunk`.\n // Audio might come later.\n //\n // Let's assume we always want audio track capability.\n muxerConfig.audio = {\n codec: 'aac',\n sampleRate: this.audioChunkMeta?.sampleRate || 48000,\n numberOfChannels: this.audioChunkMeta?.numberOfChannels || 2,\n };\n\n this.muxer = new Muxer(muxerConfig);\n }\n\n private videoChunkCount = 0;\n\n writeVideoChunk(chunk: EncodedVideoChunk, metadata?: EncodedVideoChunkMetadata): void {\n let meta: EncodedVideoChunkMetadata | undefined;\n\n if (this.firstVideoChunk) {\n if (metadata && metadata.decoderConfig) {\n this.videoChunkMeta = metadata.decoderConfig;\n }\n\n if (this.videoChunkMeta) {\n meta = { decoderConfig: this.videoChunkMeta };\n }\n\n // Ensure we have metadata for first chunk if it's a keyframe\n if (chunk.type === 'key' && !meta) {\n console.warn('[MP4Muxer] First video chunk is keyframe but missing decoderConfig');\n }\n\n this.firstVideoChunk = false;\n }\n\n this.videoChunkCount++;\n this.muxer.addVideoChunk(chunk, meta);\n }\n\n private audioChunkCount = 0;\n\n writeAudioChunk(chunk: EncodedAudioChunk, metadata?: EncodedAudioChunkMetadata): void {\n // Safari (and sometimes Chromium) may emit tiny (e.g. 6 bytes) \"audio\" chunks that are not valid AAC frames.\n // mp4-muxer treats durations as authoritative; dropping *valid* small chunks can collapse silence gaps and cause stutter.\n // So only drop tiny chunks when they are clearly invalid (duration missing/<=0).\n if (chunk.byteLength <= 16 && (!chunk.duration || chunk.duration <= 0)) {\n return;\n }\n\n let meta: EncodedAudioChunkMetadata | undefined;\n\n if (this.firstAudioChunk) {\n if (metadata && metadata.decoderConfig) {\n this.audioChunkMeta = metadata.decoderConfig;\n }\n\n if (this.audioChunkMeta) {\n meta = { decoderConfig: this.audioChunkMeta };\n }\n\n this.firstAudioChunk = false;\n }\n\n // Some platforms output duration=0/undefined for audio chunks.\n // mp4-muxer uses duration to build the timeline; 0 duration can collapse time and remove intended gaps.\n // Try to synthesize a sane duration:\n // - Prefer the chunk's own duration when >0\n // - Otherwise use AAC-LC frame duration (1024 samples) inferred from decoderConfig sampleRate if known\n const hasValidDuration = !!chunk.duration && chunk.duration > 0;\n if (!hasValidDuration) {\n const sampleRate =\n (metadata as any)?.decoderConfig?.sampleRate ?? this.audioChunkMeta?.sampleRate ?? 48_000;\n const inferredDurationUs =\n this.lastAudioDurationUs ?? Math.max(1, Math.round((1024 / sampleRate) * 1_000_000));\n\n const buffer = new ArrayBuffer(chunk.byteLength);\n chunk.copyTo(buffer);\n chunk = new EncodedAudioChunk({\n type: chunk.type,\n timestamp: chunk.timestamp,\n duration: inferredDurationUs,\n data: buffer,\n });\n this.lastAudioDurationUs = inferredDurationUs;\n } else {\n this.lastAudioDurationUs = chunk.duration!;\n }\n\n this.audioChunkCount++;\n this.muxer.addAudioChunk(chunk, meta);\n }\n\n finalize(): Blob | null {\n this.muxer.finalize();\n if (this.muxer.target instanceof ArrayBufferTarget) {\n const buffer = this.muxer.target.buffer;\n return new Blob([buffer], { type: 'video/mp4' });\n }\n // Stream target has already delivered data via onData callback.\n return null;\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1,13 +1,17 @@
1
+ import { MuxOutputConfig } from './MP4Muxer';
2
+
1
3
  export declare class MuxManager {
2
4
  private muxer;
5
+ private output;
3
6
  constructor();
4
7
  start(config: {
5
8
  width: number;
6
9
  height: number;
7
10
  fps: number;
11
+ output?: MuxOutputConfig;
8
12
  }): void;
9
13
  writeVideoChunk(chunk: EncodedVideoChunk, metadata?: EncodedVideoChunkMetadata): void;
10
14
  writeAudioChunk(chunk: EncodedAudioChunk, metadata?: EncodedAudioChunkMetadata): void;
11
- finalize(): Blob;
15
+ finalize(): Blob | null;
12
16
  }
13
17
  //# sourceMappingURL=MuxManager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"MuxManager.d.ts","sourceRoot":"","sources":["../../../src/stages/mux/MuxManager.ts"],"names":[],"mappings":"AAEA,qBAAa,UAAU;IACrB,OAAO,CAAC,KAAK,CAAyB;;IAItC,KAAK,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE;IAU5D,eAAe,CAAC,KAAK,EAAE,iBAAiB,EAAE,QAAQ,CAAC,EAAE,yBAAyB;IAK9E,eAAe,CAAC,KAAK,EAAE,iBAAiB,EAAE,QAAQ,CAAC,EAAE,yBAAyB;IAS9E,QAAQ,IAAI,IAAI;CAMjB"}
1
+ {"version":3,"file":"MuxManager.d.ts","sourceRoot":"","sources":["../../../src/stages/mux/MuxManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAE5D,qBAAa,UAAU;IACrB,OAAO,CAAC,KAAK,CAAyB;IACtC,OAAO,CAAC,MAAM,CAAgC;;IAI9C,KAAK,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,eAAe,CAAA;KAAE;IAgBtF,eAAe,CAAC,KAAK,EAAE,iBAAiB,EAAE,QAAQ,CAAC,EAAE,yBAAyB;IAK9E,eAAe,CAAC,KAAK,EAAE,iBAAiB,EAAE,QAAQ,CAAC,EAAE,yBAAyB;IAS9E,QAAQ,IAAI,IAAI,GAAG,IAAI;CAOxB"}
@@ -1,16 +1,23 @@
1
1
  import { MP4Muxer } from "./MP4Muxer.js";
2
2
  class MuxManager {
3
3
  muxer = null;
4
+ output = null;
4
5
  constructor() {
5
6
  }
6
7
  start(config) {
7
- this.muxer = new MP4Muxer({
8
- width: config.width,
9
- height: config.height,
10
- fps: config.fps,
11
- fastStart: "in-memory"
12
- // Metadata will be handled by first chunks
13
- });
8
+ this.output = config.output ?? { kind: "blob" };
9
+ this.muxer = new MP4Muxer(
10
+ {
11
+ width: config.width,
12
+ height: config.height,
13
+ fps: config.fps,
14
+ // For blob output, prefer fast start by keeping chunks in memory.
15
+ // For stream output, prefer minimal memory usage: metadata at end (non-fMP4).
16
+ fastStart: this.output.kind === "stream" ? false : "in-memory"
17
+ // Metadata will be handled by first chunks
18
+ },
19
+ this.output
20
+ );
14
21
  }
15
22
  writeVideoChunk(chunk, metadata) {
16
23
  if (!this.muxer) throw new Error("Muxer not started");
@@ -27,6 +34,7 @@ class MuxManager {
27
34
  if (!this.muxer) throw new Error("Muxer not started");
28
35
  const blob = this.muxer.finalize();
29
36
  this.muxer = null;
37
+ this.output = null;
30
38
  return blob;
31
39
  }
32
40
  }
@@ -1 +1 @@
1
- {"version":3,"file":"MuxManager.js","sources":["../../../src/stages/mux/MuxManager.ts"],"sourcesContent":["import { MP4Muxer } from './MP4Muxer';\n\nexport class MuxManager {\n private muxer: MP4Muxer | null = null;\n\n constructor() {}\n\n start(config: { width: number; height: number; fps: number }) {\n this.muxer = new MP4Muxer({\n width: config.width,\n height: config.height,\n fps: config.fps,\n fastStart: 'in-memory',\n // Metadata will be handled by first chunks\n });\n }\n\n writeVideoChunk(chunk: EncodedVideoChunk, metadata?: EncodedVideoChunkMetadata) {\n if (!this.muxer) throw new Error('Muxer not started');\n this.muxer.writeVideoChunk(chunk, metadata);\n }\n\n writeAudioChunk(chunk: EncodedAudioChunk, metadata?: EncodedAudioChunkMetadata) {\n // Check if muxer is available (it might have been finalized already if audio is late)\n if (!this.muxer) {\n console.warn('[MuxManager] writeAudioChunk called after finalization, dropping chunk');\n return;\n }\n this.muxer.writeAudioChunk(chunk, metadata);\n }\n\n finalize(): Blob {\n if (!this.muxer) throw new Error('Muxer not started');\n const blob = this.muxer.finalize();\n this.muxer = null;\n return blob;\n }\n}\n"],"names":[],"mappings":";AAEO,MAAM,WAAW;AAAA,EACd,QAAyB;AAAA,EAEjC,cAAc;AAAA,EAAC;AAAA,EAEf,MAAM,QAAwD;AAC5D,SAAK,QAAQ,IAAI,SAAS;AAAA,MACxB,OAAO,OAAO;AAAA,MACd,QAAQ,OAAO;AAAA,MACf,KAAK,OAAO;AAAA,MACZ,WAAW;AAAA;AAAA,IAAA,CAEZ;AAAA,EACH;AAAA,EAEA,gBAAgB,OAA0B,UAAsC;AAC9E,QAAI,CAAC,KAAK,MAAO,OAAM,IAAI,MAAM,mBAAmB;AACpD,SAAK,MAAM,gBAAgB,OAAO,QAAQ;AAAA,EAC5C;AAAA,EAEA,gBAAgB,OAA0B,UAAsC;AAE9E,QAAI,CAAC,KAAK,OAAO;AACf,cAAQ,KAAK,wEAAwE;AACrF;AAAA,IACF;AACA,SAAK,MAAM,gBAAgB,OAAO,QAAQ;AAAA,EAC5C;AAAA,EAEA,WAAiB;AACf,QAAI,CAAC,KAAK,MAAO,OAAM,IAAI,MAAM,mBAAmB;AACpD,UAAM,OAAO,KAAK,MAAM,SAAA;AACxB,SAAK,QAAQ;AACb,WAAO;AAAA,EACT;AACF;"}
1
+ {"version":3,"file":"MuxManager.js","sources":["../../../src/stages/mux/MuxManager.ts"],"sourcesContent":["import { MP4Muxer, type MuxOutputConfig } from './MP4Muxer';\n\nexport class MuxManager {\n private muxer: MP4Muxer | null = null;\n private output: MuxOutputConfig | null = null;\n\n constructor() {}\n\n start(config: { width: number; height: number; fps: number; output?: MuxOutputConfig }) {\n this.output = config.output ?? { kind: 'blob' };\n this.muxer = new MP4Muxer(\n {\n width: config.width,\n height: config.height,\n fps: config.fps,\n // For blob output, prefer fast start by keeping chunks in memory.\n // For stream output, prefer minimal memory usage: metadata at end (non-fMP4).\n fastStart: this.output.kind === 'stream' ? false : 'in-memory',\n // Metadata will be handled by first chunks\n },\n this.output\n );\n }\n\n writeVideoChunk(chunk: EncodedVideoChunk, metadata?: EncodedVideoChunkMetadata) {\n if (!this.muxer) throw new Error('Muxer not started');\n this.muxer.writeVideoChunk(chunk, metadata);\n }\n\n writeAudioChunk(chunk: EncodedAudioChunk, metadata?: EncodedAudioChunkMetadata) {\n // Check if muxer is available (it might have been finalized already if audio is late)\n if (!this.muxer) {\n console.warn('[MuxManager] writeAudioChunk called after finalization, dropping chunk');\n return;\n }\n this.muxer.writeAudioChunk(chunk, metadata);\n }\n\n finalize(): Blob | null {\n if (!this.muxer) throw new Error('Muxer not started');\n const blob = this.muxer.finalize();\n this.muxer = null;\n this.output = null;\n return blob;\n }\n}\n"],"names":[],"mappings":";AAEO,MAAM,WAAW;AAAA,EACd,QAAyB;AAAA,EACzB,SAAiC;AAAA,EAEzC,cAAc;AAAA,EAAC;AAAA,EAEf,MAAM,QAAkF;AACtF,SAAK,SAAS,OAAO,UAAU,EAAE,MAAM,OAAA;AACvC,SAAK,QAAQ,IAAI;AAAA,MACf;AAAA,QACE,OAAO,OAAO;AAAA,QACd,QAAQ,OAAO;AAAA,QACf,KAAK,OAAO;AAAA;AAAA;AAAA,QAGZ,WAAW,KAAK,OAAO,SAAS,WAAW,QAAQ;AAAA;AAAA,MAAA;AAAA,MAGrD,KAAK;AAAA,IAAA;AAAA,EAET;AAAA,EAEA,gBAAgB,OAA0B,UAAsC;AAC9E,QAAI,CAAC,KAAK,MAAO,OAAM,IAAI,MAAM,mBAAmB;AACpD,SAAK,MAAM,gBAAgB,OAAO,QAAQ;AAAA,EAC5C;AAAA,EAEA,gBAAgB,OAA0B,UAAsC;AAE9E,QAAI,CAAC,KAAK,OAAO;AACf,cAAQ,KAAK,wEAAwE;AACrF;AAAA,IACF;AACA,SAAK,MAAM,gBAAgB,OAAO,QAAQ;AAAA,EAC5C;AAAA,EAEA,WAAwB;AACtB,QAAI,CAAC,KAAK,MAAO,OAAM,IAAI,MAAM,mBAAmB;AACpD,UAAM,OAAO,KAAK,MAAM,SAAA;AACxB,SAAK,QAAQ;AACb,SAAK,SAAS;AACd,WAAO;AAAA,EACT;AACF;"}
package/dist/types.d.ts CHANGED
@@ -21,5 +21,13 @@ export interface ExportOptions {
21
21
  fps?: number;
22
22
  /** Quality preset */
23
23
  quality?: 'low' | 'medium' | 'high' | 'highest';
24
+ /** Export output mode (default blob). */
25
+ exportMode?: 'blob' | 'stream';
26
+ /** Streaming mux callback, required when exportMode is 'stream'. */
27
+ onMuxData?: (data: Uint8Array, position: number) => void;
28
+ /** Optional chunk sizing hint for streaming mux output. */
29
+ muxChunkSizeBytes?: number;
30
+ /** Whether to enable mp4-muxer chunked StreamTarget (default true). */
31
+ muxChunked?: boolean;
24
32
  }
25
33
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEpE,MAAM,MAAM,YAAY,GACpB,MAAM,GACN,SAAS,GACT,OAAO,GACP,SAAS,GACT,QAAQ,GACR,WAAW,GACX,OAAO,GACP,WAAW,CAAC;AAEhB,MAAM,WAAW,aAAa;IAC5B,oBAAoB;IACpB,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAExB,kBAAkB;IAClB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,CAAC;IAE7C,kBAAkB;IAClB,UAAU,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAE5B,2BAA2B;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,2BAA2B;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,wBAAwB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,iBAAiB;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,qBAAqB;IACrB,OAAO,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;CACjD"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEpE,MAAM,MAAM,YAAY,GACpB,MAAM,GACN,SAAS,GACT,OAAO,GACP,SAAS,GACT,QAAQ,GACR,WAAW,GACX,OAAO,GACP,WAAW,CAAC;AAEhB,MAAM,WAAW,aAAa;IAC5B,oBAAoB;IACpB,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAExB,kBAAkB;IAClB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,CAAC;IAE7C,kBAAkB;IAClB,UAAU,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAE5B,2BAA2B;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,2BAA2B;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,wBAAwB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,iBAAiB;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,qBAAqB;IACrB,OAAO,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;IAEhD,yCAAyC;IACzC,UAAU,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC/B,oEAAoE;IACpE,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACzD,2DAA2D;IAC3D,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,uEAAuE;IACvE,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB"}
@@ -1,4 +1,4 @@
1
- import * as mp4box_all from "../medeo-fe/node_modules/.pnpm/mp4box@0.5.4/node_modules/mp4box/dist/mp4box.all.js";
1
+ import * as mp4box_all from "../node_modules/.pnpm/mp4box@0.5.4/node_modules/mp4box/dist/mp4box.all.js";
2
2
  const lib = mp4box_all;
3
3
  const MP4Box = lib.default && typeof lib.default.createFile === "function" ? lib.default : lib;
4
4
  if (typeof MP4Box.createFile !== "function") {