@meframe/core 0.0.28 → 0.0.30-beta

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 (220) hide show
  1. package/dist/Meframe.d.ts +2 -13
  2. package/dist/Meframe.d.ts.map +1 -1
  3. package/dist/Meframe.js +6 -100
  4. package/dist/Meframe.js.map +1 -1
  5. package/dist/cache/CacheManager.d.ts +35 -19
  6. package/dist/cache/CacheManager.d.ts.map +1 -1
  7. package/dist/cache/CacheManager.js +223 -134
  8. package/dist/cache/CacheManager.js.map +1 -1
  9. package/dist/cache/l1/VideoL1Cache.d.ts +15 -2
  10. package/dist/cache/l1/VideoL1Cache.d.ts.map +1 -1
  11. package/dist/cache/l1/VideoL1Cache.js +58 -38
  12. package/dist/cache/l1/VideoL1Cache.js.map +1 -1
  13. package/dist/cache/l2/L2Cache.d.ts.map +1 -1
  14. package/dist/cache/l2/L2Cache.js +5 -5
  15. package/dist/cache/l2/L2Cache.js.map +1 -1
  16. package/dist/cache/l2/L2OPFSStore.d.ts +37 -0
  17. package/dist/cache/l2/L2OPFSStore.d.ts.map +1 -0
  18. package/dist/cache/l2/L2OPFSStore.js +89 -0
  19. package/dist/cache/l2/L2OPFSStore.js.map +1 -0
  20. package/dist/cache/resource/AudioSampleCache.d.ts +52 -0
  21. package/dist/cache/resource/AudioSampleCache.d.ts.map +1 -0
  22. package/dist/cache/resource/AudioSampleCache.js +69 -0
  23. package/dist/cache/resource/AudioSampleCache.js.map +1 -0
  24. package/dist/cache/resource/ImageBitmapCache.d.ts +65 -0
  25. package/dist/cache/resource/ImageBitmapCache.d.ts.map +1 -0
  26. package/dist/cache/resource/ImageBitmapCache.js +101 -0
  27. package/dist/cache/resource/ImageBitmapCache.js.map +1 -0
  28. package/dist/cache/resource/MP4IndexCache.d.ts +48 -0
  29. package/dist/cache/resource/MP4IndexCache.d.ts.map +1 -0
  30. package/dist/cache/resource/MP4IndexCache.js +104 -0
  31. package/dist/cache/resource/MP4IndexCache.js.map +1 -0
  32. package/dist/cache/resource/ResourceCache.d.ts +46 -0
  33. package/dist/cache/resource/ResourceCache.d.ts.map +1 -0
  34. package/dist/cache/resource/ResourceCache.js +92 -0
  35. package/dist/cache/resource/ResourceCache.js.map +1 -0
  36. package/dist/cache/storage/indexeddb/ChunkRecordStore.d.ts +75 -0
  37. package/dist/cache/storage/indexeddb/ChunkRecordStore.d.ts.map +1 -0
  38. package/dist/cache/{l2/IndexedDBStore.js → storage/indexeddb/ChunkRecordStore.js} +3 -3
  39. package/dist/cache/storage/indexeddb/ChunkRecordStore.js.map +1 -0
  40. package/dist/cache/storage/opfs/OPFSManager.d.ts +54 -0
  41. package/dist/cache/storage/opfs/OPFSManager.d.ts.map +1 -0
  42. package/dist/cache/storage/opfs/OPFSManager.js +133 -0
  43. package/dist/cache/storage/opfs/OPFSManager.js.map +1 -0
  44. package/dist/cache/storage/opfs/types.d.ts +16 -0
  45. package/dist/cache/storage/opfs/types.d.ts.map +1 -0
  46. package/dist/config/defaults.d.ts.map +1 -1
  47. package/dist/config/defaults.js +21 -2
  48. package/dist/config/defaults.js.map +1 -1
  49. package/dist/config/types.d.ts +28 -0
  50. package/dist/config/types.d.ts.map +1 -1
  51. package/dist/controllers/ExportController.d.ts +16 -0
  52. package/dist/controllers/ExportController.d.ts.map +1 -0
  53. package/dist/controllers/ExportController.js +44 -0
  54. package/dist/controllers/ExportController.js.map +1 -0
  55. package/dist/controllers/PlaybackController.d.ts +28 -4
  56. package/dist/controllers/PlaybackController.d.ts.map +1 -1
  57. package/dist/controllers/PlaybackController.js +117 -52
  58. package/dist/controllers/PlaybackController.js.map +1 -1
  59. package/dist/controllers/index.d.ts +2 -3
  60. package/dist/controllers/index.d.ts.map +1 -1
  61. package/dist/controllers/types.d.ts +0 -28
  62. package/dist/controllers/types.d.ts.map +1 -1
  63. package/dist/event/events.d.ts +8 -0
  64. package/dist/event/events.d.ts.map +1 -1
  65. package/dist/event/events.js +1 -0
  66. package/dist/event/events.js.map +1 -1
  67. package/dist/model/CompositionModel.d.ts.map +1 -1
  68. package/dist/model/CompositionModel.js +11 -6
  69. package/dist/model/CompositionModel.js.map +1 -1
  70. package/dist/model/RcFrame.d.ts +2 -0
  71. package/dist/model/RcFrame.d.ts.map +1 -1
  72. package/dist/model/RcFrame.js +3 -0
  73. package/dist/model/RcFrame.js.map +1 -1
  74. package/dist/orchestrator/ExportScheduler.d.ts +35 -0
  75. package/dist/orchestrator/ExportScheduler.d.ts.map +1 -0
  76. package/dist/orchestrator/ExportScheduler.js +241 -0
  77. package/dist/orchestrator/ExportScheduler.js.map +1 -0
  78. package/dist/orchestrator/GlobalAudioSession.d.ts +21 -7
  79. package/dist/orchestrator/GlobalAudioSession.d.ts.map +1 -1
  80. package/dist/orchestrator/GlobalAudioSession.js +132 -140
  81. package/dist/orchestrator/GlobalAudioSession.js.map +1 -1
  82. package/dist/orchestrator/OnDemandVideoSession.d.ts +73 -0
  83. package/dist/orchestrator/OnDemandVideoSession.d.ts.map +1 -0
  84. package/dist/orchestrator/OnDemandVideoSession.js +281 -0
  85. package/dist/orchestrator/OnDemandVideoSession.js.map +1 -0
  86. package/dist/orchestrator/Orchestrator.d.ts +22 -17
  87. package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
  88. package/dist/orchestrator/Orchestrator.js +234 -301
  89. package/dist/orchestrator/Orchestrator.js.map +1 -1
  90. package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
  91. package/dist/orchestrator/VideoClipSession.js +3 -15
  92. package/dist/orchestrator/VideoClipSession.js.map +1 -1
  93. package/dist/orchestrator/index.d.ts +0 -1
  94. package/dist/orchestrator/index.d.ts.map +1 -1
  95. package/dist/orchestrator/types.d.ts +4 -4
  96. package/dist/orchestrator/types.d.ts.map +1 -1
  97. package/dist/stages/compose/FilterProcessor.d.ts +1 -1
  98. package/dist/stages/compose/FilterProcessor.d.ts.map +1 -1
  99. package/dist/stages/compose/FilterProcessor.js +226 -0
  100. package/dist/stages/compose/FilterProcessor.js.map +1 -0
  101. package/dist/stages/compose/FrameRateConverter.d.ts +68 -0
  102. package/dist/stages/compose/FrameRateConverter.d.ts.map +1 -0
  103. package/dist/stages/compose/LayerRenderer.d.ts +1 -1
  104. package/dist/stages/compose/LayerRenderer.d.ts.map +1 -1
  105. package/dist/stages/compose/LayerRenderer.js +270 -0
  106. package/dist/stages/compose/LayerRenderer.js.map +1 -0
  107. package/dist/stages/compose/TransitionProcessor.d.ts +1 -1
  108. package/dist/stages/compose/TransitionProcessor.d.ts.map +1 -1
  109. package/dist/stages/compose/TransitionProcessor.js +189 -0
  110. package/dist/stages/compose/TransitionProcessor.js.map +1 -0
  111. package/dist/stages/compose/VideoComposer.d.ts +6 -4
  112. package/dist/stages/compose/VideoComposer.d.ts.map +1 -1
  113. package/dist/stages/compose/VideoComposer.js +229 -0
  114. package/dist/stages/compose/VideoComposer.js.map +1 -0
  115. package/dist/stages/compose/text-renderers/animation-utils.js +76 -0
  116. package/dist/stages/compose/text-renderers/animation-utils.js.map +1 -0
  117. package/dist/stages/compose/text-renderers/basic-text-renderer.d.ts +2 -2
  118. package/dist/stages/compose/text-renderers/basic-text-renderer.d.ts.map +1 -1
  119. package/dist/stages/compose/text-renderers/basic-text-renderer.js +93 -0
  120. package/dist/stages/compose/text-renderers/basic-text-renderer.js.map +1 -0
  121. package/dist/stages/compose/text-renderers/character-ktv-renderer.d.ts +1 -1
  122. package/dist/stages/compose/text-renderers/character-ktv-renderer.d.ts.map +1 -1
  123. package/dist/stages/compose/text-renderers/character-ktv-renderer.js +132 -0
  124. package/dist/stages/compose/text-renderers/character-ktv-renderer.js.map +1 -0
  125. package/dist/stages/compose/text-renderers/word-by-word-renderer.d.ts +1 -1
  126. package/dist/stages/compose/text-renderers/word-by-word-renderer.d.ts.map +1 -1
  127. package/dist/stages/compose/text-renderers/word-by-word-renderer.js +128 -0
  128. package/dist/stages/compose/text-renderers/word-by-word-renderer.js.map +1 -0
  129. package/dist/stages/compose/text-renderers/word-fancy-renderer.d.ts +1 -1
  130. package/dist/stages/compose/text-renderers/word-fancy-renderer.d.ts.map +1 -1
  131. package/dist/stages/compose/text-renderers/word-fancy-renderer.js +135 -0
  132. package/dist/stages/compose/text-renderers/word-fancy-renderer.js.map +1 -0
  133. package/dist/stages/compose/text-utils/locale-detector.js +16 -0
  134. package/dist/stages/compose/text-utils/locale-detector.js.map +1 -0
  135. package/dist/stages/compose/text-utils/text-metrics.js +21 -0
  136. package/dist/stages/compose/text-utils/text-metrics.js.map +1 -0
  137. package/dist/stages/compose/text-utils/text-wrapper.js +225 -0
  138. package/dist/stages/compose/text-utils/text-wrapper.js.map +1 -0
  139. package/dist/stages/compose/types.d.ts +2 -1
  140. package/dist/stages/compose/types.d.ts.map +1 -1
  141. package/dist/stages/decode/BaseDecoder.js +0 -3
  142. package/dist/stages/decode/BaseDecoder.js.map +1 -1
  143. package/dist/stages/demux/MP4Demuxer.d.ts +5 -0
  144. package/dist/stages/demux/MP4Demuxer.d.ts.map +1 -1
  145. package/dist/stages/demux/MP4Demuxer.js +281 -0
  146. package/dist/stages/demux/MP4Demuxer.js.map +1 -0
  147. package/dist/stages/demux/MP4IndexParser.d.ts +71 -0
  148. package/dist/stages/demux/MP4IndexParser.d.ts.map +1 -0
  149. package/dist/stages/demux/MP4IndexParser.js +416 -0
  150. package/dist/stages/demux/MP4IndexParser.js.map +1 -0
  151. package/dist/stages/demux/types.d.ts +48 -0
  152. package/dist/stages/demux/types.d.ts.map +1 -1
  153. package/dist/stages/encode/index.d.ts +0 -1
  154. package/dist/stages/encode/index.d.ts.map +1 -1
  155. package/dist/stages/load/ResourceLoader.d.ts +44 -2
  156. package/dist/stages/load/ResourceLoader.d.ts.map +1 -1
  157. package/dist/stages/load/ResourceLoader.js +281 -37
  158. package/dist/stages/load/ResourceLoader.js.map +1 -1
  159. package/dist/stages/load/TaskManager.d.ts +6 -2
  160. package/dist/stages/load/TaskManager.d.ts.map +1 -1
  161. package/dist/stages/load/TaskManager.js +27 -4
  162. package/dist/stages/load/TaskManager.js.map +1 -1
  163. package/dist/stages/load/types.d.ts +7 -0
  164. package/dist/stages/load/types.d.ts.map +1 -1
  165. package/dist/stages/mux/MP4Muxer.d.ts +2 -2
  166. package/dist/stages/mux/MP4Muxer.d.ts.map +1 -1
  167. package/dist/stages/mux/MP4Muxer.js +24 -13
  168. package/dist/stages/mux/MP4Muxer.js.map +1 -1
  169. package/dist/stages/mux/MuxManager.d.ts +10 -21
  170. package/dist/stages/mux/MuxManager.d.ts.map +1 -1
  171. package/dist/stages/mux/MuxManager.js +21 -162
  172. package/dist/stages/mux/MuxManager.js.map +1 -1
  173. package/dist/stages/mux/index.d.ts +0 -1
  174. package/dist/stages/mux/index.d.ts.map +1 -1
  175. package/dist/utils/binary-search.d.ts +12 -4
  176. package/dist/utils/binary-search.d.ts.map +1 -1
  177. package/dist/utils/binary-search.js +52 -6
  178. package/dist/utils/binary-search.js.map +1 -1
  179. package/dist/workers/{BaseDecoder.BWYu1W0B.js → BaseDecoder.CTW-vr29.js} +1 -4
  180. package/dist/workers/BaseDecoder.CTW-vr29.js.map +1 -0
  181. package/dist/workers/{MP4Demuxer.CFHDkPYc.js → MP4Demuxer.BEa6PLJm.js} +10 -3
  182. package/dist/workers/{MP4Demuxer.CFHDkPYc.js.map → MP4Demuxer.BEa6PLJm.js.map} +1 -1
  183. package/dist/workers/stages/compose/{video-compose.worker.M5uomNVr.js → video-compose.worker.DHQ8B105.js} +260 -83
  184. package/dist/workers/stages/compose/video-compose.worker.DHQ8B105.js.map +1 -0
  185. package/dist/workers/stages/decode/{audio-decode.worker.DnS17GD9.js → audio-decode.worker.CP8bXXa4.js} +2 -2
  186. package/dist/workers/stages/decode/{audio-decode.worker.DnS17GD9.js.map → audio-decode.worker.CP8bXXa4.js.map} +1 -1
  187. package/dist/workers/stages/decode/{video-decode.worker.BEYsjOXp.js → video-decode.worker.BIspTxgV.js} +2 -2
  188. package/dist/workers/stages/decode/{video-decode.worker.BEYsjOXp.js.map → video-decode.worker.BIspTxgV.js.map} +1 -1
  189. package/dist/workers/stages/demux/{audio-demux.worker.BTFPcY7P.js → audio-demux.worker._VRQdLdv.js} +2 -2
  190. package/dist/workers/stages/demux/{audio-demux.worker.BTFPcY7P.js.map → audio-demux.worker._VRQdLdv.js.map} +1 -1
  191. package/dist/workers/stages/demux/{video-demux.worker.D_WeHPkt.js → video-demux.worker.CSkxGtmx.js} +3 -19
  192. package/dist/workers/stages/demux/video-demux.worker.CSkxGtmx.js.map +1 -0
  193. package/dist/workers/worker-manifest.json +5 -5
  194. package/package.json +1 -1
  195. package/dist/cache/l2/IndexedDBStore.js.map +0 -1
  196. package/dist/cache/l2/OPFSStore.js +0 -131
  197. package/dist/cache/l2/OPFSStore.js.map +0 -1
  198. package/dist/controllers/PreRenderService.d.ts +0 -59
  199. package/dist/controllers/PreRenderService.d.ts.map +0 -1
  200. package/dist/controllers/PreRenderService.js +0 -185
  201. package/dist/controllers/PreRenderService.js.map +0 -1
  202. package/dist/controllers/PreRenderTaskQueue.d.ts +0 -21
  203. package/dist/controllers/PreRenderTaskQueue.d.ts.map +0 -1
  204. package/dist/orchestrator/ClipSessionManager.d.ts +0 -70
  205. package/dist/orchestrator/ClipSessionManager.d.ts.map +0 -1
  206. package/dist/orchestrator/ClipSessionManager.js +0 -158
  207. package/dist/orchestrator/ClipSessionManager.js.map +0 -1
  208. package/dist/stages/decode/AudioChunkDecoder.js +0 -169
  209. package/dist/stages/decode/AudioChunkDecoder.js.map +0 -1
  210. package/dist/stages/encode/ClipEncoderManager.d.ts +0 -64
  211. package/dist/stages/encode/ClipEncoderManager.d.ts.map +0 -1
  212. package/dist/stages/mux/OPFSWriter.d.ts +0 -46
  213. package/dist/stages/mux/OPFSWriter.d.ts.map +0 -1
  214. package/dist/utils/BackpressureAdapter.d.ts +0 -26
  215. package/dist/utils/BackpressureAdapter.d.ts.map +0 -1
  216. package/dist/utils/time-utils.js +0 -45
  217. package/dist/utils/time-utils.js.map +0 -1
  218. package/dist/workers/BaseDecoder.BWYu1W0B.js.map +0 -1
  219. package/dist/workers/stages/compose/video-compose.worker.M5uomNVr.js.map +0 -1
  220. package/dist/workers/stages/demux/video-demux.worker.D_WeHPkt.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"video-decode.worker.BEYsjOXp.js","sources":["../../../../src/stages/decode/VideoChunkDecoder.ts","../../../../src/stages/decode/video-decode.worker.ts"],"sourcesContent":["import { VideoDecoderConfig } from './types';\nimport { BaseDecoder } from './BaseDecoder';\n\n/**\n * Video decoder with GOP tracking\n * Tracks keyframe boundaries and attaches GOP metadata to decoded frames\n */\nexport class VideoChunkDecoder extends BaseDecoder<\n VideoDecoder,\n VideoDecoderConfig,\n EncodedVideoChunk,\n VideoFrame\n> {\n private static readonly DEFAULT_HIGH_WATER_MARK = 4;\n private static readonly DEFAULT_DECODE_QUEUE_THRESHOLD = 16;\n\n readonly trackId: string;\n\n protected readonly highWaterMark: number;\n protected readonly decodeQueueThreshold: number;\n\n // Buffering support for delayed configuration\n private bufferedChunks: EncodedVideoChunk[] = [];\n private isProcessingBuffer: boolean = false;\n\n constructor(trackId: string, config?: Partial<VideoDecoderConfig>) {\n super((config || {}) as VideoDecoderConfig);\n\n this.trackId = trackId;\n this.highWaterMark =\n config?.backpressure?.highWaterMark ?? VideoChunkDecoder.DEFAULT_HIGH_WATER_MARK;\n this.decodeQueueThreshold =\n config?.backpressure?.decodeQueueThreshold ??\n VideoChunkDecoder.DEFAULT_DECODE_QUEUE_THRESHOLD;\n }\n\n // Computed properties\n get isConfigured(): boolean {\n return this.isReady;\n }\n\n get state(): string {\n return this.decoder?.state || 'unconfigured';\n }\n\n /**\n * Update configuration - can be called before or after initialization\n */\n async updateConfig(config: Partial<VideoDecoderConfig>): Promise<void> {\n if (!this.isReady && config.codec) {\n await this.configure(config as VideoDecoderConfig);\n await this.processBufferedChunks();\n }\n }\n\n // Override createStream to handle GOP tracking and buffering\n // Always create new stream for each clip (ReadableStreams can only be consumed once)\n override createStream(): TransformStream<EncodedVideoChunk, VideoFrame> {\n return new TransformStream<EncodedVideoChunk, VideoFrame>(\n {\n start: async (controller) => {\n this.controller = controller;\n // Don't initialize if no codec config yet\n if (this.config?.codec && this.config?.description && !this.isReady) {\n await this.initialize();\n }\n },\n\n transform: async (chunk) => {\n // If not configured yet, buffer the chunk\n if (!this.isReady) {\n this.bufferedChunks.push(chunk);\n return; // Don't process yet\n }\n\n // If we're processing buffered chunks, add to buffer to maintain order\n if (this.isProcessingBuffer) {\n this.bufferedChunks.push(chunk);\n return;\n }\n\n // Normal processing\n await this.processChunk(chunk);\n },\n\n flush: async () => {\n if (this.isReady) {\n await this.flush();\n }\n },\n },\n {\n highWaterMark: this.highWaterMark,\n size: () => 1,\n }\n );\n }\n\n /**\n * Process a single chunk (extracted from transform for reuse)\n */\n private async processChunk(chunk: EncodedVideoChunk): Promise<void> {\n if (!this.decoder) {\n throw new Error('Decoder not initialized');\n }\n\n if (this.decoder.state !== 'configured') {\n console.error('[VideoChunkDecoder] Decoder in unexpected state:', this.decoder.state);\n throw new Error(`Decoder not configured, state: ${this.decoder.state}`);\n }\n\n // Check decoder queue pressure\n if (this.decoder.decodeQueueSize >= this.decodeQueueThreshold) {\n await new Promise<void>((resolve) => {\n const check = () => {\n if (!this.decoder || this.decoder.decodeQueueSize < this.decodeQueueThreshold - 1) {\n resolve();\n } else {\n setTimeout(check, 20);\n }\n };\n check();\n });\n }\n\n // Decode the chunk\n try {\n this.decode(chunk);\n } catch (error) {\n console.error(`[VideoChunkDecoder] decode error:`, error);\n throw error; // Re-throw to propagate\n }\n }\n\n // Override handleOutput to enqueue decoded frames\n protected override handleOutput(frame: VideoFrame): void {\n super.handleOutput(frame);\n }\n\n // Implement abstract methods\n protected async isConfigSupported(config: VideoDecoderConfig): Promise<{ supported: boolean }> {\n const result = await VideoDecoder.isConfigSupported({\n codec: config.codec,\n codedWidth: config.width,\n codedHeight: config.height,\n });\n return { supported: result.supported ?? false };\n }\n\n protected createDecoder(init: {\n output: (frame: VideoFrame) => void;\n error: (error: DOMException) => void;\n }): VideoDecoder {\n return new VideoDecoder(init);\n }\n\n protected getDecoderType(): string {\n return 'Video';\n }\n\n protected async configureDecoder(config: VideoDecoderConfig): Promise<void> {\n if (!this.decoder) return;\n\n const decoderConfig = {\n codec: config.codec,\n codedWidth: config.width,\n codedHeight: config.height,\n hardwareAcceleration: config.hardwareAcceleration || 'no-preference',\n optimizeForLatency: false,\n ...(config.description && { description: config.description }),\n ...(config.displayAspectWidth && { displayAspectWidth: config.displayAspectWidth }),\n ...(config.displayAspectHeight && { displayAspectHeight: config.displayAspectHeight }),\n };\n\n this.decoder.configure(decoderConfig as any);\n // console.log('[VideoChunkDecoder] Decoder configured, state:', this.decoder.state);\n }\n\n protected decode(chunk: EncodedVideoChunk): void {\n this.decoder?.decode(chunk);\n }\n\n /**\n * Configure the decoder with codec info (can be called after creation)\n */\n async configure(config: VideoDecoderConfig): Promise<void> {\n // console.log('[VideoChunkDecoder] Configuring with:', config);\n\n if (this.isReady) {\n // If already configured, reconfigure\n await this.reconfigure(config);\n return;\n }\n\n this.config = config as any;\n\n // Initialize decoder with new config\n await this.initialize();\n }\n\n /**\n * Process any buffered chunks after configuration\n */\n async processBufferedChunks(): Promise<void> {\n if (!this.isReady || this.bufferedChunks.length === 0) {\n return;\n }\n\n // console.log('[VideoChunkDecoder] Processing', this.bufferedChunks.length, 'buffered chunks');\n this.isProcessingBuffer = true;\n\n // Process buffered chunks in order\n const chunks = [...this.bufferedChunks];\n this.bufferedChunks = [];\n\n for (const chunk of chunks) {\n try {\n await this.processChunk(chunk);\n } catch (error) {\n console.error('[VideoChunkDecoder] Error processing buffered chunk:', error);\n }\n }\n\n this.isProcessingBuffer = false;\n\n // Process any new chunks that arrived while processing buffer\n if (this.bufferedChunks.length > 0) {\n await this.processBufferedChunks();\n }\n }\n\n // Override close to clean up buffered chunks\n override async close(): Promise<void> {\n // Clear buffered chunks\n this.bufferedChunks = [];\n\n // Call parent close\n await super.close();\n }\n}\n","import { WorkerChannel } from '../../worker/WorkerChannel';\nimport { WorkerMessageType, WorkerState } from '../../worker/types';\nimport { VideoChunkDecoder } from './VideoChunkDecoder';\nimport { VideoDecoderConfig } from './types';\n\nconst normalizeDescription = (desc?: ArrayBuffer | ArrayBufferView): ArrayBuffer | undefined => {\n if (!desc) return undefined;\n\n if (desc instanceof ArrayBuffer) return desc;\n\n const view = desc as ArrayBufferView;\n return view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength) as ArrayBuffer;\n};\n\n/**\n * VideoDecodeWorker (Clip Local) - Decodes video for a single clip\n * Receives encoded video chunks from VideoDemuxWorker and outputs decoded frames\n *\n * Pipeline: VideoDemuxWorker → VideoDecodeWorker → VideoComposeWorker\n *\n * Features:\n * - Single clip, single VideoDecoder instance (no routing)\n * - GOP-based decoding with metadata\n * - Stream-based processing with backpressure\n * - Lifecycle tied to clip pipeline\n */\nexport class VideoDecodeWorker {\n private channel: WorkerChannel;\n private decoder: VideoChunkDecoder | null = null;\n private clipId: string = '';\n\n private defaultConfig: Partial<VideoDecoderConfig> = {};\n\n private upstreamPort: MessagePort | null = null;\n private downstreamPort: MessagePort | null = null;\n\n constructor() {\n this.channel = new WorkerChannel(self as any, {\n name: 'VideoDecodeWorker',\n timeout: 30000,\n });\n\n this.setupHandlers();\n }\n\n private setupHandlers(): void {\n this.channel.registerHandler('configure', this.handleConfigure.bind(this));\n this.channel.registerHandler('connect', this.handleConnect.bind(this));\n this.channel.registerHandler('flush', this.handleFlush.bind(this));\n this.channel.registerHandler('reset', this.handleReset.bind(this));\n this.channel.registerHandler('get_stats', this.handleGetStats.bind(this));\n this.channel.registerHandler(WorkerMessageType.Dispose, this.handleDispose.bind(this));\n }\n\n private async handleConnect(payload: {\n direction: 'upstream' | 'downstream';\n port: MessagePort;\n streamType?: 'video';\n sessionId?: string;\n clipStartUs?: number;\n clipDurationUs?: number;\n }): Promise<{ success: boolean }> {\n const { port, direction, sessionId } = payload;\n\n if (direction === 'upstream') {\n this.upstreamPort = port;\n this.clipId = sessionId || 'default';\n\n const channel = new WorkerChannel(port, {\n name: 'Demux-VideoDecode',\n timeout: 30000,\n });\n\n channel.receiveStream((stream, metadata) => {\n this.handleReceiveStream(stream, {\n ...metadata,\n clipStartUs: payload.clipStartUs,\n clipDurationUs: payload.clipDurationUs,\n });\n });\n\n channel.registerHandler('configure', this.handleConfigure.bind(this));\n }\n\n if (direction === 'downstream') {\n this.downstreamPort = port;\n }\n\n return { success: true };\n }\n\n private async handleConfigure(payload: {\n config?: { video?: Partial<VideoDecoderConfig> };\n sessionId?: string;\n streamType?: 'video';\n codec?: string;\n width?: number;\n height?: number;\n description?: ArrayBuffer | Uint8Array;\n }): Promise<{ success: boolean }> {\n const { sessionId, streamType, codec, width, height, description, config } = payload;\n\n if (sessionId && streamType === 'video') {\n if (this.decoder) {\n await this.decoder.updateConfig({\n codec,\n width,\n height,\n description: normalizeDescription(description),\n });\n }\n return { success: true };\n }\n\n if (config?.video) {\n Object.assign(this.defaultConfig, config.video);\n\n if (this.decoder) {\n await this.decoder.updateConfig(config.video);\n }\n }\n\n this.channel.state = WorkerState.Ready;\n\n return { success: true };\n }\n\n private async handleReceiveStream(\n stream: ReadableStream,\n metadata?: Record<string, any>\n ): Promise<void> {\n const sessionId = metadata?.sessionId || this.clipId;\n\n if (!this.decoder) {\n this.decoder = new VideoChunkDecoder(sessionId, {\n ...this.defaultConfig,\n codec: metadata?.codec,\n width: metadata?.width,\n height: metadata?.height,\n description: normalizeDescription(metadata?.description),\n });\n }\n\n const transform = this.decoder.createStream();\n\n if (this.downstreamPort) {\n const channel = new WorkerChannel(this.downstreamPort, {\n name: 'VideoDecode-Compose',\n timeout: 30000,\n });\n\n channel.sendStream(transform.readable as ReadableStream, {\n streamType: 'video',\n sessionId,\n });\n\n stream\n .pipeTo(transform.writable)\n .catch((error) =>\n console.error('[VideoDecodeWorker] Video stream pipe error:', sessionId, error)\n );\n }\n }\n\n private async handleFlush(): Promise<{ success: boolean }> {\n if (this.decoder) {\n await this.decoder.flush();\n }\n return { success: true };\n }\n\n private async handleReset(): Promise<{ success: boolean }> {\n if (this.decoder) {\n await this.decoder.reset();\n }\n\n this.channel.notify('reset_complete', {\n type: 'video',\n });\n\n return { success: true };\n }\n\n private async handleGetStats(): Promise<{ video?: any }> {\n if (!this.decoder) {\n return {};\n }\n\n return {\n video: {\n clipId: this.clipId,\n configured: this.decoder.isConfigured,\n queueSize: this.decoder.queueSize,\n state: this.decoder.state,\n },\n };\n }\n\n private async handleDispose(): Promise<{ success: boolean }> {\n if (this.decoder) {\n await this.decoder.close();\n this.decoder = null;\n }\n\n this.upstreamPort?.close();\n this.upstreamPort = null;\n\n this.downstreamPort?.close();\n this.downstreamPort = null;\n\n this.channel.state = WorkerState.Disposed;\n\n return { success: true };\n }\n}\n\nconst worker = new VideoDecodeWorker();\n\nself.addEventListener('beforeunload', () => {\n worker['handleDispose']();\n});\n\nexport default null;\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"video-decode.worker.BIspTxgV.js","sources":["../../../../src/stages/decode/VideoChunkDecoder.ts","../../../../src/stages/decode/video-decode.worker.ts"],"sourcesContent":["import { VideoDecoderConfig } from './types';\nimport { BaseDecoder } from './BaseDecoder';\n\n/**\n * Video decoder with GOP tracking\n * Tracks keyframe boundaries and attaches GOP metadata to decoded frames\n */\nexport class VideoChunkDecoder extends BaseDecoder<\n VideoDecoder,\n VideoDecoderConfig,\n EncodedVideoChunk,\n VideoFrame\n> {\n private static readonly DEFAULT_HIGH_WATER_MARK = 4;\n private static readonly DEFAULT_DECODE_QUEUE_THRESHOLD = 16;\n\n readonly trackId: string;\n\n protected readonly highWaterMark: number;\n protected readonly decodeQueueThreshold: number;\n\n // Buffering support for delayed configuration\n private bufferedChunks: EncodedVideoChunk[] = [];\n private isProcessingBuffer: boolean = false;\n\n constructor(trackId: string, config?: Partial<VideoDecoderConfig>) {\n super((config || {}) as VideoDecoderConfig);\n\n this.trackId = trackId;\n this.highWaterMark =\n config?.backpressure?.highWaterMark ?? VideoChunkDecoder.DEFAULT_HIGH_WATER_MARK;\n this.decodeQueueThreshold =\n config?.backpressure?.decodeQueueThreshold ??\n VideoChunkDecoder.DEFAULT_DECODE_QUEUE_THRESHOLD;\n }\n\n // Computed properties\n get isConfigured(): boolean {\n return this.isReady;\n }\n\n get state(): string {\n return this.decoder?.state || 'unconfigured';\n }\n\n /**\n * Update configuration - can be called before or after initialization\n */\n async updateConfig(config: Partial<VideoDecoderConfig>): Promise<void> {\n if (!this.isReady && config.codec) {\n await this.configure(config as VideoDecoderConfig);\n await this.processBufferedChunks();\n }\n }\n\n // Override createStream to handle GOP tracking and buffering\n // Always create new stream for each clip (ReadableStreams can only be consumed once)\n override createStream(): TransformStream<EncodedVideoChunk, VideoFrame> {\n return new TransformStream<EncodedVideoChunk, VideoFrame>(\n {\n start: async (controller) => {\n this.controller = controller;\n // Don't initialize if no codec config yet\n if (this.config?.codec && this.config?.description && !this.isReady) {\n await this.initialize();\n }\n },\n\n transform: async (chunk) => {\n // If not configured yet, buffer the chunk\n if (!this.isReady) {\n this.bufferedChunks.push(chunk);\n return; // Don't process yet\n }\n\n // If we're processing buffered chunks, add to buffer to maintain order\n if (this.isProcessingBuffer) {\n this.bufferedChunks.push(chunk);\n return;\n }\n\n // Normal processing\n await this.processChunk(chunk);\n },\n\n flush: async () => {\n if (this.isReady) {\n await this.flush();\n }\n },\n },\n {\n highWaterMark: this.highWaterMark,\n size: () => 1,\n }\n );\n }\n\n /**\n * Process a single chunk (extracted from transform for reuse)\n */\n private async processChunk(chunk: EncodedVideoChunk): Promise<void> {\n if (!this.decoder) {\n throw new Error('Decoder not initialized');\n }\n\n if (this.decoder.state !== 'configured') {\n console.error('[VideoChunkDecoder] Decoder in unexpected state:', this.decoder.state);\n throw new Error(`Decoder not configured, state: ${this.decoder.state}`);\n }\n\n // Check decoder queue pressure\n if (this.decoder.decodeQueueSize >= this.decodeQueueThreshold) {\n await new Promise<void>((resolve) => {\n const check = () => {\n if (!this.decoder || this.decoder.decodeQueueSize < this.decodeQueueThreshold - 1) {\n resolve();\n } else {\n setTimeout(check, 20);\n }\n };\n check();\n });\n }\n\n // Decode the chunk\n try {\n this.decode(chunk);\n } catch (error) {\n console.error(`[VideoChunkDecoder] decode error:`, error);\n throw error; // Re-throw to propagate\n }\n }\n\n // Override handleOutput to enqueue decoded frames\n protected override handleOutput(frame: VideoFrame): void {\n super.handleOutput(frame);\n }\n\n // Implement abstract methods\n protected async isConfigSupported(config: VideoDecoderConfig): Promise<{ supported: boolean }> {\n const result = await VideoDecoder.isConfigSupported({\n codec: config.codec,\n codedWidth: config.width,\n codedHeight: config.height,\n });\n return { supported: result.supported ?? false };\n }\n\n protected createDecoder(init: {\n output: (frame: VideoFrame) => void;\n error: (error: DOMException) => void;\n }): VideoDecoder {\n return new VideoDecoder(init);\n }\n\n protected getDecoderType(): string {\n return 'Video';\n }\n\n protected async configureDecoder(config: VideoDecoderConfig): Promise<void> {\n if (!this.decoder) return;\n\n const decoderConfig = {\n codec: config.codec,\n codedWidth: config.width,\n codedHeight: config.height,\n hardwareAcceleration: config.hardwareAcceleration || 'no-preference',\n optimizeForLatency: false,\n ...(config.description && { description: config.description }),\n ...(config.displayAspectWidth && { displayAspectWidth: config.displayAspectWidth }),\n ...(config.displayAspectHeight && { displayAspectHeight: config.displayAspectHeight }),\n };\n\n this.decoder.configure(decoderConfig as any);\n // console.log('[VideoChunkDecoder] Decoder configured, state:', this.decoder.state);\n }\n\n protected decode(chunk: EncodedVideoChunk): void {\n this.decoder?.decode(chunk);\n }\n\n /**\n * Configure the decoder with codec info (can be called after creation)\n */\n async configure(config: VideoDecoderConfig): Promise<void> {\n // console.log('[VideoChunkDecoder] Configuring with:', config);\n\n if (this.isReady) {\n // If already configured, reconfigure\n await this.reconfigure(config);\n return;\n }\n\n this.config = config as any;\n\n // Initialize decoder with new config\n await this.initialize();\n }\n\n /**\n * Process any buffered chunks after configuration\n */\n async processBufferedChunks(): Promise<void> {\n if (!this.isReady || this.bufferedChunks.length === 0) {\n return;\n }\n\n // console.log('[VideoChunkDecoder] Processing', this.bufferedChunks.length, 'buffered chunks');\n this.isProcessingBuffer = true;\n\n // Process buffered chunks in order\n const chunks = [...this.bufferedChunks];\n this.bufferedChunks = [];\n\n for (const chunk of chunks) {\n try {\n await this.processChunk(chunk);\n } catch (error) {\n console.error('[VideoChunkDecoder] Error processing buffered chunk:', error);\n }\n }\n\n this.isProcessingBuffer = false;\n\n // Process any new chunks that arrived while processing buffer\n if (this.bufferedChunks.length > 0) {\n await this.processBufferedChunks();\n }\n }\n\n // Override close to clean up buffered chunks\n override async close(): Promise<void> {\n // Clear buffered chunks\n this.bufferedChunks = [];\n\n // Call parent close\n await super.close();\n }\n}\n","import { WorkerChannel } from '../../worker/WorkerChannel';\nimport { WorkerMessageType, WorkerState } from '../../worker/types';\nimport { VideoChunkDecoder } from './VideoChunkDecoder';\nimport { VideoDecoderConfig } from './types';\n\nconst normalizeDescription = (desc?: ArrayBuffer | ArrayBufferView): ArrayBuffer | undefined => {\n if (!desc) return undefined;\n\n if (desc instanceof ArrayBuffer) return desc;\n\n const view = desc as ArrayBufferView;\n return view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength) as ArrayBuffer;\n};\n\n/**\n * VideoDecodeWorker (Clip Local) - Decodes video for a single clip\n * Receives encoded video chunks from VideoDemuxWorker and outputs decoded frames\n *\n * Pipeline: VideoDemuxWorker → VideoDecodeWorker → VideoComposeWorker\n *\n * Features:\n * - Single clip, single VideoDecoder instance (no routing)\n * - GOP-based decoding with metadata\n * - Stream-based processing with backpressure\n * - Lifecycle tied to clip pipeline\n */\nexport class VideoDecodeWorker {\n private channel: WorkerChannel;\n private decoder: VideoChunkDecoder | null = null;\n private clipId: string = '';\n\n private defaultConfig: Partial<VideoDecoderConfig> = {};\n\n private upstreamPort: MessagePort | null = null;\n private downstreamPort: MessagePort | null = null;\n\n constructor() {\n this.channel = new WorkerChannel(self as any, {\n name: 'VideoDecodeWorker',\n timeout: 30000,\n });\n\n this.setupHandlers();\n }\n\n private setupHandlers(): void {\n this.channel.registerHandler('configure', this.handleConfigure.bind(this));\n this.channel.registerHandler('connect', this.handleConnect.bind(this));\n this.channel.registerHandler('flush', this.handleFlush.bind(this));\n this.channel.registerHandler('reset', this.handleReset.bind(this));\n this.channel.registerHandler('get_stats', this.handleGetStats.bind(this));\n this.channel.registerHandler(WorkerMessageType.Dispose, this.handleDispose.bind(this));\n }\n\n private async handleConnect(payload: {\n direction: 'upstream' | 'downstream';\n port: MessagePort;\n streamType?: 'video';\n sessionId?: string;\n clipStartUs?: number;\n clipDurationUs?: number;\n }): Promise<{ success: boolean }> {\n const { port, direction, sessionId } = payload;\n\n if (direction === 'upstream') {\n this.upstreamPort = port;\n this.clipId = sessionId || 'default';\n\n const channel = new WorkerChannel(port, {\n name: 'Demux-VideoDecode',\n timeout: 30000,\n });\n\n channel.receiveStream((stream, metadata) => {\n this.handleReceiveStream(stream, {\n ...metadata,\n clipStartUs: payload.clipStartUs,\n clipDurationUs: payload.clipDurationUs,\n });\n });\n\n channel.registerHandler('configure', this.handleConfigure.bind(this));\n }\n\n if (direction === 'downstream') {\n this.downstreamPort = port;\n }\n\n return { success: true };\n }\n\n private async handleConfigure(payload: {\n config?: { video?: Partial<VideoDecoderConfig> };\n sessionId?: string;\n streamType?: 'video';\n codec?: string;\n width?: number;\n height?: number;\n description?: ArrayBuffer | Uint8Array;\n }): Promise<{ success: boolean }> {\n const { sessionId, streamType, codec, width, height, description, config } = payload;\n\n if (sessionId && streamType === 'video') {\n if (this.decoder) {\n await this.decoder.updateConfig({\n codec,\n width,\n height,\n description: normalizeDescription(description),\n });\n }\n return { success: true };\n }\n\n if (config?.video) {\n Object.assign(this.defaultConfig, config.video);\n\n if (this.decoder) {\n await this.decoder.updateConfig(config.video);\n }\n }\n\n this.channel.state = WorkerState.Ready;\n\n return { success: true };\n }\n\n private async handleReceiveStream(\n stream: ReadableStream,\n metadata?: Record<string, any>\n ): Promise<void> {\n const sessionId = metadata?.sessionId || this.clipId;\n\n if (!this.decoder) {\n this.decoder = new VideoChunkDecoder(sessionId, {\n ...this.defaultConfig,\n codec: metadata?.codec,\n width: metadata?.width,\n height: metadata?.height,\n description: normalizeDescription(metadata?.description),\n });\n }\n\n const transform = this.decoder.createStream();\n\n if (this.downstreamPort) {\n const channel = new WorkerChannel(this.downstreamPort, {\n name: 'VideoDecode-Compose',\n timeout: 30000,\n });\n\n channel.sendStream(transform.readable as ReadableStream, {\n streamType: 'video',\n sessionId,\n });\n\n stream\n .pipeTo(transform.writable)\n .catch((error) =>\n console.error('[VideoDecodeWorker] Video stream pipe error:', sessionId, error)\n );\n }\n }\n\n private async handleFlush(): Promise<{ success: boolean }> {\n if (this.decoder) {\n await this.decoder.flush();\n }\n return { success: true };\n }\n\n private async handleReset(): Promise<{ success: boolean }> {\n if (this.decoder) {\n await this.decoder.reset();\n }\n\n this.channel.notify('reset_complete', {\n type: 'video',\n });\n\n return { success: true };\n }\n\n private async handleGetStats(): Promise<{ video?: any }> {\n if (!this.decoder) {\n return {};\n }\n\n return {\n video: {\n clipId: this.clipId,\n configured: this.decoder.isConfigured,\n queueSize: this.decoder.queueSize,\n state: this.decoder.state,\n },\n };\n }\n\n private async handleDispose(): Promise<{ success: boolean }> {\n if (this.decoder) {\n await this.decoder.close();\n this.decoder = null;\n }\n\n this.upstreamPort?.close();\n this.upstreamPort = null;\n\n this.downstreamPort?.close();\n this.downstreamPort = null;\n\n this.channel.state = WorkerState.Disposed;\n\n return { success: true };\n }\n}\n\nconst worker = new VideoDecodeWorker();\n\nself.addEventListener('beforeunload', () => {\n worker['handleDispose']();\n});\n\nexport default null;\n"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { W as WorkerChannel, a as WorkerMessageType, b as WorkerState } from "../../WorkerChannel.CE5euh3R.js";
2
- import { M as MP4Demuxer } from "../../MP4Demuxer.CFHDkPYc.js";
2
+ import { M as MP4Demuxer } from "../../MP4Demuxer.BEa6PLJm.js";
3
3
  const BITRATE_TABLE = {
4
4
  "1-3": [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0],
5
5
  "1-2": [0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0],
@@ -499,4 +499,4 @@ export {
499
499
  AudioDemuxWorker,
500
500
  audioDemux_worker as default
501
501
  };
502
- //# sourceMappingURL=audio-demux.worker.BTFPcY7P.js.map
502
+ //# sourceMappingURL=audio-demux.worker._VRQdLdv.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"audio-demux.worker.BTFPcY7P.js","sources":["../../../../src/stages/demux/MP3FrameParser.ts","../../../../src/stages/demux/audio-demux.worker.ts"],"sourcesContent":["const BITRATE_TABLE: Record<string, number[]> = {\n '1-3': [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0],\n '1-2': [0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0],\n '1-1': [0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0],\n '2-3': [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],\n '2-2': [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],\n '2-1': [0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0],\n};\n\nconst SAMPLE_RATE_TABLE: Record<number, number[]> = {\n 1: [44100, 48000, 32000],\n 2: [22050, 24000, 16000],\n 2.5: [11025, 12000, 8000],\n};\n\nexport interface MP3Config {\n codec: 'mp3';\n sampleRate: number;\n channels: number;\n bitrateKbps: number | null;\n samplesPerFrame: number;\n}\n\nexport interface MP3Frame {\n data: Uint8Array;\n timestampUs: number;\n durationUs: number;\n}\n\ninterface ParseResult {\n frames: MP3Frame[];\n config?: MP3Config;\n}\n\nexport class MP3FrameParser {\n private buffer = new Uint8Array(0);\n private config: MP3Config | null = null;\n private timestampUs = 0;\n\n push(chunk: Uint8Array): ParseResult {\n this.appendToBuffer(chunk);\n this.skipId3Headers();\n\n const frames: MP3Frame[] = [];\n let configEmitted: MP3Config | undefined;\n\n let offset = 0;\n while (offset <= this.buffer.length - 4) {\n const header = this.parseHeader(this.buffer, offset);\n if (!header) {\n offset += 1;\n continue;\n }\n\n if (offset + header.frameSize > this.buffer.length) {\n break;\n }\n\n const frameBytes = this.buffer.slice(offset, offset + header.frameSize);\n const durationUs = Math.round((header.samplesPerFrame * 1_000_000) / header.sampleRate);\n\n if (!this.config) {\n this.config = {\n codec: 'mp3',\n sampleRate: header.sampleRate,\n channels: header.channels,\n bitrateKbps: header.bitrateKbps,\n samplesPerFrame: header.samplesPerFrame,\n };\n configEmitted = this.config;\n }\n\n frames.push({\n data: frameBytes,\n timestampUs: this.timestampUs,\n durationUs,\n });\n\n this.timestampUs += durationUs;\n offset += header.frameSize;\n }\n\n if (offset > 0) {\n this.buffer = this.buffer.slice(offset);\n }\n\n return { frames, config: configEmitted };\n }\n\n flush(): MP3Frame[] {\n const { frames } = this.push(new Uint8Array(0));\n const remainder = this.buffer;\n this.buffer = new Uint8Array(0);\n if (remainder.length) {\n console.warn('[MP3FrameParser] Remaining unparsed bytes:', remainder.length);\n }\n return frames;\n }\n\n private appendToBuffer(chunk: Uint8Array): void {\n if (chunk.length === 0) {\n return;\n }\n const combined = new Uint8Array(this.buffer.length + chunk.length);\n combined.set(this.buffer, 0);\n combined.set(chunk, this.buffer.length);\n this.buffer = combined;\n }\n\n private skipId3Headers(): void {\n let offset = 0;\n while (this.buffer.length - offset >= 10) {\n if (\n this.buffer[offset] === 0x49 &&\n this.buffer[offset + 1] === 0x44 &&\n this.buffer[offset + 2] === 0x33\n ) {\n const size = this.readSynchsafeInteger(this.buffer.subarray(offset + 6, offset + 10));\n const total = size + 10;\n if (offset + total <= this.buffer.length) {\n offset += total;\n continue;\n }\n }\n break;\n }\n if (offset > 0) {\n this.buffer = this.buffer.slice(offset);\n }\n }\n\n private parseHeader(\n buffer: Uint8Array,\n offset: number\n ): {\n frameSize: number;\n sampleRate: number;\n channels: number;\n bitrateKbps: number | null;\n samplesPerFrame: number;\n } | null {\n if (offset + 3 >= buffer.length) {\n return null;\n }\n if (buffer[offset] !== 0xff || ((buffer[offset + 1] ?? 0) & 0xe0) !== 0xe0) {\n return null;\n }\n\n const versionBits = ((buffer[offset + 1] ?? 0) >> 3) & 0x03;\n const layerBits = ((buffer[offset + 1] ?? 0) >> 1) & 0x03;\n const bitrateIndex = ((buffer[offset + 2] ?? 0) >> 4) & 0x0f;\n const sampleRateIndex = ((buffer[offset + 2] ?? 0) >> 2) & 0x03;\n const paddingBit = ((buffer[offset + 2] ?? 0) >> 1) & 0x01;\n const channelMode = ((buffer[offset + 3] ?? 0) >> 6) & 0x03;\n\n const version = this.getVersion(versionBits);\n const layer = this.getLayer(layerBits);\n if (!version || !layer) {\n return null;\n }\n\n const sampleRate = SAMPLE_RATE_TABLE[version]?.[sampleRateIndex] ?? null;\n if (!sampleRate) {\n return null;\n }\n\n const bitrateKey = `${version === 1 ? 1 : 2}-${layer}`;\n const bitrateKbps = BITRATE_TABLE[bitrateKey]?.[bitrateIndex] ?? null;\n if (bitrateKbps === null) {\n return null;\n }\n\n const samplesPerFrame = this.getSamplesPerFrame(version, layer);\n const frameSize = this.calculateFrameSize(layer, bitrateKbps, sampleRate, paddingBit);\n if (!frameSize || frameSize < 24) {\n return null;\n }\n\n const channels = channelMode === 3 ? 1 : 2;\n\n return {\n frameSize,\n sampleRate,\n channels,\n bitrateKbps: bitrateKbps || null,\n samplesPerFrame,\n };\n }\n\n private getVersion(bits: number): 1 | 2 | 2.5 | null {\n switch (bits) {\n case 0b11:\n return 1;\n case 0b10:\n return 2;\n case 0b00:\n return 2.5;\n default:\n return null;\n }\n }\n\n private getLayer(bits: number): 1 | 2 | 3 | null {\n switch (bits) {\n case 0b11:\n return 1;\n case 0b10:\n return 2;\n case 0b01:\n return 3;\n default:\n return null;\n }\n }\n\n private getSamplesPerFrame(version: 1 | 2 | 2.5, layer: 1 | 2 | 3): number {\n if (layer === 1) {\n return 384;\n }\n if (layer === 2) {\n return 1152;\n }\n return version === 1 ? 1152 : 576;\n }\n\n private calculateFrameSize(\n layer: 1 | 2 | 3,\n bitrateKbps: number,\n sampleRate: number,\n padding: number\n ): number {\n if (bitrateKbps <= 0) {\n return 0;\n }\n\n if (layer === 1) {\n return ((12 * bitrateKbps * 1000) / sampleRate + padding) * 4;\n }\n\n return Math.floor((144 * bitrateKbps * 1000) / sampleRate + padding);\n }\n\n private readSynchsafeInteger(bytes: Uint8Array): number {\n if (bytes.length !== 4) {\n return 0;\n }\n return (\n (((bytes[0] ?? 0) & 0x7f) << 21) |\n (((bytes[1] ?? 0) & 0x7f) << 14) |\n (((bytes[2] ?? 0) & 0x7f) << 7) |\n ((bytes[3] ?? 0) & 0x7f)\n );\n }\n}\n","import { WorkerChannel } from '../../worker/WorkerChannel';\nimport { WorkerMessageType, WorkerState } from '../../worker/types';\nimport { MP4Demuxer } from './MP4Demuxer';\nimport { MP3FrameParser, type MP3Config, type MP3Frame } from './MP3FrameParser';\nimport type { DemuxConfig } from './types';\n\n/**\n * AudioDemuxWorker - First stage for audio processing\n * Extracts audio tracks from various container formats (MP3, MP4/M4A, etc.)\n *\n * Pipeline: ResourceLoader (Main Thread) → AudioDemuxWorker → DecodeWorker\n *\n * Features:\n * - Multi-format support (MP3, AAC in MP4/M4A)\n * - Stream-based processing with backpressure\n * - Direct streaming to DecodeWorker\n */\nexport class AudioDemuxWorker {\n private channel: WorkerChannel;\n private demuxer: MP4Demuxer | null = null;\n private audioStream: TransformStream<Uint8Array, EncodedAudioChunk> | null = null;\n private mp3Parser: MP3FrameParser | null = null;\n private currentFormat: 'mp3' | 'mp4' | null = null;\n\n // Connection to decoder worker\n private decoderPort: MessagePort | null = null;\n\n constructor() {\n // Initialize WorkerChannel\n this.channel = new WorkerChannel(self as any, {\n name: 'AudioDemuxWorker',\n timeout: 30000,\n });\n\n this.setupHandlers();\n }\n\n private setupHandlers(): void {\n // Register message handlers\n this.channel.registerHandler('configure', this.handleConfigure.bind(this));\n // Unified stream connect (feature-flagged)\n this.channel.registerHandler('connect' as any, this.handleConnect.bind(this));\n // Unified stream connect only\n this.channel.registerHandler('get_stats', this.handleGetStats.bind(this));\n this.channel.registerHandler(WorkerMessageType.Dispose, this.handleDispose.bind(this));\n\n // Setup stream receiver from ResourceLoader (main thread)\n this.channel.receiveStream(this.handleReceiveStream.bind(this));\n }\n\n /**\n * Unified connect handler used by stream pipeline\n */\n private async handleConnect(payload: {\n direction: 'upstream';\n port: MessagePort;\n streamType: 'video' | 'audio' | 'frame' | 'chunk';\n }): Promise<{ success: boolean }> {\n this.decoderPort = payload.port;\n // Demux connects upstream to decoder\n return { success: true };\n }\n\n /**\n * Configure demuxer with format settings\n * @param payload.config - Demuxer configuration\n * @param payload.initial - If true, initialize worker state; otherwise just update config\n */\n private async handleConfigure(payload: {\n config: DemuxConfig;\n initial?: boolean;\n }): Promise<{ success: boolean; tracks?: any[] }> {\n const { config, initial = false } = payload;\n\n try {\n if (initial) {\n // Initial setup - set worker state to ready\n this.channel.state = WorkerState.Ready;\n\n // Detect format and create appropriate demuxer\n const format = config.container || this.detectFormat(config);\n\n if (format === 'mp3') {\n this.mp3Parser = new MP3FrameParser();\n this.demuxer?.destroy();\n this.demuxer = null;\n this.audioStream = null;\n this.currentFormat = 'mp3';\n\n const mp3Track = { id: 1, type: 'audio', codec: 'mp3' };\n this.channel.notify('configured', {\n tracks: [mp3Track],\n format,\n codec: 'mp3',\n });\n\n return { success: true, tracks: [mp3Track] };\n } else if (format === 'mp4' || format === 'm4a') {\n if (this.demuxer) {\n this.demuxer.destroy();\n }\n\n this.demuxer = new MP4Demuxer({\n ...config,\n skipVideo: true, // Audio only\n });\n this.audioStream = this.demuxer.createAudioStream();\n this.mp3Parser = null;\n this.currentFormat = 'mp4';\n } else {\n throw {\n code: 'UNSUPPORTED_FORMAT',\n message: `Unsupported audio format: ${format}`,\n };\n }\n\n if (!this.audioStream) {\n throw {\n code: 'NO_AUDIO_TRACK',\n message: 'No audio track found in container',\n };\n }\n\n const tracks = Array.from(this.demuxer.tracks.values());\n\n // Notify configuration complete\n this.channel.notify('configured', {\n tracks,\n format,\n codec: tracks[0]?.codec,\n });\n\n return { success: true, tracks };\n } else {\n // Update configuration only (e.g., backpressure settings)\n if (!this.demuxer) {\n throw {\n code: 'NOT_INITIALIZED',\n message: 'Demuxer not initialized. Call configure with initial=true first',\n };\n }\n\n // Demuxers don't support runtime config updates\n // Would need initial=true for changes\n\n return { success: true };\n }\n } catch (error: any) {\n throw {\n code: error.code || 'CONFIG_ERROR',\n message: error.message,\n };\n }\n }\n\n /**\n * Detect audio format from configuration\n */\n private detectFormat(config: DemuxConfig): string {\n // Simple format detection based on codec or file extension\n if (config.codec?.includes('mp3')) return 'mp3';\n if (config.codec?.includes('aac')) return 'mp4';\n if (config.codec?.includes('opus')) return 'webm';\n\n return 'mp3';\n }\n\n /**\n * Handle input stream from ResourceLoader (main thread)\n */\n private async handleReceiveStream(\n stream: ReadableStream<Uint8Array>,\n metadata?: Record<string, any>\n ): Promise<void> {\n // Initialize demuxer if not configured\n if (this.currentFormat === 'mp3' && !this.mp3Parser) {\n this.mp3Parser = new MP3FrameParser();\n }\n\n if (this.currentFormat === 'mp4' && (!this.demuxer || !this.audioStream)) {\n await this.handleConfigure({\n config: { container: 'mp4', highWaterMark: 10 },\n initial: true,\n }).catch((error) => {\n console.error('[AudioDemuxWorker] Configure error:', metadata?.sessionId, error);\n return;\n });\n }\n\n if (!this.decoderPort) {\n console.error('[AudioDemuxWorker] Decoder not connected');\n return;\n }\n\n // Setup channel to decoder\n const decoderChannel = new WorkerChannel(this.decoderPort, {\n name: 'AudioDemux-Decoder',\n timeout: 30000,\n });\n\n if (this.currentFormat === 'mp3') {\n await this.pipeMp3Stream(stream, decoderChannel, metadata).catch((error) => {\n console.error('[AudioDemuxWorker] MP3 stream error:', metadata?.sessionId, error);\n });\n return;\n }\n\n if (!this.demuxer || !this.audioStream) {\n console.error('[AudioDemuxWorker] Audio demuxer not initialized');\n return;\n }\n\n const transformed = stream.pipeThrough(this.audioStream);\n const trackInfo = Array.from(this.demuxer.tracks.values())[0];\n\n await decoderChannel\n .sendStream(transformed, {\n streamType: 'audio',\n sessionId: metadata?.sessionId ?? 'default',\n trackId: metadata?.trackId ?? 'main',\n codec: trackInfo?.codec ?? 'mp4a.40.2',\n sampleRate: trackInfo?.sampleRate ?? 44_100,\n numberOfChannels: trackInfo?.numberOfChannels ?? 2,\n description: trackInfo?.description,\n })\n .catch((error) => {\n console.error('[AudioDemuxWorker] Send stream error:', metadata?.sessionId, error);\n });\n\n this.channel.notify('demux_complete', {\n tracksProcessed: this.demuxer?.tracks.size || 0,\n });\n }\n\n private async pipeMp3Stream(\n stream: ReadableStream<Uint8Array>,\n decoderChannel: WorkerChannel,\n metadata?: Record<string, any>\n ): Promise<void> {\n if (!this.mp3Parser) {\n this.mp3Parser = new MP3FrameParser();\n }\n\n const reader = stream.getReader();\n let currentConfig: MP3Config | null = null;\n let configured = false;\n const bufferedFrames: MP3Frame[] = [];\n let writer: WritableStreamDefaultWriter<EncodedAudioChunk> | null = null;\n\n const ensureWriter = async (): Promise<WritableStreamDefaultWriter<EncodedAudioChunk>> => {\n if (!writer) {\n const cfg = currentConfig;\n if (!cfg) {\n throw new Error('MP3 config missing while creating writer');\n }\n const transform = new TransformStream<EncodedAudioChunk, EncodedAudioChunk>();\n writer = transform.writable.getWriter();\n await decoderChannel.sendStream(transform.readable, {\n streamType: 'audio',\n sessionId: metadata?.sessionId ?? 'default',\n trackId: metadata?.trackId ?? 'main',\n codec: 'mp3',\n sampleRate: cfg.sampleRate,\n numberOfChannels: cfg.channels,\n description: undefined,\n });\n }\n return writer!;\n };\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n\n if (!value) {\n continue;\n }\n\n const { frames, config } = this.mp3Parser.push(value);\n\n if (config) {\n currentConfig = config;\n }\n\n if (config && !configured) {\n await decoderChannel.send('configure' as any, {\n streamType: 'audio',\n clipId: metadata?.clipId ?? 'default',\n codec: 'mp3',\n sampleRate: config.sampleRate,\n numberOfChannels: config.channels,\n description: undefined,\n });\n this.channel.notify('demux_configured', {\n clipId: metadata?.clipId ?? 'default',\n codec: 'mp3',\n sampleRate: config.sampleRate,\n numberOfChannels: config.channels,\n });\n configured = true;\n }\n\n if (config) {\n this.currentFormat = 'mp3';\n }\n\n if (!configured) {\n bufferedFrames.push(...frames);\n continue;\n }\n\n const targetWriter = await ensureWriter();\n const readyFrames = bufferedFrames.splice(0, bufferedFrames.length);\n readyFrames.push(...frames);\n for (const frame of readyFrames) {\n const chunk = new EncodedAudioChunk({\n type: 'key',\n timestamp: frame.timestampUs,\n duration: frame.durationUs,\n data: frame.data.buffer.slice(\n frame.data.byteOffset,\n frame.data.byteOffset + frame.data.byteLength\n ),\n });\n await targetWriter.write(chunk);\n }\n }\n\n const remaining = this.mp3Parser.flush();\n if (remaining.length) {\n if (!configured) {\n throw new Error('MP3 stream ended before configuration');\n }\n const targetWriter = await ensureWriter();\n for (const frame of remaining) {\n const chunk = new EncodedAudioChunk({\n type: 'key',\n timestamp: frame.timestampUs,\n duration: frame.durationUs,\n data: frame.data.buffer.slice(\n frame.data.byteOffset,\n frame.data.byteOffset + frame.data.byteLength\n ),\n });\n await targetWriter.write(chunk);\n }\n }\n\n if (writer) {\n await (writer as WritableStreamDefaultWriter<EncodedAudioChunk>).close();\n }\n\n this.channel.notify('demux_complete', {\n tracksProcessed: 1,\n });\n } finally {\n reader.releaseLock();\n }\n }\n\n /**\n * Get demuxer statistics\n */\n private async handleGetStats(): Promise<{\n queueSize?: number;\n tracksInfo?: any[];\n format?: string;\n state?: WorkerState;\n }> {\n if (this.currentFormat === 'mp3') {\n return {\n tracksInfo: this.mp3Parser ? [{ id: 1, type: 'audio', codec: 'mp3' }] : [],\n format: 'mp3',\n state: this.channel.state,\n };\n }\n\n if (this.demuxer) {\n return {\n tracksInfo: Array.from(this.demuxer.tracks.values()),\n format: 'mp4',\n state: this.channel.state,\n };\n }\n\n return { state: this.channel.state };\n }\n\n /**\n * Dispose worker and cleanup resources\n */\n private async handleDispose(): Promise<{ success: boolean }> {\n if (this.demuxer) {\n this.demuxer.destroy();\n }\n this.demuxer = null;\n this.audioStream = null;\n this.mp3Parser = null;\n this.currentFormat = null;\n\n this.decoderPort?.close();\n this.decoderPort = null;\n\n this.channel.state = WorkerState.Disposed;\n\n return { success: true };\n }\n}\n\n// Initialize worker\nconst worker = new AudioDemuxWorker();\n\n// Handle worker termination\nself.addEventListener('beforeunload', () => {\n worker['handleDispose']();\n});\n\nexport default null; // Required for TypeScript worker compilation\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"audio-demux.worker._VRQdLdv.js","sources":["../../../../src/stages/demux/MP3FrameParser.ts","../../../../src/stages/demux/audio-demux.worker.ts"],"sourcesContent":["const BITRATE_TABLE: Record<string, number[]> = {\n '1-3': [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0],\n '1-2': [0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0],\n '1-1': [0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0],\n '2-3': [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],\n '2-2': [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],\n '2-1': [0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0],\n};\n\nconst SAMPLE_RATE_TABLE: Record<number, number[]> = {\n 1: [44100, 48000, 32000],\n 2: [22050, 24000, 16000],\n 2.5: [11025, 12000, 8000],\n};\n\nexport interface MP3Config {\n codec: 'mp3';\n sampleRate: number;\n channels: number;\n bitrateKbps: number | null;\n samplesPerFrame: number;\n}\n\nexport interface MP3Frame {\n data: Uint8Array;\n timestampUs: number;\n durationUs: number;\n}\n\ninterface ParseResult {\n frames: MP3Frame[];\n config?: MP3Config;\n}\n\nexport class MP3FrameParser {\n private buffer = new Uint8Array(0);\n private config: MP3Config | null = null;\n private timestampUs = 0;\n\n push(chunk: Uint8Array): ParseResult {\n this.appendToBuffer(chunk);\n this.skipId3Headers();\n\n const frames: MP3Frame[] = [];\n let configEmitted: MP3Config | undefined;\n\n let offset = 0;\n while (offset <= this.buffer.length - 4) {\n const header = this.parseHeader(this.buffer, offset);\n if (!header) {\n offset += 1;\n continue;\n }\n\n if (offset + header.frameSize > this.buffer.length) {\n break;\n }\n\n const frameBytes = this.buffer.slice(offset, offset + header.frameSize);\n const durationUs = Math.round((header.samplesPerFrame * 1_000_000) / header.sampleRate);\n\n if (!this.config) {\n this.config = {\n codec: 'mp3',\n sampleRate: header.sampleRate,\n channels: header.channels,\n bitrateKbps: header.bitrateKbps,\n samplesPerFrame: header.samplesPerFrame,\n };\n configEmitted = this.config;\n }\n\n frames.push({\n data: frameBytes,\n timestampUs: this.timestampUs,\n durationUs,\n });\n\n this.timestampUs += durationUs;\n offset += header.frameSize;\n }\n\n if (offset > 0) {\n this.buffer = this.buffer.slice(offset);\n }\n\n return { frames, config: configEmitted };\n }\n\n flush(): MP3Frame[] {\n const { frames } = this.push(new Uint8Array(0));\n const remainder = this.buffer;\n this.buffer = new Uint8Array(0);\n if (remainder.length) {\n console.warn('[MP3FrameParser] Remaining unparsed bytes:', remainder.length);\n }\n return frames;\n }\n\n private appendToBuffer(chunk: Uint8Array): void {\n if (chunk.length === 0) {\n return;\n }\n const combined = new Uint8Array(this.buffer.length + chunk.length);\n combined.set(this.buffer, 0);\n combined.set(chunk, this.buffer.length);\n this.buffer = combined;\n }\n\n private skipId3Headers(): void {\n let offset = 0;\n while (this.buffer.length - offset >= 10) {\n if (\n this.buffer[offset] === 0x49 &&\n this.buffer[offset + 1] === 0x44 &&\n this.buffer[offset + 2] === 0x33\n ) {\n const size = this.readSynchsafeInteger(this.buffer.subarray(offset + 6, offset + 10));\n const total = size + 10;\n if (offset + total <= this.buffer.length) {\n offset += total;\n continue;\n }\n }\n break;\n }\n if (offset > 0) {\n this.buffer = this.buffer.slice(offset);\n }\n }\n\n private parseHeader(\n buffer: Uint8Array,\n offset: number\n ): {\n frameSize: number;\n sampleRate: number;\n channels: number;\n bitrateKbps: number | null;\n samplesPerFrame: number;\n } | null {\n if (offset + 3 >= buffer.length) {\n return null;\n }\n if (buffer[offset] !== 0xff || ((buffer[offset + 1] ?? 0) & 0xe0) !== 0xe0) {\n return null;\n }\n\n const versionBits = ((buffer[offset + 1] ?? 0) >> 3) & 0x03;\n const layerBits = ((buffer[offset + 1] ?? 0) >> 1) & 0x03;\n const bitrateIndex = ((buffer[offset + 2] ?? 0) >> 4) & 0x0f;\n const sampleRateIndex = ((buffer[offset + 2] ?? 0) >> 2) & 0x03;\n const paddingBit = ((buffer[offset + 2] ?? 0) >> 1) & 0x01;\n const channelMode = ((buffer[offset + 3] ?? 0) >> 6) & 0x03;\n\n const version = this.getVersion(versionBits);\n const layer = this.getLayer(layerBits);\n if (!version || !layer) {\n return null;\n }\n\n const sampleRate = SAMPLE_RATE_TABLE[version]?.[sampleRateIndex] ?? null;\n if (!sampleRate) {\n return null;\n }\n\n const bitrateKey = `${version === 1 ? 1 : 2}-${layer}`;\n const bitrateKbps = BITRATE_TABLE[bitrateKey]?.[bitrateIndex] ?? null;\n if (bitrateKbps === null) {\n return null;\n }\n\n const samplesPerFrame = this.getSamplesPerFrame(version, layer);\n const frameSize = this.calculateFrameSize(layer, bitrateKbps, sampleRate, paddingBit);\n if (!frameSize || frameSize < 24) {\n return null;\n }\n\n const channels = channelMode === 3 ? 1 : 2;\n\n return {\n frameSize,\n sampleRate,\n channels,\n bitrateKbps: bitrateKbps || null,\n samplesPerFrame,\n };\n }\n\n private getVersion(bits: number): 1 | 2 | 2.5 | null {\n switch (bits) {\n case 0b11:\n return 1;\n case 0b10:\n return 2;\n case 0b00:\n return 2.5;\n default:\n return null;\n }\n }\n\n private getLayer(bits: number): 1 | 2 | 3 | null {\n switch (bits) {\n case 0b11:\n return 1;\n case 0b10:\n return 2;\n case 0b01:\n return 3;\n default:\n return null;\n }\n }\n\n private getSamplesPerFrame(version: 1 | 2 | 2.5, layer: 1 | 2 | 3): number {\n if (layer === 1) {\n return 384;\n }\n if (layer === 2) {\n return 1152;\n }\n return version === 1 ? 1152 : 576;\n }\n\n private calculateFrameSize(\n layer: 1 | 2 | 3,\n bitrateKbps: number,\n sampleRate: number,\n padding: number\n ): number {\n if (bitrateKbps <= 0) {\n return 0;\n }\n\n if (layer === 1) {\n return ((12 * bitrateKbps * 1000) / sampleRate + padding) * 4;\n }\n\n return Math.floor((144 * bitrateKbps * 1000) / sampleRate + padding);\n }\n\n private readSynchsafeInteger(bytes: Uint8Array): number {\n if (bytes.length !== 4) {\n return 0;\n }\n return (\n (((bytes[0] ?? 0) & 0x7f) << 21) |\n (((bytes[1] ?? 0) & 0x7f) << 14) |\n (((bytes[2] ?? 0) & 0x7f) << 7) |\n ((bytes[3] ?? 0) & 0x7f)\n );\n }\n}\n","import { WorkerChannel } from '../../worker/WorkerChannel';\nimport { WorkerMessageType, WorkerState } from '../../worker/types';\nimport { MP4Demuxer } from './MP4Demuxer';\nimport { MP3FrameParser, type MP3Config, type MP3Frame } from './MP3FrameParser';\nimport type { DemuxConfig } from './types';\n\n/**\n * AudioDemuxWorker - First stage for audio processing\n * Extracts audio tracks from various container formats (MP3, MP4/M4A, etc.)\n *\n * Pipeline: ResourceLoader (Main Thread) → AudioDemuxWorker → DecodeWorker\n *\n * Features:\n * - Multi-format support (MP3, AAC in MP4/M4A)\n * - Stream-based processing with backpressure\n * - Direct streaming to DecodeWorker\n */\nexport class AudioDemuxWorker {\n private channel: WorkerChannel;\n private demuxer: MP4Demuxer | null = null;\n private audioStream: TransformStream<Uint8Array, EncodedAudioChunk> | null = null;\n private mp3Parser: MP3FrameParser | null = null;\n private currentFormat: 'mp3' | 'mp4' | null = null;\n\n // Connection to decoder worker\n private decoderPort: MessagePort | null = null;\n\n constructor() {\n // Initialize WorkerChannel\n this.channel = new WorkerChannel(self as any, {\n name: 'AudioDemuxWorker',\n timeout: 30000,\n });\n\n this.setupHandlers();\n }\n\n private setupHandlers(): void {\n // Register message handlers\n this.channel.registerHandler('configure', this.handleConfigure.bind(this));\n // Unified stream connect (feature-flagged)\n this.channel.registerHandler('connect' as any, this.handleConnect.bind(this));\n // Unified stream connect only\n this.channel.registerHandler('get_stats', this.handleGetStats.bind(this));\n this.channel.registerHandler(WorkerMessageType.Dispose, this.handleDispose.bind(this));\n\n // Setup stream receiver from ResourceLoader (main thread)\n this.channel.receiveStream(this.handleReceiveStream.bind(this));\n }\n\n /**\n * Unified connect handler used by stream pipeline\n */\n private async handleConnect(payload: {\n direction: 'upstream';\n port: MessagePort;\n streamType: 'video' | 'audio' | 'frame' | 'chunk';\n }): Promise<{ success: boolean }> {\n this.decoderPort = payload.port;\n // Demux connects upstream to decoder\n return { success: true };\n }\n\n /**\n * Configure demuxer with format settings\n * @param payload.config - Demuxer configuration\n * @param payload.initial - If true, initialize worker state; otherwise just update config\n */\n private async handleConfigure(payload: {\n config: DemuxConfig;\n initial?: boolean;\n }): Promise<{ success: boolean; tracks?: any[] }> {\n const { config, initial = false } = payload;\n\n try {\n if (initial) {\n // Initial setup - set worker state to ready\n this.channel.state = WorkerState.Ready;\n\n // Detect format and create appropriate demuxer\n const format = config.container || this.detectFormat(config);\n\n if (format === 'mp3') {\n this.mp3Parser = new MP3FrameParser();\n this.demuxer?.destroy();\n this.demuxer = null;\n this.audioStream = null;\n this.currentFormat = 'mp3';\n\n const mp3Track = { id: 1, type: 'audio', codec: 'mp3' };\n this.channel.notify('configured', {\n tracks: [mp3Track],\n format,\n codec: 'mp3',\n });\n\n return { success: true, tracks: [mp3Track] };\n } else if (format === 'mp4' || format === 'm4a') {\n if (this.demuxer) {\n this.demuxer.destroy();\n }\n\n this.demuxer = new MP4Demuxer({\n ...config,\n skipVideo: true, // Audio only\n });\n this.audioStream = this.demuxer.createAudioStream();\n this.mp3Parser = null;\n this.currentFormat = 'mp4';\n } else {\n throw {\n code: 'UNSUPPORTED_FORMAT',\n message: `Unsupported audio format: ${format}`,\n };\n }\n\n if (!this.audioStream) {\n throw {\n code: 'NO_AUDIO_TRACK',\n message: 'No audio track found in container',\n };\n }\n\n const tracks = Array.from(this.demuxer.tracks.values());\n\n // Notify configuration complete\n this.channel.notify('configured', {\n tracks,\n format,\n codec: tracks[0]?.codec,\n });\n\n return { success: true, tracks };\n } else {\n // Update configuration only (e.g., backpressure settings)\n if (!this.demuxer) {\n throw {\n code: 'NOT_INITIALIZED',\n message: 'Demuxer not initialized. Call configure with initial=true first',\n };\n }\n\n // Demuxers don't support runtime config updates\n // Would need initial=true for changes\n\n return { success: true };\n }\n } catch (error: any) {\n throw {\n code: error.code || 'CONFIG_ERROR',\n message: error.message,\n };\n }\n }\n\n /**\n * Detect audio format from configuration\n */\n private detectFormat(config: DemuxConfig): string {\n // Simple format detection based on codec or file extension\n if (config.codec?.includes('mp3')) return 'mp3';\n if (config.codec?.includes('aac')) return 'mp4';\n if (config.codec?.includes('opus')) return 'webm';\n\n return 'mp3';\n }\n\n /**\n * Handle input stream from ResourceLoader (main thread)\n */\n private async handleReceiveStream(\n stream: ReadableStream<Uint8Array>,\n metadata?: Record<string, any>\n ): Promise<void> {\n // Initialize demuxer if not configured\n if (this.currentFormat === 'mp3' && !this.mp3Parser) {\n this.mp3Parser = new MP3FrameParser();\n }\n\n if (this.currentFormat === 'mp4' && (!this.demuxer || !this.audioStream)) {\n await this.handleConfigure({\n config: { container: 'mp4', highWaterMark: 10 },\n initial: true,\n }).catch((error) => {\n console.error('[AudioDemuxWorker] Configure error:', metadata?.sessionId, error);\n return;\n });\n }\n\n if (!this.decoderPort) {\n console.error('[AudioDemuxWorker] Decoder not connected');\n return;\n }\n\n // Setup channel to decoder\n const decoderChannel = new WorkerChannel(this.decoderPort, {\n name: 'AudioDemux-Decoder',\n timeout: 30000,\n });\n\n if (this.currentFormat === 'mp3') {\n await this.pipeMp3Stream(stream, decoderChannel, metadata).catch((error) => {\n console.error('[AudioDemuxWorker] MP3 stream error:', metadata?.sessionId, error);\n });\n return;\n }\n\n if (!this.demuxer || !this.audioStream) {\n console.error('[AudioDemuxWorker] Audio demuxer not initialized');\n return;\n }\n\n const transformed = stream.pipeThrough(this.audioStream);\n const trackInfo = Array.from(this.demuxer.tracks.values())[0];\n\n await decoderChannel\n .sendStream(transformed, {\n streamType: 'audio',\n sessionId: metadata?.sessionId ?? 'default',\n trackId: metadata?.trackId ?? 'main',\n codec: trackInfo?.codec ?? 'mp4a.40.2',\n sampleRate: trackInfo?.sampleRate ?? 44_100,\n numberOfChannels: trackInfo?.numberOfChannels ?? 2,\n description: trackInfo?.description,\n })\n .catch((error) => {\n console.error('[AudioDemuxWorker] Send stream error:', metadata?.sessionId, error);\n });\n\n this.channel.notify('demux_complete', {\n tracksProcessed: this.demuxer?.tracks.size || 0,\n });\n }\n\n private async pipeMp3Stream(\n stream: ReadableStream<Uint8Array>,\n decoderChannel: WorkerChannel,\n metadata?: Record<string, any>\n ): Promise<void> {\n if (!this.mp3Parser) {\n this.mp3Parser = new MP3FrameParser();\n }\n\n const reader = stream.getReader();\n let currentConfig: MP3Config | null = null;\n let configured = false;\n const bufferedFrames: MP3Frame[] = [];\n let writer: WritableStreamDefaultWriter<EncodedAudioChunk> | null = null;\n\n const ensureWriter = async (): Promise<WritableStreamDefaultWriter<EncodedAudioChunk>> => {\n if (!writer) {\n const cfg = currentConfig;\n if (!cfg) {\n throw new Error('MP3 config missing while creating writer');\n }\n const transform = new TransformStream<EncodedAudioChunk, EncodedAudioChunk>();\n writer = transform.writable.getWriter();\n await decoderChannel.sendStream(transform.readable, {\n streamType: 'audio',\n sessionId: metadata?.sessionId ?? 'default',\n trackId: metadata?.trackId ?? 'main',\n codec: 'mp3',\n sampleRate: cfg.sampleRate,\n numberOfChannels: cfg.channels,\n description: undefined,\n });\n }\n return writer!;\n };\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n\n if (!value) {\n continue;\n }\n\n const { frames, config } = this.mp3Parser.push(value);\n\n if (config) {\n currentConfig = config;\n }\n\n if (config && !configured) {\n await decoderChannel.send('configure' as any, {\n streamType: 'audio',\n clipId: metadata?.clipId ?? 'default',\n codec: 'mp3',\n sampleRate: config.sampleRate,\n numberOfChannels: config.channels,\n description: undefined,\n });\n this.channel.notify('demux_configured', {\n clipId: metadata?.clipId ?? 'default',\n codec: 'mp3',\n sampleRate: config.sampleRate,\n numberOfChannels: config.channels,\n });\n configured = true;\n }\n\n if (config) {\n this.currentFormat = 'mp3';\n }\n\n if (!configured) {\n bufferedFrames.push(...frames);\n continue;\n }\n\n const targetWriter = await ensureWriter();\n const readyFrames = bufferedFrames.splice(0, bufferedFrames.length);\n readyFrames.push(...frames);\n for (const frame of readyFrames) {\n const chunk = new EncodedAudioChunk({\n type: 'key',\n timestamp: frame.timestampUs,\n duration: frame.durationUs,\n data: frame.data.buffer.slice(\n frame.data.byteOffset,\n frame.data.byteOffset + frame.data.byteLength\n ),\n });\n await targetWriter.write(chunk);\n }\n }\n\n const remaining = this.mp3Parser.flush();\n if (remaining.length) {\n if (!configured) {\n throw new Error('MP3 stream ended before configuration');\n }\n const targetWriter = await ensureWriter();\n for (const frame of remaining) {\n const chunk = new EncodedAudioChunk({\n type: 'key',\n timestamp: frame.timestampUs,\n duration: frame.durationUs,\n data: frame.data.buffer.slice(\n frame.data.byteOffset,\n frame.data.byteOffset + frame.data.byteLength\n ),\n });\n await targetWriter.write(chunk);\n }\n }\n\n if (writer) {\n await (writer as WritableStreamDefaultWriter<EncodedAudioChunk>).close();\n }\n\n this.channel.notify('demux_complete', {\n tracksProcessed: 1,\n });\n } finally {\n reader.releaseLock();\n }\n }\n\n /**\n * Get demuxer statistics\n */\n private async handleGetStats(): Promise<{\n queueSize?: number;\n tracksInfo?: any[];\n format?: string;\n state?: WorkerState;\n }> {\n if (this.currentFormat === 'mp3') {\n return {\n tracksInfo: this.mp3Parser ? [{ id: 1, type: 'audio', codec: 'mp3' }] : [],\n format: 'mp3',\n state: this.channel.state,\n };\n }\n\n if (this.demuxer) {\n return {\n tracksInfo: Array.from(this.demuxer.tracks.values()),\n format: 'mp4',\n state: this.channel.state,\n };\n }\n\n return { state: this.channel.state };\n }\n\n /**\n * Dispose worker and cleanup resources\n */\n private async handleDispose(): Promise<{ success: boolean }> {\n if (this.demuxer) {\n this.demuxer.destroy();\n }\n this.demuxer = null;\n this.audioStream = null;\n this.mp3Parser = null;\n this.currentFormat = null;\n\n this.decoderPort?.close();\n this.decoderPort = null;\n\n this.channel.state = WorkerState.Disposed;\n\n return { success: true };\n }\n}\n\n// Initialize worker\nconst worker = new AudioDemuxWorker();\n\n// Handle worker termination\nself.addEventListener('beforeunload', () => {\n worker['handleDispose']();\n});\n\nexport default null; // Required for TypeScript worker compilation\n"],"names":[],"mappings":"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"}
@@ -1,12 +1,11 @@
1
1
  import { W as WorkerChannel, a as WorkerMessageType, b as WorkerState } from "../../WorkerChannel.CE5euh3R.js";
2
- import { M as MP4Demuxer } from "../../MP4Demuxer.CFHDkPYc.js";
2
+ import { M as MP4Demuxer } from "../../MP4Demuxer.BEa6PLJm.js";
3
3
  class VideoDemuxWorker {
4
4
  channel;
5
5
  demuxer = null;
6
6
  sessionId = null;
7
7
  videoDownstreamPort = null;
8
8
  audioDownstreamPort = null;
9
- emitAudioToMain = false;
10
9
  constructor() {
11
10
  this.channel = new WorkerChannel(self, {
12
11
  name: "VideoDemuxWorker",
@@ -91,20 +90,6 @@ class VideoDemuxWorker {
91
90
  });
92
91
  return;
93
92
  }
94
- if (this.emitAudioToMain) {
95
- const mapped = audioStream.pipeThrough(
96
- new TransformStream({
97
- transform: (chunk, ctl) => {
98
- const decoderConfig = this.buildAudioDecoderConfig();
99
- ctl.enqueue({ chunk, metadata: { decoderConfig } });
100
- }
101
- })
102
- );
103
- this.channel.sendStream(mapped, {
104
- streamType: "audio",
105
- sessionId: this.sessionId
106
- });
107
- }
108
93
  }
109
94
  /**
110
95
  * Handle connection from orchestrator
@@ -137,7 +122,6 @@ class VideoDemuxWorker {
137
122
  if (this.demuxer) {
138
123
  this.demuxer.destroy();
139
124
  }
140
- this.emitAudioToMain = !!config.emitAudioToMain;
141
125
  this.demuxer = new MP4Demuxer({
142
126
  ...config,
143
127
  onReady: () => this.handleDemuxerReady()
@@ -169,7 +153,7 @@ class VideoDemuxWorker {
169
153
  onReady: () => this.handleDemuxerReady()
170
154
  });
171
155
  }
172
- if (!this.videoDownstreamPort && !this.emitAudioToMain) {
156
+ if (!this.videoDownstreamPort) {
173
157
  console.error("[VideoDemuxWorker] Decoder not connected", this.sessionId);
174
158
  return;
175
159
  }
@@ -223,4 +207,4 @@ export {
223
207
  VideoDemuxWorker,
224
208
  videoDemux_worker as default
225
209
  };
226
- //# sourceMappingURL=video-demux.worker.D_WeHPkt.js.map
210
+ //# sourceMappingURL=video-demux.worker.CSkxGtmx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video-demux.worker.CSkxGtmx.js","sources":["../../../../src/stages/demux/video-demux.worker.ts"],"sourcesContent":["import { WorkerChannel } from '../../worker/WorkerChannel';\nimport { WorkerMessageType, WorkerState } from '../../worker/types';\nimport { MP4Demuxer } from './MP4Demuxer';\nimport type { DemuxConfig } from './types';\n\ninterface LoaderStreamMetadata {\n sessionId?: string;\n byteStart?: number;\n byteEnd?: number;\n}\n/**\n * VideoDemuxWorker - First stage for video processing\n * Extracts video tracks from container formats (MP4, etc.)\n *\n * Pipeline: ResourceLoader (Main Thread) → VideoDemuxWorker → DecodeWorker\n *\n * Architecture Note:\n * - One VideoDemuxWorker instance per CLIP (not per resource)\n * - Multiple clips can share the same resource (different workers, independent processing)\n * - This enables clean 2-Clip strategy lifecycle management\n *\n * Features:\n * - MP4 container demuxing with mp4box.js\n * - Stream-based processing with backpressure\n * - Direct streaming to DecodeWorker\n */\nexport class VideoDemuxWorker {\n private channel: WorkerChannel;\n private demuxer: MP4Demuxer | null = null;\n private sessionId: string | null = null;\n private videoDownstreamPort: MessagePort | null = null;\n private audioDownstreamPort: MessagePort | null = null;\n\n constructor() {\n // Initialize WorkerChannel\n this.channel = new WorkerChannel(self as any, {\n name: 'VideoDemuxWorker',\n timeout: 30000,\n });\n this.setupHandlers();\n }\n\n /* @better-ai.mdc For test visibility */\n protected setupHandlers(): void {\n // Register message handlers\n this.channel.registerHandler('configure', this.handleConfigure.bind(this));\n this.channel.registerHandler('connect', this.handleConnect.bind(this));\n this.channel.registerHandler('get_stats', this.handleGetStats.bind(this));\n this.channel.registerHandler(WorkerMessageType.Dispose, this.handleDispose.bind(this));\n\n // Setup stream receiver from ResourceLoader (main thread)\n this.channel.receiveStream(this.handleReceiveStream.bind(this));\n }\n\n // ===== Helper methods (reduce duplication) =====\n private buildAudioDecoderConfig(): any {\n const info = this.demuxer?.audioTrackInfo;\n if (!info) return undefined;\n return {\n codec: info.codec,\n sampleRate: info.sampleRate,\n numberOfChannels: info.numberOfChannels,\n description: info.description,\n };\n }\n\n private sendVideoConfigure(): void {\n if (!this.videoDownstreamPort || !this.demuxer) return;\n const videoTrackInfo = this.demuxer.videoTrackInfo;\n if (!videoTrackInfo) {\n console.error('[VideoDemuxWorker] No video track found after ready');\n return;\n }\n const downstreamChannel = new WorkerChannel(this.videoDownstreamPort, {\n name: 'VideoDemux-Decoder',\n timeout: 30000,\n });\n downstreamChannel.send('configure' as any, {\n sessionId: this.sessionId,\n streamType: 'video',\n codec: videoTrackInfo.codec,\n width: videoTrackInfo.width,\n height: videoTrackInfo.height,\n description: videoTrackInfo.description,\n });\n }\n\n private sendAudioConfigure(): void {\n if (!this.audioDownstreamPort) return;\n const cfg = this.buildAudioDecoderConfig();\n if (!cfg) return;\n const audioChannel = new WorkerChannel(this.audioDownstreamPort, {\n name: 'VideoDemux-AudioDecoder',\n timeout: 30000,\n });\n audioChannel.send('configure' as any, {\n sessionId: this.sessionId,\n streamType: 'audio',\n ...cfg,\n });\n }\n\n private sendVideoStream(videoStream: ReadableStream<EncodedVideoChunk>): void {\n if (!this.videoDownstreamPort) return;\n const videoChannel = new WorkerChannel(this.videoDownstreamPort, {\n name: 'VideoDemux-Decoder',\n timeout: 30000,\n });\n videoChannel.sendStream(videoStream, {\n streamType: 'video',\n sessionId: this.sessionId,\n });\n }\n\n private sendAudioStream(audioStream: ReadableStream<EncodedAudioChunk> | null): void {\n if (!audioStream) return;\n\n // Prefer decoder when connected; otherwise emit to main when enabled\n if (this.audioDownstreamPort) {\n const audioChannel = new WorkerChannel(this.audioDownstreamPort, {\n name: 'VideoDemux-AudioDecoder',\n timeout: 30000,\n });\n audioChannel.sendStream(audioStream, {\n streamType: 'audio',\n sessionId: this.sessionId,\n });\n return;\n }\n }\n\n /**\n * Handle connection from orchestrator\n */\n private async handleConnect(payload: {\n port?: MessagePort;\n streamType?: string;\n sessionId?: string;\n direction?: string;\n }): Promise<{ success: boolean }> {\n const { port, sessionId, direction, streamType } = payload;\n\n if (!port) {\n return { success: false };\n }\n\n if (direction === 'downstream') {\n if (streamType === 'audio') {\n this.audioDownstreamPort = port;\n } else {\n this.videoDownstreamPort = port;\n }\n }\n\n this.sessionId = sessionId || null;\n return { success: true };\n }\n\n /**\n * Configure demuxer with format settings\n * @param payload.config - Demuxer configuration\n * @param payload.initial - If true, initialize worker state; otherwise just update config\n */\n private async handleConfigure(payload: {\n config: DemuxConfig;\n initial?: boolean;\n }): Promise<{ success: boolean; tracks?: any[] }> {\n const { config, initial = false } = payload;\n\n try {\n if (initial) {\n // Initial setup - set worker state to ready\n this.channel.state = WorkerState.Ready;\n\n // Create new demuxer instance\n if (this.demuxer) {\n this.demuxer.destroy();\n }\n\n this.demuxer = new MP4Demuxer({\n ...config,\n onReady: () => this.handleDemuxerReady(),\n });\n\n // Notify configuration complete\n this.channel.notify('configured');\n\n return { success: true };\n } else {\n // Update configuration only (e.g., backpressure settings)\n this.demuxer?.updateConfig(config);\n return { success: true };\n }\n } catch (error: any) {\n throw {\n code: error.code || 'CONFIG_ERROR',\n message: error.message,\n };\n }\n }\n\n /**\n * Handle input stream from ResourceLoader (main thread)\n * Strategy: Stream immediately, send codec info when ready\n */\n private async handleReceiveStream(\n stream: ReadableStream<Uint8Array | ArrayBuffer>,\n metadata?: LoaderStreamMetadata\n ): Promise<void> {\n // Store sessionId from metadata (only happens once per worker lifecycle)\n // Note: Do NOT override if already set by handleConnect (which has correct sessionId)\n if (!this.sessionId) {\n this.sessionId = metadata?.sessionId || this.sessionId;\n }\n\n // Initialize demuxer on first stream\n if (!this.demuxer) {\n this.demuxer = new MP4Demuxer({\n highWaterMark: 10,\n onReady: () => this.handleDemuxerReady(),\n });\n }\n\n // If no video downstream is connected, we can still proceed when\n // we only need to emit audio to the main thread for L2 caching.\n if (!this.videoDownstreamPort) {\n console.error('[VideoDemuxWorker] Decoder not connected', this.sessionId);\n return;\n }\n\n // Create output streams (readable only)\n const videoStream = this.demuxer.createVideoStream();\n const audioStream = this.demuxer.createAudioStream();\n\n // Send streams\n this.sendVideoStream(videoStream);\n this.sendAudioStream(audioStream);\n\n // Create input stream and pipe source to it (single appendBuffer)\n const inputStream = this.demuxer.createInputStream();\n await stream.pipeTo(inputStream).catch((error) => {\n console.error('[VideoDemuxWorker] Input stream error:', this.sessionId, error);\n });\n }\n\n private handleDemuxerReady(): void {\n if (!this.demuxer) return;\n this.sendVideoConfigure();\n this.sendAudioConfigure();\n }\n\n /**\n * Get demuxer statistics\n */\n private async handleGetStats(): Promise<{\n queueSize?: number;\n tracksInfo?: any[];\n state?: WorkerState;\n }> {\n if (!this.demuxer) {\n return { state: this.channel.state };\n }\n\n return {\n tracksInfo: Array.from(this.demuxer.tracks.values()),\n state: this.channel.state,\n };\n }\n\n /**\n * Dispose worker and cleanup resources\n */\n private async handleDispose(): Promise<{ success: boolean }> {\n // Destroy demuxer\n this.demuxer?.destroy();\n this.demuxer = null;\n this.sessionId = null;\n\n // Close connections\n this.videoDownstreamPort?.close();\n this.videoDownstreamPort = null;\n this.audioDownstreamPort?.close();\n this.audioDownstreamPort = null;\n\n this.channel.state = WorkerState.Disposed;\n\n return { success: true };\n }\n}\n// Initialize worker\nconst worker = new VideoDemuxWorker();\n\n// Handle worker termination\nself.addEventListener('beforeunload', () => {\n worker['handleDispose']();\n});\n\nexport default null; // Required for TypeScript worker compilation\n"],"names":[],"mappings":"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"}
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "audio-compose.worker": "stages/compose/audio-compose.worker.rW63uN6z.js",
3
- "video-compose.worker": "stages/compose/video-compose.worker.M5uomNVr.js",
4
- "audio-decode.worker": "stages/decode/audio-decode.worker.DnS17GD9.js",
5
- "video-decode.worker": "stages/decode/video-decode.worker.BEYsjOXp.js",
6
- "audio-demux.worker": "stages/demux/audio-demux.worker.BTFPcY7P.js",
7
- "video-demux.worker": "stages/demux/video-demux.worker.D_WeHPkt.js",
3
+ "video-compose.worker": "stages/compose/video-compose.worker.DHQ8B105.js",
4
+ "audio-decode.worker": "stages/decode/audio-decode.worker.CP8bXXa4.js",
5
+ "video-decode.worker": "stages/decode/video-decode.worker.BIspTxgV.js",
6
+ "audio-demux.worker": "stages/demux/audio-demux.worker._VRQdLdv.js",
7
+ "video-demux.worker": "stages/demux/video-demux.worker.CSkxGtmx.js",
8
8
  "video-encode.worker": "stages/encode/video-encode.worker.u2o7iXCT.js"
9
9
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meframe/core",
3
- "version": "0.0.28",
3
+ "version": "0.0.30-beta",
4
4
  "description": "Next generation media processing framework based on WebCodecs",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1 +0,0 @@
1
- {"version":3,"file":"IndexedDBStore.js","sources":["../../../src/cache/l2/IndexedDBStore.ts"],"sourcesContent":["import type { ChunkBatch } from './OPFSStore';\n\nexport interface ChunkRecord {\n projectId: string;\n clipId: string;\n track: 'video' | 'audio';\n fileName: string;\n batches: ChunkBatch[];\n lastAccess: number;\n totalBytes: number;\n isComplete: boolean;\n expectedDurationUs?: number;\n metadata?: {\n codec?: string;\n description?: Uint8Array;\n codedWidth?: number;\n codedHeight?: number;\n displayAspectWidth?: number;\n displayAspectHeight?: number;\n colorSpace?: VideoColorSpaceInit;\n hardwareAcceleration?: HardwareAcceleration;\n optimizeForLatency?: boolean;\n sampleRate?: number;\n numberOfChannels?: number;\n };\n}\n\n/**\n * IndexedDB storage for L2 cache metadata\n * Handles all database operations for chunk records\n */\nexport class IndexedDBStore {\n private db: IDBDatabase | null = null;\n private initPromise: Promise<void> | null = null;\n\n async init(): Promise<void> {\n if (this.initPromise) return this.initPromise;\n\n this.initPromise = this.initStorage();\n return this.initPromise;\n }\n\n private async initStorage(): Promise<void> {\n // meframe_cache schema v1.1 (IndexedDB version 2)\n // - Added projectId support for multi-project isolation\n // - Composite key: [projectId, clipId, track]\n const request = indexedDB.open('meframe_cache', 2);\n\n request.onupgradeneeded = (event) => {\n const db = (event.target as IDBOpenDBRequest).result;\n const oldVersion = event.oldVersion;\n\n // Version 1→2: Add projectId support\n if (oldVersion < 2) {\n // Delete old chunks store if exists\n if (db.objectStoreNames.contains('chunks')) {\n db.deleteObjectStore('chunks');\n }\n\n // Create new chunks store with composite key [projectId, clipId, track]\n const store = db.createObjectStore('chunks', {\n keyPath: ['projectId', 'clipId', 'track'],\n });\n store.createIndex('lastAccess', 'lastAccess');\n store.createIndex('projectId', 'projectId'); // New: query by project\n }\n\n // meta store (unchanged)\n if (!db.objectStoreNames.contains('meta')) {\n db.createObjectStore('meta', { keyPath: 'projectId' });\n }\n };\n\n this.db = await new Promise((resolve, reject) => {\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n }\n\n /**\n * Get a chunk record\n */\n async getRecord(projectId: string, clipId: string, track: string): Promise<ChunkRecord | null> {\n if (!this.db) return null;\n\n const tx = this.db.transaction('chunks', 'readonly');\n const store = tx.objectStore('chunks');\n const record = await this.promisifyRequest<ChunkRecord>(store.get([projectId, clipId, track]));\n\n return record || null;\n }\n\n /**\n * Put/update a chunk record\n */\n async putRecord(record: ChunkRecord): Promise<void> {\n if (!this.db) return;\n\n const tx = this.db.transaction('chunks', 'readwrite');\n const store = tx.objectStore('chunks');\n store.put(record);\n\n // Wait for transaction to complete\n await new Promise<void>((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n });\n }\n\n /**\n * Delete a chunk record\n */\n async deleteRecord(projectId: string, clipId: string, track: string): Promise<void> {\n if (!this.db) return;\n\n const tx = this.db.transaction('chunks', 'readwrite');\n const store = tx.objectStore('chunks');\n await this.promisifyRequest(store.delete([projectId, clipId, track]));\n }\n\n /**\n * Update last access time for a record\n */\n async updateLastAccess(projectId: string, clipId: string, track: string): Promise<void> {\n if (!this.db) return;\n\n const tx = this.db.transaction('chunks', 'readwrite');\n const store = tx.objectStore('chunks');\n const record = await this.promisifyRequest<ChunkRecord>(store.get([projectId, clipId, track]));\n\n if (record) {\n record.lastAccess = Date.now();\n await this.promisifyRequest(store.put(record));\n }\n }\n\n /**\n * Collect all records for a specific clip\n */\n async collectRecordsByClipId(projectId: string, clipId: string): Promise<ChunkRecord[]> {\n if (!this.db) return [];\n\n const tx = this.db.transaction('chunks', 'readonly');\n const store = tx.objectStore('chunks');\n const records: ChunkRecord[] = [];\n const cursor = store.openCursor();\n\n await new Promise<void>((resolve) => {\n cursor.onsuccess = (event) => {\n const cursor = (event.target as IDBRequest).result;\n if (!cursor) {\n resolve();\n return;\n }\n\n const record: ChunkRecord = cursor.value;\n if (record.projectId === projectId && record.clipId === clipId) {\n records.push(record);\n }\n\n cursor.continue();\n };\n });\n\n return records;\n }\n\n /**\n * Get all records for a specific project\n */\n async getRecordsByProjectId(projectId: string): Promise<ChunkRecord[]> {\n if (!this.db) return [];\n\n const tx = this.db.transaction('chunks', 'readonly');\n const store = tx.objectStore('chunks');\n const index = store.index('projectId');\n const records = await this.promisifyRequest<ChunkRecord[]>(\n index.getAll(IDBKeyRange.only(projectId))\n );\n\n return records;\n }\n\n /**\n * Get all records sorted by lastAccess (for quota enforcement)\n */\n async getAllRecordsSortedByAccess(): Promise<ChunkRecord[]> {\n if (!this.db) return [];\n\n const tx = this.db.transaction('chunks', 'readonly');\n const store = tx.objectStore('chunks');\n const index = store.index('lastAccess');\n const records: ChunkRecord[] = [];\n\n const cursor = index.openCursor();\n await new Promise<void>((resolve) => {\n cursor.onsuccess = (event) => {\n const cursor = (event.target as IDBRequest).result;\n if (!cursor) {\n resolve();\n return;\n }\n\n records.push(cursor.value);\n cursor.continue();\n };\n });\n\n return records;\n }\n\n /**\n * Get all records (for project statistics)\n */\n async getAllRecords(): Promise<ChunkRecord[]> {\n if (!this.db) return [];\n\n const tx = this.db.transaction('chunks', 'readonly');\n const store = tx.objectStore('chunks');\n const records: ChunkRecord[] = [];\n\n const cursor = store.openCursor();\n await new Promise<void>((resolve) => {\n cursor.onsuccess = (event) => {\n const cursor = (event.target as IDBRequest).result;\n if (!cursor) {\n resolve();\n return;\n }\n\n records.push(cursor.value);\n cursor.continue();\n };\n });\n\n return records;\n }\n\n /**\n * Clear all data from chunks and meta stores\n */\n async clear(): Promise<void> {\n if (!this.db) return;\n\n const tx = this.db.transaction(['chunks', 'meta'], 'readwrite');\n await this.promisifyRequest(tx.objectStore('chunks').clear());\n await this.promisifyRequest(tx.objectStore('meta').clear());\n }\n\n private promisifyRequest<T>(request: IDBRequest): Promise<T> {\n return new Promise((resolve, reject) => {\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n }\n}\n"],"names":["cursor"],"mappings":"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"}
@@ -1,131 +0,0 @@
1
- class OPFSStore {
2
- opfsRoot = null;
3
- initPromise = null;
4
- async init() {
5
- if (this.initPromise) return this.initPromise;
6
- this.initPromise = this.initStorage();
7
- return this.initPromise;
8
- }
9
- async initStorage() {
10
- this.opfsRoot = await navigator.storage.getDirectory();
11
- }
12
- /**
13
- * Read chunk data from OPFS file
14
- */
15
- async read(fileName, batch, projectId) {
16
- if (!this.opfsRoot) return null;
17
- try {
18
- const projectDir = await this.opfsRoot.getDirectoryHandle(`meframe-project-${projectId}`, {
19
- create: false
20
- });
21
- const fileHandle = await projectDir.getFileHandle(fileName);
22
- const file = await fileHandle.getFile();
23
- const slice = file.slice(batch.byteOffset, batch.byteOffset + batch.byteLength);
24
- return await slice.arrayBuffer();
25
- } catch (error) {
26
- if (error?.name === "NotFoundError") {
27
- return null;
28
- }
29
- throw error;
30
- }
31
- }
32
- /**
33
- * Append chunks to OPFS file (or create new file)
34
- * Supports incremental writing for streaming scenarios
35
- */
36
- async append(fileName, chunks, existingBatches, projectId) {
37
- if (!this.opfsRoot) return [];
38
- const projectDir = await this.opfsRoot.getDirectoryHandle(`meframe-project-${projectId}`, {
39
- create: true
40
- });
41
- const fileHandle = await projectDir.getFileHandle(fileName, { create: true });
42
- let offset = 0;
43
- if (existingBatches && existingBatches.length > 0) {
44
- const lastBatch = existingBatches[existingBatches.length - 1];
45
- if (lastBatch) {
46
- offset = lastBatch.byteOffset + lastBatch.byteLength;
47
- }
48
- }
49
- const writable = await fileHandle.createWritable({ keepExistingData: true });
50
- const batches = [];
51
- for (const chunk of chunks) {
52
- const data = await this.chunkToArrayBuffer(chunk);
53
- await writable.write({ type: "write", position: offset, data });
54
- batches.push({
55
- startUs: chunk.timestamp,
56
- durationUs: chunk.duration || 0,
57
- byteOffset: offset,
58
- byteLength: data.byteLength,
59
- type: chunk.type
60
- });
61
- offset += data.byteLength;
62
- }
63
- await writable.close();
64
- return batches;
65
- }
66
- /**
67
- * Delete a file from OPFS
68
- */
69
- async deleteFile(fileName, projectId) {
70
- if (!this.opfsRoot) return;
71
- try {
72
- const projectDir = await this.opfsRoot.getDirectoryHandle(`meframe-project-${projectId}`, {
73
- create: false
74
- });
75
- await projectDir.removeEntry(fileName);
76
- } catch (error) {
77
- if (error?.name !== "NotFoundError") {
78
- console.warn(`[OPFSStore] Failed to delete file ${fileName}:`, error);
79
- }
80
- }
81
- }
82
- /**
83
- * Delete entire project directory
84
- */
85
- async deleteProjectDirectory(projectId) {
86
- if (!this.opfsRoot) return;
87
- try {
88
- const projectDir = await this.opfsRoot.getDirectoryHandle(`meframe-project-${projectId}`, {
89
- create: false
90
- });
91
- await this.opfsRoot.removeEntry(projectDir.name, { recursive: true });
92
- } catch (error) {
93
- if (error?.name !== "NotFoundError") {
94
- console.warn(`[OPFSStore] Failed to remove project directory ${projectId}:`, error);
95
- }
96
- }
97
- }
98
- /**
99
- * Check if a file exists in OPFS
100
- */
101
- async fileExists(fileName, projectId) {
102
- if (!this.opfsRoot) return false;
103
- try {
104
- const projectDir = await this.opfsRoot.getDirectoryHandle(`meframe-project-${projectId}`, {
105
- create: false
106
- });
107
- await projectDir.getFileHandle(fileName, { create: false });
108
- return true;
109
- } catch (error) {
110
- if (error?.name === "NotFoundError") {
111
- return false;
112
- }
113
- throw error;
114
- }
115
- }
116
- /**
117
- * Clear all OPFS data for current project
118
- */
119
- async clear(projectId) {
120
- await this.deleteProjectDirectory(projectId);
121
- }
122
- async chunkToArrayBuffer(chunk) {
123
- const buffer = new ArrayBuffer(chunk.byteLength);
124
- chunk.copyTo(buffer);
125
- return buffer;
126
- }
127
- }
128
- export {
129
- OPFSStore
130
- };
131
- //# sourceMappingURL=OPFSStore.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"OPFSStore.js","sources":["../../../src/cache/l2/OPFSStore.ts"],"sourcesContent":["import type { TimeUs } from '../../model/types';\n\nexport interface ChunkBatch {\n startUs: TimeUs;\n durationUs: TimeUs;\n byteOffset: number;\n byteLength: number;\n type: 'key' | 'delta';\n}\n\n/**\n * OPFS file storage for L2 cache\n * Handles all file operations in Origin Private File System\n */\nexport class OPFSStore {\n private opfsRoot: FileSystemDirectoryHandle | null = null;\n private initPromise: Promise<void> | null = null;\n\n async init(): Promise<void> {\n if (this.initPromise) return this.initPromise;\n\n this.initPromise = this.initStorage();\n return this.initPromise;\n }\n\n private async initStorage(): Promise<void> {\n this.opfsRoot = await navigator.storage.getDirectory();\n }\n\n /**\n * Read chunk data from OPFS file\n */\n async read(fileName: string, batch: ChunkBatch, projectId: string): Promise<ArrayBuffer | null> {\n if (!this.opfsRoot) return null;\n\n try {\n const projectDir = await this.opfsRoot.getDirectoryHandle(`meframe-project-${projectId}`, {\n create: false,\n });\n const fileHandle = await projectDir.getFileHandle(fileName);\n const file = await fileHandle.getFile();\n const slice = file.slice(batch.byteOffset, batch.byteOffset + batch.byteLength);\n return await slice.arrayBuffer();\n } catch (error) {\n if ((error as any)?.name === 'NotFoundError') {\n return null;\n }\n throw error;\n }\n }\n\n /**\n * Append chunks to OPFS file (or create new file)\n * Supports incremental writing for streaming scenarios\n */\n async append(\n fileName: string,\n chunks: Array<EncodedVideoChunk | EncodedAudioChunk>,\n existingBatches: ChunkBatch[] | undefined,\n projectId: string\n ): Promise<ChunkBatch[]> {\n if (!this.opfsRoot) return [];\n\n const projectDir = await this.opfsRoot.getDirectoryHandle(`meframe-project-${projectId}`, {\n create: true,\n });\n const fileHandle = await projectDir.getFileHandle(fileName, { create: true });\n\n // Calculate starting offset from existing batches\n let offset = 0;\n if (existingBatches && existingBatches.length > 0) {\n const lastBatch = existingBatches[existingBatches.length - 1];\n if (lastBatch) {\n offset = lastBatch.byteOffset + lastBatch.byteLength;\n }\n }\n\n const writable = await fileHandle.createWritable({ keepExistingData: true });\n\n const batches: ChunkBatch[] = [];\n\n for (const chunk of chunks) {\n const data = await this.chunkToArrayBuffer(chunk);\n await writable.write({ type: 'write', position: offset, data });\n\n batches.push({\n startUs: chunk.timestamp,\n durationUs: chunk.duration || 0,\n byteOffset: offset,\n byteLength: data.byteLength,\n type: chunk.type,\n });\n\n offset += data.byteLength;\n }\n\n await writable.close();\n return batches;\n }\n\n /**\n * Delete a file from OPFS\n */\n async deleteFile(fileName: string, projectId: string): Promise<void> {\n if (!this.opfsRoot) return;\n\n try {\n const projectDir = await this.opfsRoot.getDirectoryHandle(`meframe-project-${projectId}`, {\n create: false,\n });\n await projectDir.removeEntry(fileName);\n } catch (error) {\n if ((error as any)?.name !== 'NotFoundError') {\n console.warn(`[OPFSStore] Failed to delete file ${fileName}:`, error);\n }\n }\n }\n\n /**\n * Delete entire project directory\n */\n async deleteProjectDirectory(projectId: string): Promise<void> {\n if (!this.opfsRoot) return;\n\n try {\n const projectDir = await this.opfsRoot.getDirectoryHandle(`meframe-project-${projectId}`, {\n create: false,\n });\n await this.opfsRoot.removeEntry(projectDir.name, { recursive: true });\n } catch (error) {\n if ((error as any)?.name !== 'NotFoundError') {\n console.warn(`[OPFSStore] Failed to remove project directory ${projectId}:`, error);\n }\n }\n }\n\n /**\n * Check if a file exists in OPFS\n */\n async fileExists(fileName: string, projectId: string): Promise<boolean> {\n if (!this.opfsRoot) return false;\n\n try {\n const projectDir = await this.opfsRoot.getDirectoryHandle(`meframe-project-${projectId}`, {\n create: false,\n });\n await projectDir.getFileHandle(fileName, { create: false });\n return true;\n } catch (error) {\n if ((error as any)?.name === 'NotFoundError') {\n return false;\n }\n throw error;\n }\n }\n\n /**\n * Clear all OPFS data for current project\n */\n async clear(projectId: string): Promise<void> {\n await this.deleteProjectDirectory(projectId);\n }\n\n private async chunkToArrayBuffer(\n chunk: EncodedVideoChunk | EncodedAudioChunk\n ): Promise<ArrayBuffer> {\n const buffer = new ArrayBuffer(chunk.byteLength);\n chunk.copyTo(buffer);\n return buffer;\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1,59 +0,0 @@
1
- import { IPreRenderService, RenderStrategy, PreRenderStatus } from './types';
2
- import { TimeUs } from '../model/types';
3
- import { Orchestrator } from '../orchestrator';
4
-
5
- /**
6
- * PreRenderService: Background pre-render for filling L2 cache
7
- *
8
- * Dual-mode strategy:
9
- * 1. When playback active: Ensure 2-Clip window (Prev/Current/Next) for preview
10
- * 2. When idle: Continue rendering subsequent clips for L2 export cache
11
- *
12
- * Goals:
13
- * - Maintain smooth preview (2-Clip L1 cache)
14
- * - Build complete L2 cache for fast export
15
- * - Operate in background without blocking
16
- */
17
- export declare class PreRenderService implements IPreRenderService {
18
- private orchestrator;
19
- private _isRunning;
20
- private _isPaused;
21
- private renderLoopId;
22
- private _framesRendered;
23
- private _currentTimeUs;
24
- private isPlaybackActive;
25
- private strategy;
26
- private isRendering;
27
- private highPriorityMode;
28
- private exportWaiters;
29
- private progressCallback;
30
- constructor(orchestrator: Orchestrator, _eventBus: any);
31
- start(): void;
32
- stop(): Promise<void>;
33
- pause(): void;
34
- resume(): void;
35
- setWindow(size: TimeUs): void;
36
- setStrategy(strategy: Partial<RenderStrategy>): void;
37
- setPriority(_priority: 'low' | 'normal' | 'high'): void;
38
- updatePlaybackTime(timeUs: TimeUs): void;
39
- setPlaybackActive(active: boolean): void;
40
- get isRunning(): boolean;
41
- get queueSize(): number;
42
- get status(): PreRenderStatus;
43
- private scheduleNextRender;
44
- private renderTick;
45
- /**
46
- * Render a complete clip to L2 cache
47
- * Creates dedicated pipeline that bypasses L1 window management
48
- */
49
- private renderClipToL2;
50
- /**
51
- * Ensure all clips are in L2 cache
52
- * Switches to high priority mode and waits for completion
53
- */
54
- ensureClipsInL2(options?: {
55
- onProgress?: (completed: number, total: number) => void;
56
- signal?: AbortSignal;
57
- }): Promise<void>;
58
- }
59
- //# sourceMappingURL=PreRenderService.d.ts.map