@meframe/core 0.0.1 → 0.0.3

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 (159) hide show
  1. package/README.md +17 -4
  2. package/dist/Meframe.d.ts.map +1 -1
  3. package/dist/Meframe.js +2 -4
  4. package/dist/Meframe.js.map +1 -1
  5. package/dist/cache/CacheManager.d.ts.map +1 -1
  6. package/dist/cache/CacheManager.js +8 -1
  7. package/dist/cache/CacheManager.js.map +1 -1
  8. package/dist/config/defaults.d.ts.map +1 -1
  9. package/dist/config/defaults.js +2 -9
  10. package/dist/config/defaults.js.map +1 -1
  11. package/dist/config/types.d.ts +3 -4
  12. package/dist/config/types.d.ts.map +1 -1
  13. package/dist/controllers/PlaybackController.d.ts +4 -2
  14. package/dist/controllers/PlaybackController.d.ts.map +1 -1
  15. package/dist/controllers/PlaybackController.js +7 -13
  16. package/dist/controllers/PlaybackController.js.map +1 -1
  17. package/dist/controllers/PreRenderService.d.ts +3 -2
  18. package/dist/controllers/PreRenderService.d.ts.map +1 -1
  19. package/dist/controllers/PreRenderService.js.map +1 -1
  20. package/dist/controllers/PreviewHandle.d.ts +2 -0
  21. package/dist/controllers/PreviewHandle.d.ts.map +1 -1
  22. package/dist/controllers/PreviewHandle.js +6 -0
  23. package/dist/controllers/PreviewHandle.js.map +1 -1
  24. package/dist/controllers/index.d.ts +1 -1
  25. package/dist/controllers/index.d.ts.map +1 -1
  26. package/dist/controllers/types.d.ts +2 -12
  27. package/dist/controllers/types.d.ts.map +1 -1
  28. package/dist/event/events.d.ts +5 -59
  29. package/dist/event/events.d.ts.map +1 -1
  30. package/dist/event/events.js +1 -6
  31. package/dist/event/events.js.map +1 -1
  32. package/dist/model/CompositionModel.js +1 -2
  33. package/dist/model/CompositionModel.js.map +1 -1
  34. package/dist/orchestrator/CompositionPlanner.d.ts.map +1 -1
  35. package/dist/orchestrator/CompositionPlanner.js +1 -0
  36. package/dist/orchestrator/CompositionPlanner.js.map +1 -1
  37. package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
  38. package/dist/orchestrator/Orchestrator.js +3 -13
  39. package/dist/orchestrator/Orchestrator.js.map +1 -1
  40. package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
  41. package/dist/orchestrator/VideoClipSession.js +4 -5
  42. package/dist/orchestrator/VideoClipSession.js.map +1 -1
  43. package/dist/orchestrator/types.d.ts +1 -1
  44. package/dist/orchestrator/types.d.ts.map +1 -1
  45. package/dist/stages/compose/GlobalAudioSession.d.ts.map +1 -1
  46. package/dist/stages/compose/GlobalAudioSession.js +3 -2
  47. package/dist/stages/compose/GlobalAudioSession.js.map +1 -1
  48. package/dist/stages/compose/VideoComposer.d.ts.map +1 -1
  49. package/dist/stages/compose/types.d.ts +3 -1
  50. package/dist/stages/compose/types.d.ts.map +1 -1
  51. package/dist/stages/decode/AudioChunkDecoder.d.ts.map +1 -1
  52. package/dist/stages/decode/VideoChunkDecoder.d.ts +0 -1
  53. package/dist/stages/decode/VideoChunkDecoder.d.ts.map +1 -1
  54. package/dist/stages/demux/MP4Demuxer.d.ts +2 -1
  55. package/dist/stages/demux/MP4Demuxer.d.ts.map +1 -1
  56. package/dist/stages/load/EventHandlers.d.ts +2 -11
  57. package/dist/stages/load/EventHandlers.d.ts.map +1 -1
  58. package/dist/stages/load/EventHandlers.js +1 -24
  59. package/dist/stages/load/EventHandlers.js.map +1 -1
  60. package/dist/stages/load/ResourceLoader.d.ts.map +1 -1
  61. package/dist/stages/load/ResourceLoader.js +11 -13
  62. package/dist/stages/load/ResourceLoader.js.map +1 -1
  63. package/dist/stages/load/TaskManager.d.ts +1 -1
  64. package/dist/stages/load/TaskManager.d.ts.map +1 -1
  65. package/dist/stages/load/TaskManager.js +3 -2
  66. package/dist/stages/load/TaskManager.js.map +1 -1
  67. package/dist/stages/load/types.d.ts +2 -0
  68. package/dist/stages/load/types.d.ts.map +1 -1
  69. package/dist/utils/time-utils.d.ts +3 -2
  70. package/dist/utils/time-utils.d.ts.map +1 -1
  71. package/dist/utils/time-utils.js +2 -1
  72. package/dist/utils/time-utils.js.map +1 -1
  73. package/dist/vite-plugin.d.ts +19 -0
  74. package/dist/vite-plugin.d.ts.map +1 -0
  75. package/dist/vite-plugin.js +145 -0
  76. package/dist/vite-plugin.js.map +1 -0
  77. package/dist/worker/WorkerPool.d.ts +7 -4
  78. package/dist/worker/WorkerPool.d.ts.map +1 -1
  79. package/dist/worker/WorkerPool.js +29 -18
  80. package/dist/worker/WorkerPool.js.map +1 -1
  81. package/dist/{stages/demux → workers}/MP4Demuxer.js +17 -15
  82. package/dist/workers/MP4Demuxer.js.map +1 -0
  83. package/dist/workers/WorkerChannel.js +486 -0
  84. package/dist/workers/WorkerChannel.js.map +1 -0
  85. package/dist/workers/mp4box.all.js +7049 -0
  86. package/dist/workers/mp4box.all.js.map +1 -0
  87. package/dist/workers/stages/compose/audio-compose.worker.js +1063 -0
  88. package/dist/workers/stages/compose/audio-compose.worker.js.map +1 -0
  89. package/dist/workers/stages/compose/video-compose.worker.js +1209 -0
  90. package/dist/workers/stages/compose/video-compose.worker.js.map +1 -0
  91. package/dist/{stages → workers/stages}/decode/decode.worker.js +401 -20
  92. package/dist/workers/stages/decode/decode.worker.js.map +1 -0
  93. package/dist/{stages → workers/stages}/demux/audio-demux.worker.js +184 -4
  94. package/dist/workers/stages/demux/audio-demux.worker.js.map +1 -0
  95. package/dist/{stages → workers/stages}/demux/video-demux.worker.js +7 -30
  96. package/dist/workers/stages/demux/video-demux.worker.js.map +1 -0
  97. package/dist/{stages → workers/stages}/encode/encode.worker.js +238 -5
  98. package/dist/workers/stages/encode/encode.worker.js.map +1 -0
  99. package/dist/{stages/mux/MP4Muxer.js → workers/stages/mux/mux.worker.js} +244 -5
  100. package/dist/workers/stages/mux/mux.worker.js.map +1 -0
  101. package/package.json +27 -21
  102. package/dist/model/types.js +0 -5
  103. package/dist/model/types.js.map +0 -1
  104. package/dist/plugins/BackpressureMonitor.js +0 -62
  105. package/dist/plugins/BackpressureMonitor.js.map +0 -1
  106. package/dist/stages/compose/AudioDucker.js +0 -161
  107. package/dist/stages/compose/AudioDucker.js.map +0 -1
  108. package/dist/stages/compose/AudioMixer.js +0 -373
  109. package/dist/stages/compose/AudioMixer.js.map +0 -1
  110. package/dist/stages/compose/FilterProcessor.js +0 -226
  111. package/dist/stages/compose/FilterProcessor.js.map +0 -1
  112. package/dist/stages/compose/LayerRenderer.js +0 -215
  113. package/dist/stages/compose/LayerRenderer.js.map +0 -1
  114. package/dist/stages/compose/TransitionProcessor.js +0 -189
  115. package/dist/stages/compose/TransitionProcessor.js.map +0 -1
  116. package/dist/stages/compose/VideoComposer.js +0 -186
  117. package/dist/stages/compose/VideoComposer.js.map +0 -1
  118. package/dist/stages/compose/audio-compose.worker.d.ts +0 -79
  119. package/dist/stages/compose/audio-compose.worker.d.ts.map +0 -1
  120. package/dist/stages/compose/audio-compose.worker.js +0 -541
  121. package/dist/stages/compose/audio-compose.worker.js.map +0 -1
  122. package/dist/stages/compose/video-compose.worker.d.ts +0 -60
  123. package/dist/stages/compose/video-compose.worker.d.ts.map +0 -1
  124. package/dist/stages/compose/video-compose.worker.js +0 -369
  125. package/dist/stages/compose/video-compose.worker.js.map +0 -1
  126. package/dist/stages/decode/AudioChunkDecoder.js +0 -83
  127. package/dist/stages/decode/AudioChunkDecoder.js.map +0 -1
  128. package/dist/stages/decode/BaseDecoder.js +0 -130
  129. package/dist/stages/decode/BaseDecoder.js.map +0 -1
  130. package/dist/stages/decode/VideoChunkDecoder.js +0 -209
  131. package/dist/stages/decode/VideoChunkDecoder.js.map +0 -1
  132. package/dist/stages/decode/decode.worker.d.ts +0 -70
  133. package/dist/stages/decode/decode.worker.d.ts.map +0 -1
  134. package/dist/stages/decode/decode.worker.js.map +0 -1
  135. package/dist/stages/demux/MP3FrameParser.js +0 -186
  136. package/dist/stages/demux/MP3FrameParser.js.map +0 -1
  137. package/dist/stages/demux/MP4Demuxer.js.map +0 -1
  138. package/dist/stages/demux/audio-demux.worker.d.ts +0 -51
  139. package/dist/stages/demux/audio-demux.worker.d.ts.map +0 -1
  140. package/dist/stages/demux/audio-demux.worker.js.map +0 -1
  141. package/dist/stages/demux/video-demux.worker.d.ts +0 -48
  142. package/dist/stages/demux/video-demux.worker.d.ts.map +0 -1
  143. package/dist/stages/demux/video-demux.worker.js.map +0 -1
  144. package/dist/stages/encode/AudioChunkEncoder.js +0 -37
  145. package/dist/stages/encode/AudioChunkEncoder.js.map +0 -1
  146. package/dist/stages/encode/BaseEncoder.js +0 -164
  147. package/dist/stages/encode/BaseEncoder.js.map +0 -1
  148. package/dist/stages/encode/VideoChunkEncoder.js +0 -50
  149. package/dist/stages/encode/VideoChunkEncoder.js.map +0 -1
  150. package/dist/stages/encode/encode.worker.d.ts +0 -3
  151. package/dist/stages/encode/encode.worker.d.ts.map +0 -1
  152. package/dist/stages/encode/encode.worker.js.map +0 -1
  153. package/dist/stages/mux/MP4Muxer.js.map +0 -1
  154. package/dist/stages/mux/mux.worker.d.ts +0 -65
  155. package/dist/stages/mux/mux.worker.d.ts.map +0 -1
  156. package/dist/stages/mux/mux.worker.js +0 -219
  157. package/dist/stages/mux/mux.worker.js.map +0 -1
  158. package/dist/stages/mux/utils.js +0 -34
  159. package/dist/stages/mux/utils.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"BaseEncoder.js","sources":["../../../src/stages/encode/BaseEncoder.ts"],"sourcesContent":["// Base encoder implementation\n\n/**\n * Base encoder class for both video and audio encoding\n * Handles common WebCodecs encoder operations\n */\nexport abstract class BaseEncoder<\n TEncoder extends VideoEncoder | AudioEncoder,\n TConfig extends VideoEncoderConfig | AudioEncoderConfig,\n TInput extends VideoFrame | AudioData,\n TChunk extends EncodedVideoChunk | EncodedAudioChunk,\n TMetadata extends EncodedVideoChunkMetadata | EncodedAudioChunkMetadata,\n> {\n protected encoder?: TEncoder;\n protected config: TConfig;\n protected controller: TransformStreamDefaultController<TChunk> | null = null;\n\n constructor(config: TConfig) {\n this.config = config;\n }\n\n getConfig(): TConfig {\n return { ...this.config };\n }\n\n protected get currentConfig(): TConfig {\n return this.config;\n }\n\n protected shouldReconfigure(partial: Partial<TConfig>): boolean {\n const next = { ...this.config, ...partial } as TConfig;\n const keys = Object.keys(partial ?? {}) as Array<keyof TConfig>;\n for (const key of keys) {\n if (partial[key] !== undefined && next[key] !== this.config[key]) {\n return true;\n }\n }\n return false;\n }\n\n protected hasConfigChanged(next: TConfig): boolean {\n const currentEntries = Object.entries(this.config) as Array<[keyof TConfig, any]>;\n for (const [key, value] of currentEntries) {\n if (next[key] !== value) {\n return true;\n }\n }\n\n for (const key of Object.keys(next) as Array<keyof TConfig>) {\n if (this.config[key] !== next[key]) {\n return true;\n }\n }\n\n return false;\n }\n\n protected configsEqual(a: TConfig, b: TConfig): boolean {\n return JSON.stringify(a) === JSON.stringify(b);\n }\n\n async initialize(): Promise<void> {\n if (this.encoder?.state === 'configured') {\n return;\n }\n\n const isSupported = await this.isConfigSupported(this.config);\n if (!isSupported.supported) {\n throw new Error(`Codec not supported: ${this.config.codec}`);\n }\n\n this.encoder = this.createEncoder({\n output: this.handleOutput.bind(this),\n error: this.handleError.bind(this),\n });\n\n (this.encoder as any).configure(this.config);\n }\n\n async reconfigure(config: Partial<TConfig>): Promise<void> {\n if (!config || Object.keys(config).length === 0) {\n return;\n }\n\n const nextConfig = { ...this.config, ...config } as TConfig;\n\n if (this.configsEqual(this.config, nextConfig)) {\n return;\n }\n\n if (!this.encoder) {\n this.config = nextConfig;\n await this.initialize();\n return;\n }\n\n if (this.encoder.state === 'configured') {\n await this.encoder.flush();\n }\n\n const isSupported = await this.isConfigSupported(nextConfig);\n if (!isSupported.supported) {\n throw new Error(`New configuration not supported: ${nextConfig.codec}`);\n }\n\n this.config = nextConfig;\n (this.encoder as any).configure(this.config);\n }\n\n async flush(): Promise<void> {\n if (!this.encoder) {\n return;\n }\n\n await this.encoder.flush();\n }\n\n async reset(): Promise<void> {\n if (!this.encoder) {\n return;\n }\n\n this.encoder.reset();\n this.onReset();\n }\n\n async close(): Promise<void> {\n if (!this.encoder) {\n return;\n }\n\n if (this.encoder.state === 'configured') {\n await this.encoder.flush();\n }\n\n this.encoder.close();\n this.encoder = undefined;\n }\n\n get isReady(): boolean {\n return this.encoder?.state === 'configured';\n }\n\n get queueSize(): number {\n return this.encoder?.encodeQueueSize ?? 0;\n }\n\n protected handleOutput(chunk: TChunk, _metadata: TMetadata): void {\n // Enqueue to stream controller\n this.controller?.enqueue(chunk);\n }\n\n protected handleError(error: DOMException): void {\n console.error(`${this.getEncoderType()} encoder error:`, error);\n this.controller?.error(error);\n }\n\n // Abstract methods to be implemented by subclasses\n protected abstract isConfigSupported(config: TConfig): Promise<{ supported: boolean }>;\n protected abstract createEncoder(init: EncoderInit): TEncoder;\n protected abstract getEncoderType(): string;\n\n // Hook for subclasses to handle reset\n protected onReset(): void {\n // Override in subclasses if needed\n }\n\n // Abstract properties for backpressure configuration\n protected abstract readonly highWaterMark: number;\n protected abstract readonly encodeQueueThreshold: number;\n\n /**\n * Create transform stream for encoding\n * Implements common stream logic with backpressure handling\n */\n createStream(): TransformStream<TInput, TChunk> {\n return new TransformStream<TInput, TChunk>(\n {\n start: async (controller) => {\n this.controller = controller;\n\n // Initialize encoder if not already initialized\n if (!this.isReady) {\n await this.initialize();\n }\n },\n\n transform: async (input) => {\n if (!this.encoder || this.encoder.state !== 'configured') {\n throw new Error('Encoder not configured');\n }\n\n // Check encoder queue pressure\n if (this.encoder.encodeQueueSize >= this.encodeQueueThreshold) {\n // Wait for queue to drain\n await new Promise<void>((resolve) => {\n const check = () => {\n if (!this.encoder || this.encoder.encodeQueueSize < this.encodeQueueThreshold - 1) {\n resolve();\n } else {\n setTimeout(check, 10);\n }\n };\n check();\n });\n }\n\n // Encode the input\n this.encode(input);\n },\n\n flush: async () => {\n await this.flush();\n },\n },\n // Queuing strategy with backpressure configuration\n {\n highWaterMark: this.highWaterMark,\n size: () => 1, // Count-based\n }\n );\n }\n\n // Abstract method for encoding\n abstract encode(input: TInput): void;\n}\n\ninterface EncoderInit {\n output: (chunk: any, metadata: any) => void;\n error: (error: DOMException) => void;\n}\n"],"names":[],"mappings":"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"}
@@ -1,50 +0,0 @@
1
- import { BaseEncoder } from "./BaseEncoder.js";
2
- class VideoChunkEncoder extends BaseEncoder {
3
- static DEFAULT_HIGH_WATER_MARK = 2;
4
- static DEFAULT_ENCODE_QUEUE_THRESHOLD = 8;
5
- highWaterMark;
6
- encodeQueueThreshold;
7
- frameCount = 0;
8
- keyFrameInterval = 60;
9
- // 2 seconds at 30fps
10
- constructor(config) {
11
- super(config);
12
- this.highWaterMark = config.backpressure?.highWaterMark ?? VideoChunkEncoder.DEFAULT_HIGH_WATER_MARK;
13
- this.encodeQueueThreshold = config.backpressure?.encodeQueueThreshold ?? VideoChunkEncoder.DEFAULT_ENCODE_QUEUE_THRESHOLD;
14
- }
15
- async isConfigSupported(config) {
16
- const result = await VideoEncoder.isConfigSupported(config);
17
- return { supported: result.supported ?? false };
18
- }
19
- createEncoder(init) {
20
- return new VideoEncoder(init);
21
- }
22
- getEncoderType() {
23
- return "Video";
24
- }
25
- onReset() {
26
- this.frameCount = 0;
27
- }
28
- encode(frame) {
29
- const keyFrame = this.shouldGenerateKeyFrame();
30
- const encodeOptions = {
31
- keyFrame
32
- };
33
- this.encoder.encode(frame, encodeOptions);
34
- this.frameCount++;
35
- frame.close();
36
- }
37
- setKeyFrameInterval(interval) {
38
- this.keyFrameInterval = Math.max(1, interval);
39
- }
40
- shouldGenerateKeyFrame() {
41
- if (this.frameCount === 0) {
42
- return true;
43
- }
44
- return this.frameCount % this.keyFrameInterval === 0;
45
- }
46
- }
47
- export {
48
- VideoChunkEncoder
49
- };
50
- //# sourceMappingURL=VideoChunkEncoder.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"VideoChunkEncoder.js","sources":["../../../src/stages/encode/VideoChunkEncoder.ts"],"sourcesContent":["import { BaseEncoder } from './BaseEncoder';\nimport type { VideoEncoderConfig } from './types';\n\n/**\n * VideoChunkEncoder - Encodes VideoFrame to EncodedVideoChunk\n * Stream-based encoder with backpressure handling\n */\nexport class VideoChunkEncoder extends BaseEncoder<\n VideoEncoder,\n VideoEncoderConfig,\n VideoFrame,\n EncodedVideoChunk,\n EncodedVideoChunkMetadata\n> {\n private static readonly DEFAULT_HIGH_WATER_MARK = 2;\n private static readonly DEFAULT_ENCODE_QUEUE_THRESHOLD = 8;\n\n protected readonly highWaterMark: number;\n protected readonly encodeQueueThreshold: number;\n\n private frameCount = 0;\n private keyFrameInterval = 60; // 2 seconds at 30fps\n\n constructor(config: VideoEncoderConfig) {\n super(config);\n\n // Initialize backpressure settings from config or use defaults\n this.highWaterMark =\n config.backpressure?.highWaterMark ?? VideoChunkEncoder.DEFAULT_HIGH_WATER_MARK;\n this.encodeQueueThreshold =\n config.backpressure?.encodeQueueThreshold ?? VideoChunkEncoder.DEFAULT_ENCODE_QUEUE_THRESHOLD;\n }\n\n protected async isConfigSupported(config: VideoEncoderConfig): Promise<{ supported: boolean }> {\n const result = await VideoEncoder.isConfigSupported(config);\n return { supported: result.supported ?? false };\n }\n\n protected createEncoder(init: VideoEncoderInit): VideoEncoder {\n return new VideoEncoder(init);\n }\n\n protected getEncoderType(): string {\n return 'Video';\n }\n\n protected override onReset(): void {\n this.frameCount = 0;\n }\n\n encode(frame: VideoFrame): void {\n const keyFrame = this.shouldGenerateKeyFrame();\n const encodeOptions: VideoEncoderEncodeOptions = {\n keyFrame,\n };\n\n this.encoder!.encode(frame, encodeOptions);\n this.frameCount++;\n frame.close();\n }\n\n setKeyFrameInterval(interval: number): void {\n this.keyFrameInterval = Math.max(1, interval);\n }\n\n private shouldGenerateKeyFrame(): boolean {\n if (this.frameCount === 0) {\n return true;\n }\n return this.frameCount % this.keyFrameInterval === 0;\n }\n}\n"],"names":[],"mappings":";AAOO,MAAM,0BAA0B,YAMrC;AAAA,EACA,OAAwB,0BAA0B;AAAA,EAClD,OAAwB,iCAAiC;AAAA,EAEtC;AAAA,EACA;AAAA,EAEX,aAAa;AAAA,EACb,mBAAmB;AAAA;AAAA,EAE3B,YAAY,QAA4B;AACtC,UAAM,MAAM;AAGZ,SAAK,gBACH,OAAO,cAAc,iBAAiB,kBAAkB;AAC1D,SAAK,uBACH,OAAO,cAAc,wBAAwB,kBAAkB;AAAA,EACnE;AAAA,EAEA,MAAgB,kBAAkB,QAA6D;AAC7F,UAAM,SAAS,MAAM,aAAa,kBAAkB,MAAM;AAC1D,WAAO,EAAE,WAAW,OAAO,aAAa,MAAA;AAAA,EAC1C;AAAA,EAEU,cAAc,MAAsC;AAC5D,WAAO,IAAI,aAAa,IAAI;AAAA,EAC9B;AAAA,EAEU,iBAAyB;AACjC,WAAO;AAAA,EACT;AAAA,EAEmB,UAAgB;AACjC,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,OAAO,OAAyB;AAC9B,UAAM,WAAW,KAAK,uBAAA;AACtB,UAAM,gBAA2C;AAAA,MAC/C;AAAA,IAAA;AAGF,SAAK,QAAS,OAAO,OAAO,aAAa;AACzC,SAAK;AACL,UAAM,MAAA;AAAA,EACR;AAAA,EAEA,oBAAoB,UAAwB;AAC1C,SAAK,mBAAmB,KAAK,IAAI,GAAG,QAAQ;AAAA,EAC9C;AAAA,EAEQ,yBAAkC;AACxC,QAAI,KAAK,eAAe,GAAG;AACzB,aAAO;AAAA,IACT;AACA,WAAO,KAAK,aAAa,KAAK,qBAAqB;AAAA,EACrD;AACF;"}
@@ -1,3 +0,0 @@
1
- declare const _default: null;
2
- export default _default;
3
- //# sourceMappingURL=encode.worker.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"encode.worker.d.ts","sourceRoot":"","sources":["../../../src/stages/encode/encode.worker.ts"],"names":[],"mappings":";AA0aA,wBAAoB"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"encode.worker.js","sources":["../../../src/stages/encode/encode.worker.ts"],"sourcesContent":["import { WorkerChannel } from '../../worker/WorkerChannel';\nimport { WorkerMessageType, WorkerState } from '../../worker/types';\nimport { VideoChunkEncoder } from './VideoChunkEncoder';\nimport { AudioChunkEncoder } from './AudioChunkEncoder';\nimport { VideoEncoderConfig, AudioEncoderConfig } from './types';\n\n/**\n * EncodeWorker - Seventh stage in the pipeline\n * Receives composed frames from ComposeWorkers and outputs encoded chunks to CacheManager/MuxWorker\n *\n * Pipeline: VideoComposeWorker/AudioComposeWorker → EncodeWorker → CacheManager/MuxWorker\n *\n * Features:\n * - Hardware-accelerated encoding via WebCodecs\n * - Configurable bitrate and quality settings\n * - Batch flush for efficient I/O (0.5s batches)\n * - Direct streaming to cache/mux workers\n */\nclass EncodeWorker {\n private channel: WorkerChannel;\n private videoEncoder: VideoChunkEncoder | null = null;\n private audioEncoder: AudioChunkEncoder | null = null;\n\n // Connections to other workers\n private cachePort: MessagePort | null = null;\n private muxPort: MessagePort | null = null;\n private composePorts = new Map<string, MessagePort>(); // Connections from compose workers\n\n constructor() {\n // Initialize WorkerChannel with MessagePort\n this.channel = new WorkerChannel(self as any, {\n name: 'EncodeWorker',\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 this.channel.registerHandler('connect', this.handleConnect.bind(this));\n this.channel.registerHandler('configure_video', this.handleConfigureVideo.bind(this));\n this.channel.registerHandler('configure_audio', this.handleConfigureAudio.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 /**\n * 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 const { port, streamType } = payload;\n if (streamType === 'video') return this.handleConnectComposer({ composeType: 'video', port });\n if (streamType === 'audio') return this.handleConnectComposer({ composeType: 'audio', port });\n if (streamType === 'chunk') return this.handleConnectMux({ port });\n return { success: true };\n }\n\n /**\n * Handle configuration message from orchestrator\n * @param payload.initial - If true, initialize worker and recreate encoder instances; otherwise just update config\n */\n private async handleConfigure(payload: {\n config: {\n video?: Partial<VideoEncoderConfig> & { stream?: ReadableStream<VideoFrame> };\n audio?: Partial<AudioEncoderConfig> & { stream?: ReadableStream<AudioData> };\n };\n initial?: boolean;\n }): Promise<{ success: boolean }> {\n const { config, initial = false } = payload;\n\n // Set worker state to ready on initial configuration\n if (initial) {\n this.channel.state = WorkerState.Ready;\n }\n\n // Handle video encoder configuration\n if (config.video) {\n if (initial || !this.videoEncoder) {\n if (this.videoEncoder) {\n await this.videoEncoder.close();\n }\n this.videoEncoder = new VideoChunkEncoder(config.video as VideoEncoderConfig);\n await this.videoEncoder.initialize();\n\n const videoStream = config.video.stream?.pipeThrough(this.videoEncoder.createStream());\n if (videoStream && this.cachePort) {\n const cacheChannel = new WorkerChannel(this.cachePort, {\n name: 'Encode-Cache-Video',\n timeout: 30000,\n });\n await cacheChannel.sendStream(videoStream, {\n type: 'video',\n width: config.video.width,\n height: config.video.height,\n framerate: config.video.framerate,\n });\n }\n } else {\n await this.videoEncoder.reconfigure(config.video);\n }\n }\n\n if (config.audio) {\n if (initial || !this.audioEncoder) {\n if (this.audioEncoder) {\n await this.audioEncoder.close();\n }\n this.audioEncoder = new AudioChunkEncoder(config.audio as AudioEncoderConfig);\n await this.audioEncoder.initialize();\n\n const audioStream = config.audio.stream?.pipeThrough(this.audioEncoder.createStream());\n if (audioStream && this.cachePort) {\n const cacheChannel = new WorkerChannel(this.cachePort, {\n name: 'Encode-Cache-Audio',\n timeout: 30000,\n });\n await cacheChannel.sendStream(audioStream, {\n type: 'audio',\n sampleRate: config.audio.sampleRate,\n numberOfChannels: config.audio.numberOfChannels,\n });\n }\n } else {\n await this.audioEncoder.reconfigure(config.audio);\n }\n }\n\n return { success: true };\n }\n\n /**\n * Connect to a compose worker to receive frames/audio data\n */\n private async handleConnectComposer(payload: {\n composeType: 'video' | 'audio';\n port: MessagePort;\n }): Promise<{ success: boolean }> {\n const { composeType, port } = payload;\n\n // Store the port\n this.composePorts.set(composeType, port);\n\n // Setup channel for receiving streams\n const composeChannel = new WorkerChannel(port, {\n name: `Encode-${composeType}Compose`,\n timeout: 30000,\n });\n\n // Receive stream from composer\n composeChannel.receiveStream(async (stream, metadata) => {\n console.log('[EncodeWorker] receiveStream', metadata);\n if (metadata?.streamType === 'video' && this.videoEncoder) {\n // Process video frames\n const reader = stream.getReader();\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n // value might be wrapped object {frame, metadata} or plain VideoFrame\n const wrappedValue = value as any;\n const videoFrame = wrappedValue.frame || wrappedValue;\n\n try {\n const frame = (videoFrame as VideoFrame).clone();\n this.videoEncoder.encode(frame);\n } finally {\n // Close the original frame after cloning and encoding\n (videoFrame as VideoFrame).close();\n }\n }\n } finally {\n reader.releaseLock();\n }\n } else if (metadata?.streamType === 'audio' && this.audioEncoder) {\n const composedConfig = {\n sampleRate: metadata.sampleRate,\n numberOfChannels: metadata.numberOfChannels,\n };\n\n const currentConfig = this.audioEncoder.getConfig();\n\n if (\n typeof composedConfig.sampleRate === 'number' &&\n composedConfig.sampleRate > 0 &&\n composedConfig.sampleRate !== currentConfig.sampleRate\n ) {\n await this.audioEncoder.reconfigure({ sampleRate: composedConfig.sampleRate });\n }\n\n if (\n typeof composedConfig.numberOfChannels === 'number' &&\n composedConfig.numberOfChannels > 0 &&\n composedConfig.numberOfChannels !== currentConfig.numberOfChannels\n ) {\n await this.audioEncoder.reconfigure({\n numberOfChannels: composedConfig.numberOfChannels,\n });\n }\n\n const reader = stream.getReader();\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n this.audioEncoder.encode(value as unknown as AudioData);\n }\n } finally {\n reader.releaseLock();\n }\n }\n });\n\n return { success: true };\n }\n\n /**\n * Connect to cache manager for output streaming\n */\n // private async handleConnectCache(payload: { port: MessagePort }): Promise<{ success: boolean }> {\n // this.cachePort = payload.port;\n // return { success: true };\n // }\n\n /**\n * Connect to mux worker for output streaming\n */\n private async handleConnectMux(payload: { port: MessagePort }): Promise<{ success: boolean }> {\n this.muxPort = payload.port;\n return { success: true };\n }\n\n /**\n * Configure video encoder with specific settings\n */\n private async handleConfigureVideo(config: VideoEncoderConfig): Promise<{ success: boolean }> {\n try {\n // Create encoder if not exists (shouldn't happen if configure was called)\n if (!this.videoEncoder) {\n this.videoEncoder = new VideoChunkEncoder(config);\n await this.videoEncoder.initialize();\n } else {\n await this.videoEncoder.reconfigure(config);\n }\n\n // Notify configuration complete\n this.channel.notify('video_configured', {\n codec: config.codec,\n width: config.width,\n height: config.height,\n bitrate: config.bitrate,\n });\n\n return { success: true };\n } catch (error: any) {\n throw {\n code: 'VIDEO_CONFIG_ERROR',\n message: error.message,\n };\n }\n }\n\n /**\n * Configure audio encoder with specific settings\n */\n private async handleConfigureAudio(config: AudioEncoderConfig): Promise<{ success: boolean }> {\n try {\n // Create encoder if not exists (shouldn't happen if configure was called)\n if (!this.audioEncoder) {\n this.audioEncoder = new AudioChunkEncoder(config);\n await this.audioEncoder.initialize();\n } else {\n await this.audioEncoder.reconfigure(config);\n }\n\n // Notify configuration complete\n this.channel.notify('audio_configured', {\n codec: config.codec,\n sampleRate: config.sampleRate,\n numberOfChannels: config.numberOfChannels,\n bitrate: config.bitrate,\n });\n\n return { success: true };\n } catch (error: any) {\n throw {\n code: 'AUDIO_CONFIG_ERROR',\n message: error.message,\n };\n }\n }\n\n /**\n * Flush encoders and get buffered chunks\n */\n private async handleFlush(payload?: { type?: 'video' | 'audio' }): Promise<{\n videoChunks?: any[];\n audioChunks?: any[];\n }> {\n try {\n const result: any = {};\n\n if (!payload?.type || payload.type === 'video') {\n const chunks = await this.videoEncoder?.flush();\n if (chunks) {\n result.videoChunks = chunks;\n }\n }\n\n if (!payload?.type || payload.type === 'audio') {\n const chunks = await this.audioEncoder?.flush();\n if (chunks) {\n result.audioChunks = chunks;\n }\n }\n\n return result;\n } catch (error: any) {\n throw {\n code: 'FLUSH_ERROR',\n message: error.message,\n };\n }\n }\n\n /**\n * Reset encoders\n */\n private async handleReset(payload?: { type?: 'video' | 'audio' }): Promise<{ success: boolean }> {\n try {\n if (!payload?.type || payload.type === 'video') {\n await this.videoEncoder?.reset();\n }\n if (!payload?.type || payload.type === 'audio') {\n await this.audioEncoder?.reset();\n }\n\n // Notify reset complete\n this.channel.notify('reset_complete', {\n type: payload?.type || 'all',\n });\n\n return { success: true };\n } catch (error: any) {\n throw {\n code: 'RESET_ERROR',\n message: error.message,\n };\n }\n }\n\n /**\n * Get encoder statistics\n */\n private async handleGetStats(): Promise<{\n video?: any;\n audio?: any;\n }> {\n const stats: any = {};\n\n if (this.videoEncoder) {\n stats.video = {\n configured: this.videoEncoder.isReady,\n queueSize: this.videoEncoder.queueSize,\n };\n }\n\n if (this.audioEncoder) {\n stats.audio = {\n configured: this.audioEncoder.isReady,\n queueSize: this.audioEncoder.queueSize,\n };\n }\n\n return stats;\n }\n\n // Output and error handling is done via streams in the encoder itself\n // These placeholder methods can be implemented when needed for direct callback handling\n\n /**\n * Dispose worker and cleanup resources\n */\n private async handleDispose(): Promise<{ success: boolean }> {\n // Close encoders\n await this.videoEncoder?.close();\n await this.audioEncoder?.close();\n\n this.videoEncoder = null;\n this.audioEncoder = null;\n\n // Close connections\n this.cachePort?.close();\n this.cachePort = null;\n\n this.muxPort?.close();\n this.muxPort = null;\n\n for (const port of this.composePorts.values()) {\n port.close();\n }\n this.composePorts.clear();\n\n this.channel.state = WorkerState.Disposed;\n\n return { success: true };\n }\n}\n\n// Initialize worker\nconst worker = new EncodeWorker();\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 +0,0 @@
1
- {"version":3,"file":"MP4Muxer.js","sources":["../../../src/stages/mux/MP4Muxer.ts"],"sourcesContent":["// @ts-ignore - mp4box doesn't have proper TypeScript definitions\nimport * as MP4Box from 'mp4box';\nimport type { MuxConfig, MuxTrack } from './types';\nimport { usToTimescale, calculateTimescale, parseCodecString } from './utils';\n\ninterface MP4TrackOptions {\n timescale: number;\n type: string;\n nb_samples: number;\n width?: number;\n height?: number;\n channel_count?: number;\n samplerate?: number;\n hdlr?: string;\n name?: string;\n codec?: string;\n avcDecoderConfigRecord?: Uint8Array;\n hvcDecoderConfigRecord?: Uint8Array;\n av1DecoderConfigRecord?: Uint8Array;\n audioDecoderConfig?: Uint8Array;\n opusDecoderConfig?: Uint8Array;\n}\n\n/**\n * MP4 Muxer - Multiplex encoded chunks into MP4 container\n * Terminal stage that consumes encoded chunks and produces MP4 file\n */\nexport class MP4Muxer {\n private mp4boxFile: any;\n tracks = new Map<number, MuxTrack>();\n private mp4Tracks = new Map<number, number>(); // muxTrackId -> mp4boxTrackId\n\n // Expose output chunks directly instead of getter\n outputChunks: Uint8Array[] = [];\n\n // Expose total bytes written as computed property\n get totalBytesWritten(): number {\n return this.outputChunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);\n }\n\n // Expose finalized state\n isFinalized = false;\n\n private config: MuxConfig;\n\n constructor(config: MuxConfig = { container: 'mp4' }) {\n this.config = config;\n this.mp4boxFile = MP4Box.createFile();\n this.setupHandlers();\n this.initializeTracks();\n }\n\n private setupHandlers(): void {\n this.mp4boxFile.onError = (error: string) => {\n console.error('MP4Box error:', error);\n };\n\n // Called when MP4Box has data to write\n this.mp4boxFile.onSegment = (\n _id: number,\n _user: any,\n buffer: ArrayBuffer,\n _sampleNumber: number,\n _last: boolean\n ) => {\n const chunk = new Uint8Array(buffer);\n this.outputChunks.push(chunk);\n };\n\n // Handle initialization segment for fragmented MP4\n if (this.config.mp4?.fragmented) {\n this.mp4boxFile.onInitSegment = (_tracks: any[]) => {\n const initSegment = this.mp4boxFile.getInitializationSegment();\n if (initSegment) {\n const chunk = new Uint8Array(initSegment);\n this.outputChunks.push(chunk);\n }\n };\n }\n }\n\n private initializeTracks(): void {\n // Add video track if configured\n if (this.config.video) {\n const videoTrack: MuxTrack = {\n id: 1, // Default video track ID\n type: 'video',\n codec: this.config.video.codec,\n width: this.config.video.width,\n height: this.config.video.height,\n frameRate: this.config.video.frameRate,\n timescale: calculateTimescale(this.config.video.frameRate),\n codecConfig: this.config.video.description,\n };\n this.addTrack(videoTrack);\n }\n\n // Add audio track if configured\n if (this.config.audio) {\n const audioTrack: MuxTrack = {\n id: 2, // Default audio track ID\n type: 'audio',\n codec: this.config.audio.codec,\n sampleRate: this.config.audio.sampleRate,\n channelCount: this.config.audio.channelCount,\n timescale: this.config.audio.sampleRate,\n codecConfig: this.config.audio.description,\n };\n this.addTrack(audioTrack);\n }\n }\n\n private addTrack(track: MuxTrack): number {\n if (!this.mp4boxFile) {\n throw new Error('Muxer destroyed');\n }\n\n // Store track info\n this.tracks.set(track.id, track);\n\n // Create track options based on type\n const trackOptions =\n track.type === 'video'\n ? this.createVideoTrackOptions(track)\n : this.createAudioTrackOptions(track);\n\n // Add track to MP4Box\n const mp4TrackId = this.mp4boxFile.addTrack(trackOptions);\n this.mp4Tracks.set(track.id, mp4TrackId);\n\n // Configure track for fragmented mode if enabled\n if (this.config.mp4?.fragmented) {\n this.configureFragmentedTrack(mp4TrackId, track);\n }\n\n return track.id;\n }\n\n private configureFragmentedTrack(mp4TrackId: number, track: MuxTrack): void {\n const fragmentDuration = this.config.mp4?.fragmentDuration || 1_000_000; // 1 second default in microseconds\n\n let nbSamples: number;\n\n if (track.type === 'video') {\n // For video: calculate number of frames per fragment based on frame rate\n // nbSamples = fragment_duration_in_seconds * frame_rate\n const frameRate = track.frameRate || 30;\n const fragmentDurationInSeconds = fragmentDuration / 1_000_000;\n nbSamples = Math.ceil(fragmentDurationInSeconds * frameRate);\n } else {\n // For audio: calculate number of samples per fragment\n // Audio typically has many more samples (e.g., 48000 Hz sample rate)\n // A reasonable fragment might contain ~1 second of audio samples\n const sampleRate = track.sampleRate || 48000;\n const fragmentDurationInSeconds = fragmentDuration / 1_000_000;\n // For compressed audio (AAC, Opus), each \"sample\" is actually a frame containing multiple PCM samples\n // AAC frame typically contains 1024 PCM samples, so at 48kHz: 48000/1024 ≈ 47 frames per second\n const framesPerSecond = sampleRate / 1024; // Assuming AAC-like frame size\n nbSamples = Math.ceil(fragmentDurationInSeconds * framesPerSecond);\n }\n\n /**\n * setSegmentOptions parameters:\n * @param trackId - The track ID to configure\n * @param user - User data (passed to callbacks, we don't use it)\n * @param options - Segmentation options:\n * - nbSamples: Number of samples per segment/fragment\n * - rapAlignement: Random Access Point alignment - ensures segments start with keyframes\n * This is critical for seeking and adaptive streaming (HLS/DASH)\n */\n this.mp4boxFile.setSegmentOptions(mp4TrackId, null, {\n nbSamples,\n rapAlignement: true, // Align segments to keyframes for proper seeking\n });\n }\n\n private createVideoTrackOptions(track: MuxTrack): MP4TrackOptions {\n const options: MP4TrackOptions = {\n timescale: track.timescale || calculateTimescale(track.frameRate),\n type: 'video',\n nb_samples: 0,\n width: track.width,\n height: track.height,\n };\n\n // Configure codec-specific options\n const codecInfo = parseCodecString(track.codec);\n\n switch (codecInfo.codec) {\n case 'avc1':\n options.codec = 'avc1';\n options.avcDecoderConfigRecord = track.codecConfig;\n break;\n case 'hev1':\n case 'hvc1':\n options.codec = 'hvc1';\n options.hvcDecoderConfigRecord = track.codecConfig;\n break;\n case 'av01':\n options.codec = 'av01';\n options.av1DecoderConfigRecord = track.codecConfig;\n break;\n case 'vp09':\n options.codec = 'vp09';\n // VP9 specific configuration\n break;\n default:\n throw new Error(`Unsupported video codec: ${track.codec}`);\n }\n\n return options;\n }\n\n private createAudioTrackOptions(track: MuxTrack): MP4TrackOptions {\n const options: MP4TrackOptions = {\n timescale: track.timescale || track.sampleRate || 48000,\n type: 'audio',\n nb_samples: 0,\n channel_count: track.channelCount,\n samplerate: track.sampleRate,\n hdlr: 'soun',\n name: 'SoundHandler',\n };\n\n // Configure codec-specific options\n const codecInfo = parseCodecString(track.codec);\n\n switch (codecInfo.codec) {\n case 'mp4a':\n options.codec = 'mp4a.40.2'; // AAC-LC\n options.audioDecoderConfig = track.codecConfig;\n break;\n case 'opus':\n options.codec = 'Opus';\n options.opusDecoderConfig = track.codecConfig;\n break;\n case 'mp3':\n options.codec = 'mp3';\n break;\n case 'ac-3':\n options.codec = 'ac-3';\n break;\n case 'ec-3':\n options.codec = 'ec-3';\n break;\n default:\n throw new Error(`Unsupported audio codec: ${track.codec}`);\n }\n\n return options;\n }\n\n /**\n * Write video chunk to muxer\n */\n writeVideoChunk(chunk: EncodedVideoChunk, trackId: number = 1): void {\n const mp4TrackId = this.mp4Tracks.get(trackId);\n if (!mp4TrackId) {\n throw new Error(`Track ${trackId} not found`);\n }\n\n const track = this.tracks.get(trackId);\n if (!track) {\n throw new Error(`Track info for ${trackId} not found`);\n }\n\n // Create buffer for chunk data\n const data = new Uint8Array(chunk.byteLength);\n chunk.copyTo(data);\n\n // Convert timestamps to timescale units\n const pts = usToTimescale(chunk.timestamp, track.timescale);\n const dts =\n chunk.timestamp !== undefined ? usToTimescale(chunk.timestamp, track.timescale) : pts;\n const duration = usToTimescale(chunk.duration || 0, track.timescale);\n\n // Create MP4Box sample\n const mp4Sample: any = {\n data: data,\n pts: pts,\n dts: dts,\n duration: duration,\n is_sync: chunk.type === 'key',\n };\n\n // Add sample to track\n this.mp4boxFile.addSample(mp4TrackId, data, mp4Sample);\n }\n\n /**\n * Write audio chunk to muxer\n */\n writeAudioChunk(chunk: EncodedAudioChunk, trackId: number = 2): void {\n const mp4TrackId = this.mp4Tracks.get(trackId);\n if (!mp4TrackId) {\n throw new Error(`Track ${trackId} not found`);\n }\n\n const track = this.tracks.get(trackId);\n if (!track) {\n throw new Error(`Track info for ${trackId} not found`);\n }\n\n // Create buffer for chunk data\n const data = new Uint8Array(chunk.byteLength);\n chunk.copyTo(data);\n\n // Convert timestamps to timescale units\n const pts = usToTimescale(chunk.timestamp, track.timescale);\n const duration = usToTimescale(chunk.duration || 0, track.timescale);\n\n // Create MP4Box sample\n const mp4Sample: any = {\n data: data,\n pts: pts,\n dts: pts,\n duration: duration,\n is_sync: true, // Audio chunks are always sync samples\n };\n\n // Add sample to track\n this.mp4boxFile.addSample(mp4TrackId, data, mp4Sample);\n }\n\n /**\n * Flush pending samples\n */\n flush(): void {\n if (this.mp4boxFile) {\n this.mp4boxFile.flush();\n }\n }\n\n /**\n * Finalize and get the output\n */\n finalize(): Blob {\n if (this.isFinalized) {\n throw new Error('Muxer already finalized');\n }\n\n if (!this.mp4boxFile) {\n throw new Error('Muxer destroyed');\n }\n\n // Flush any remaining samples\n this.flush();\n\n // For non-fragmented mode, close to write moov box\n if (!this.config.mp4?.fragmented) {\n this.mp4boxFile.close();\n }\n\n this.isFinalized = true;\n\n // Return the muxed MP4 as a Blob\n return new Blob(this.outputChunks as BlobPart[], { type: 'video/mp4' });\n }\n\n /**\n * Clear output chunks (after they've been consumed)\n */\n clearOutputChunks(): void {\n this.outputChunks = [];\n }\n\n destroy(): void {\n this.mp4boxFile?.stop?.();\n this.mp4boxFile = null;\n this.tracks.clear();\n this.mp4Tracks.clear();\n this.outputChunks = [];\n }\n}\n"],"names":["MP4Box.createFile"],"mappings":"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"}
@@ -1,65 +0,0 @@
1
- /**
2
- * MuxWorker - Final stage for video/audio multiplexing
3
- * Combines encoded video and audio tracks into container format (MP4)
4
- *
5
- * Pipeline: CacheManager → MuxWorker → Export (Blob/OPFS)
6
- *
7
- * Features:
8
- * - MP4 container muxing with mp4box.js
9
- * - Stream-based processing from L2 cache
10
- * - Support for fragmented MP4 (fMP4) for streaming
11
- * - Direct blob output for download
12
- */
13
- export declare class MuxWorkerImpl {
14
- private channel;
15
- private muxer;
16
- private config;
17
- constructor();
18
- private setupHandlers;
19
- /**
20
- * Unified connect handler used by stream pipeline
21
- */
22
- private handleUnifiedConnect;
23
- /**
24
- * Connect to encoder worker to receive encoded chunks
25
- */
26
- /**
27
- * Configure muxer with container settings
28
- * @param payload.config - Muxer configuration including tracks and output format
29
- * @param payload.initial - If true, initialize worker state; otherwise just update config
30
- */
31
- private handleConfigure;
32
- /**
33
- * Write single video chunk
34
- */
35
- private handleWriteVideoChunk;
36
- /**
37
- * Write single audio chunk
38
- */
39
- private handleWriteAudioChunk;
40
- /**
41
- * Handle input stream from CacheManager (L2 encoded chunks)
42
- * This is the main export path where CacheManager sends encoded chunks
43
- */
44
- private handleInputStream;
45
- /**
46
- * Flush pending samples
47
- */
48
- private handleFlush;
49
- /**
50
- * Finalize muxing and get output
51
- * This completes the export process
52
- */
53
- private handleFinalize;
54
- /**
55
- * Get muxer statistics
56
- */
57
- private handleGetStats;
58
- /**
59
- * Dispose worker and cleanup resources
60
- */
61
- private handleDispose;
62
- }
63
- declare const _default: null;
64
- export default _default;
65
- //# sourceMappingURL=mux.worker.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mux.worker.d.ts","sourceRoot":"","sources":["../../../src/stages/mux/mux.worker.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;GAWG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,KAAK,CAAyB;IACtC,OAAO,CAAC,MAAM,CAA0B;;IAYxC,OAAO,CAAC,aAAa;IAiBrB;;OAEG;YACW,oBAAoB;IASlC;;OAEG;IAUH;;;;OAIG;YACW,eAAe;IAsD7B;;OAEG;YACW,qBAAqB;IAgBnC;;OAEG;YACW,qBAAqB;IAgBnC;;;OAGG;YACW,iBAAiB;IAwD/B;;OAEG;YACW,WAAW;IAYzB;;;OAGG;YACW,cAAc;IA2B5B;;OAEG;YACW,cAAc;IAkB5B;;OAEG;YACW,aAAa;CAU5B;;AAUD,wBAAoB"}
@@ -1,219 +0,0 @@
1
- import { WorkerChannel } from "../../worker/WorkerChannel.js";
2
- import { WorkerMessageType, WorkerState } from "../../worker/types.js";
3
- import { MP4Muxer } from "./MP4Muxer.js";
4
- class MuxWorkerImpl {
5
- channel;
6
- muxer = null;
7
- config = null;
8
- constructor() {
9
- this.channel = new WorkerChannel(self, {
10
- name: "MuxWorker",
11
- timeout: 6e4
12
- // 60s for export operations
13
- });
14
- this.setupHandlers();
15
- }
16
- setupHandlers() {
17
- this.channel.registerHandler("configure", this.handleConfigure.bind(this));
18
- this.channel.registerHandler("connect", this.handleUnifiedConnect.bind(this));
19
- this.channel.registerHandler("write_video_chunk", this.handleWriteVideoChunk.bind(this));
20
- this.channel.registerHandler("write_audio_chunk", this.handleWriteAudioChunk.bind(this));
21
- this.channel.registerHandler("flush", this.handleFlush.bind(this));
22
- this.channel.registerHandler("finalize", this.handleFinalize.bind(this));
23
- this.channel.registerHandler("get_stats", this.handleGetStats.bind(this));
24
- this.channel.registerHandler(WorkerMessageType.Dispose, this.handleDispose.bind(this));
25
- this.channel.receiveStream(this.handleInputStream.bind(this));
26
- }
27
- /**
28
- * Unified connect handler used by stream pipeline
29
- */
30
- async handleUnifiedConnect(_payload) {
31
- return { success: true };
32
- }
33
- /**
34
- * Connect to encoder worker to receive encoded chunks
35
- */
36
- // private async handleConnectEncoder(_payload: {
37
- // port: MessagePort;
38
- // }): Promise<{ success: boolean }> {
39
- // // Store the port for receiving encoded chunks
40
- // // The actual stream handling is done via receiveStream in handleInputStream
41
- // // This connection allows direct communication with the encoder worker
42
- // return { success: true };
43
- // }
44
- /**
45
- * Configure muxer with container settings
46
- * @param payload.config - Muxer configuration including tracks and output format
47
- * @param payload.initial - If true, initialize worker state; otherwise just update config
48
- */
49
- async handleConfigure(payload) {
50
- const { config, initial = false } = payload;
51
- try {
52
- if (initial) {
53
- this.channel.state = WorkerState.Ready;
54
- if (this.muxer) {
55
- this.muxer.destroy();
56
- }
57
- this.config = config;
58
- this.muxer = new MP4Muxer(config);
59
- const trackCount = (config.video ? 1 : 0) + (config.audio ? 1 : 0);
60
- this.channel.notify("configured", {
61
- container: config.container,
62
- hasVideo: !!config.video,
63
- hasAudio: !!config.audio,
64
- fragmented: !!config.mp4?.fragmented,
65
- trackCount
66
- });
67
- return { success: true, tracks: trackCount };
68
- } else {
69
- if (!this.muxer) {
70
- throw {
71
- code: "NOT_INITIALIZED",
72
- message: "Muxer not initialized. Call configure with initial=true first"
73
- };
74
- }
75
- this.config = { ...this.config, ...config };
76
- return { success: true };
77
- }
78
- } catch (error) {
79
- throw {
80
- code: error.code || "CONFIG_ERROR",
81
- message: error.message
82
- };
83
- }
84
- }
85
- /**
86
- * Write single video chunk
87
- */
88
- async handleWriteVideoChunk(payload) {
89
- if (!this.muxer) {
90
- throw {
91
- code: "NOT_INITIALIZED",
92
- message: "Muxer not initialized"
93
- };
94
- }
95
- this.muxer.writeVideoChunk(payload.chunk, payload.trackId);
96
- return { bytesWritten: this.muxer.totalBytesWritten };
97
- }
98
- /**
99
- * Write single audio chunk
100
- */
101
- async handleWriteAudioChunk(payload) {
102
- if (!this.muxer) {
103
- throw {
104
- code: "NOT_INITIALIZED",
105
- message: "Muxer not initialized"
106
- };
107
- }
108
- this.muxer.writeAudioChunk(payload.chunk, payload.trackId);
109
- return { bytesWritten: this.muxer.totalBytesWritten };
110
- }
111
- /**
112
- * Handle input stream from CacheManager (L2 encoded chunks)
113
- * This is the main export path where CacheManager sends encoded chunks
114
- */
115
- async handleInputStream(stream, metadata) {
116
- if (!this.muxer) {
117
- throw new Error("Muxer not configured");
118
- }
119
- const reader = stream.getReader();
120
- let chunksProcessed = 0;
121
- try {
122
- this.channel.state = WorkerState.Processing;
123
- while (true) {
124
- const { done, value } = await reader.read();
125
- if (done) break;
126
- const isVideoChunk = metadata?.type === "video" || value.type === "key" || value.type === "delta";
127
- if (isVideoChunk) {
128
- this.muxer.writeVideoChunk(value, metadata?.trackId || 1);
129
- } else {
130
- this.muxer.writeAudioChunk(value, metadata?.trackId || 2);
131
- }
132
- chunksProcessed++;
133
- if (chunksProcessed % 100 === 0) {
134
- this.channel.notify("mux_progress", {
135
- chunksProcessed,
136
- bytesWritten: this.muxer.totalBytesWritten
137
- });
138
- }
139
- }
140
- this.muxer.flush();
141
- this.channel.state = WorkerState.Ready;
142
- this.channel.notify("mux_complete", {
143
- chunksProcessed,
144
- bytesWritten: this.muxer.totalBytesWritten
145
- });
146
- } finally {
147
- reader.releaseLock();
148
- }
149
- }
150
- /**
151
- * Flush pending samples
152
- */
153
- async handleFlush() {
154
- if (!this.muxer) {
155
- throw {
156
- code: "NOT_INITIALIZED",
157
- message: "Muxer not initialized"
158
- };
159
- }
160
- this.muxer.flush();
161
- return { success: true };
162
- }
163
- /**
164
- * Finalize muxing and get output
165
- * This completes the export process
166
- */
167
- async handleFinalize() {
168
- if (!this.muxer) {
169
- throw {
170
- code: "NOT_INITIALIZED",
171
- message: "Muxer not initialized"
172
- };
173
- }
174
- const blob = this.muxer.finalize();
175
- this.channel.notify("export_done", {
176
- blob,
177
- totalBytes: blob.size
178
- });
179
- return {
180
- success: true,
181
- blob,
182
- totalBytes: blob.size
183
- };
184
- }
185
- /**
186
- * Get muxer statistics
187
- */
188
- async handleGetStats() {
189
- if (!this.muxer) {
190
- return { state: this.channel.state };
191
- }
192
- return {
193
- bytesWritten: this.muxer.totalBytesWritten,
194
- chunksCount: this.muxer.outputChunks.length,
195
- isFinalized: this.muxer.isFinalized,
196
- state: this.channel.state
197
- };
198
- }
199
- /**
200
- * Dispose worker and cleanup resources
201
- */
202
- async handleDispose() {
203
- this.muxer?.destroy();
204
- this.muxer = null;
205
- this.config = null;
206
- this.channel.state = WorkerState.Disposed;
207
- return { success: true };
208
- }
209
- }
210
- const worker = new MuxWorkerImpl();
211
- self.addEventListener("beforeunload", () => {
212
- worker["handleDispose"]();
213
- });
214
- const mux_worker = null;
215
- export {
216
- MuxWorkerImpl,
217
- mux_worker as default
218
- };
219
- //# sourceMappingURL=mux.worker.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mux.worker.js","sources":["../../../src/stages/mux/mux.worker.ts"],"sourcesContent":["import { WorkerChannel } from '../../worker/WorkerChannel';\nimport { WorkerMessageType, WorkerState } from '../../worker/types';\nimport { MP4Muxer } from './MP4Muxer';\nimport type { MuxConfig } from './types';\n\n/**\n * MuxWorker - Final stage for video/audio multiplexing\n * Combines encoded video and audio tracks into container format (MP4)\n *\n * Pipeline: CacheManager → MuxWorker → Export (Blob/OPFS)\n *\n * Features:\n * - MP4 container muxing with mp4box.js\n * - Stream-based processing from L2 cache\n * - Support for fragmented MP4 (fMP4) for streaming\n * - Direct blob output for download\n */\nexport class MuxWorkerImpl {\n private channel: WorkerChannel;\n private muxer: MP4Muxer | null = null;\n private config: MuxConfig | null = null;\n\n constructor() {\n // Initialize WorkerChannel\n this.channel = new WorkerChannel(self as any, {\n name: 'MuxWorker',\n timeout: 60000, // 60s for export operations\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.handleUnifiedConnect.bind(this));\n // Unified stream connect only\n this.channel.registerHandler('write_video_chunk', this.handleWriteVideoChunk.bind(this));\n this.channel.registerHandler('write_audio_chunk', this.handleWriteAudioChunk.bind(this));\n this.channel.registerHandler('flush', this.handleFlush.bind(this));\n this.channel.registerHandler('finalize', this.handleFinalize.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 CacheManager (L2 encoded chunks)\n this.channel.receiveStream(this.handleInputStream.bind(this));\n }\n\n /**\n * Unified connect handler used by stream pipeline\n */\n private async handleUnifiedConnect(_payload: {\n direction: 'upstream';\n port: MessagePort;\n streamType: 'video' | 'audio' | 'frame' | 'chunk';\n }): Promise<{ success: boolean }> {\n // Mux receives encoded chunks via receiveStream already\n return { success: true };\n }\n\n /**\n * Connect to encoder worker to receive encoded chunks\n */\n // private async handleConnectEncoder(_payload: {\n // port: MessagePort;\n // }): Promise<{ success: boolean }> {\n // // Store the port for receiving encoded chunks\n // // The actual stream handling is done via receiveStream in handleInputStream\n // // This connection allows direct communication with the encoder worker\n // return { success: true };\n // }\n\n /**\n * Configure muxer with container settings\n * @param payload.config - Muxer configuration including tracks and output format\n * @param payload.initial - If true, initialize worker state; otherwise just update config\n */\n private async handleConfigure(payload: {\n config: MuxConfig;\n initial?: boolean;\n }): Promise<{ success: boolean; tracks?: number }> {\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 muxer instance\n if (this.muxer) {\n this.muxer.destroy();\n }\n\n this.config = config;\n this.muxer = new MP4Muxer(config);\n\n const trackCount = (config.video ? 1 : 0) + (config.audio ? 1 : 0);\n\n // Notify configuration complete\n this.channel.notify('configured', {\n container: config.container,\n hasVideo: !!config.video,\n hasAudio: !!config.audio,\n fragmented: !!config.mp4?.fragmented,\n trackCount,\n });\n\n return { success: true, tracks: trackCount };\n } else {\n // Update configuration only\n if (!this.muxer) {\n throw {\n code: 'NOT_INITIALIZED',\n message: 'Muxer not initialized. Call configure with initial=true first',\n };\n }\n\n // MP4Muxer doesn't support runtime config updates\n // Would need to recreate for changes\n this.config = { ...this.config, ...config };\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 * Write single video chunk\n */\n private async handleWriteVideoChunk(payload: {\n chunk: EncodedVideoChunk;\n trackId?: number;\n }): Promise<{ bytesWritten: number }> {\n if (!this.muxer) {\n throw {\n code: 'NOT_INITIALIZED',\n message: 'Muxer not initialized',\n };\n }\n\n this.muxer.writeVideoChunk(payload.chunk, payload.trackId);\n\n return { bytesWritten: this.muxer.totalBytesWritten };\n }\n\n /**\n * Write single audio chunk\n */\n private async handleWriteAudioChunk(payload: {\n chunk: EncodedAudioChunk;\n trackId?: number;\n }): Promise<{ bytesWritten: number }> {\n if (!this.muxer) {\n throw {\n code: 'NOT_INITIALIZED',\n message: 'Muxer not initialized',\n };\n }\n\n this.muxer.writeAudioChunk(payload.chunk, payload.trackId);\n\n return { bytesWritten: this.muxer.totalBytesWritten };\n }\n\n /**\n * Handle input stream from CacheManager (L2 encoded chunks)\n * This is the main export path where CacheManager sends encoded chunks\n */\n private async handleInputStream(\n stream: ReadableStream<EncodedVideoChunk | EncodedAudioChunk>,\n metadata?: Record<string, any>\n ): Promise<void> {\n if (!this.muxer) {\n throw new Error('Muxer not configured');\n }\n\n const reader = stream.getReader();\n let chunksProcessed = 0;\n\n try {\n this.channel.state = WorkerState.Processing;\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n // Determine chunk type and write accordingly\n const isVideoChunk =\n metadata?.type === 'video' ||\n (value as any).type === 'key' ||\n (value as any).type === 'delta';\n\n if (isVideoChunk) {\n this.muxer.writeVideoChunk(value as EncodedVideoChunk, metadata?.trackId || 1);\n } else {\n this.muxer.writeAudioChunk(value as EncodedAudioChunk, metadata?.trackId || 2);\n }\n\n chunksProcessed++;\n\n // Report progress periodically\n if (chunksProcessed % 100 === 0) {\n this.channel.notify('mux_progress', {\n chunksProcessed,\n bytesWritten: this.muxer.totalBytesWritten,\n });\n }\n }\n\n // Flush after stream processing\n this.muxer.flush();\n\n this.channel.state = WorkerState.Ready;\n\n // Notify muxing complete\n this.channel.notify('mux_complete', {\n chunksProcessed,\n bytesWritten: this.muxer.totalBytesWritten,\n });\n } finally {\n reader.releaseLock();\n }\n }\n\n /**\n * Flush pending samples\n */\n private async handleFlush(): Promise<{ success: boolean }> {\n if (!this.muxer) {\n throw {\n code: 'NOT_INITIALIZED',\n message: 'Muxer not initialized',\n };\n }\n\n this.muxer.flush();\n return { success: true };\n }\n\n /**\n * Finalize muxing and get output\n * This completes the export process\n */\n private async handleFinalize(): Promise<{\n success: boolean;\n blob: Blob;\n totalBytes: number;\n }> {\n if (!this.muxer) {\n throw {\n code: 'NOT_INITIALIZED',\n message: 'Muxer not initialized',\n };\n }\n\n const blob = this.muxer.finalize();\n\n // Notify export completion as per architecture doc\n this.channel.notify('export_done', {\n blob,\n totalBytes: blob.size,\n });\n\n return {\n success: true,\n blob,\n totalBytes: blob.size,\n };\n }\n\n /**\n * Get muxer statistics\n */\n private async handleGetStats(): Promise<{\n bytesWritten?: number;\n chunksCount?: number;\n isFinalized?: boolean;\n state?: WorkerState;\n }> {\n if (!this.muxer) {\n return { state: this.channel.state };\n }\n\n return {\n bytesWritten: this.muxer.totalBytesWritten,\n chunksCount: this.muxer.outputChunks.length,\n isFinalized: this.muxer.isFinalized,\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 muxer\n this.muxer?.destroy();\n this.muxer = null;\n this.config = null;\n\n this.channel.state = WorkerState.Disposed;\n\n return { success: true };\n }\n}\n\n// Initialize worker\nconst worker = new MuxWorkerImpl();\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,34 +0,0 @@
1
- function usToTimescale(microseconds, timescale) {
2
- return Math.round(microseconds * timescale / 1e6);
3
- }
4
- function calculateTimescale(frameRate) {
5
- if (!frameRate) return 9e4;
6
- const commonRates = {
7
- 24: 24e3,
8
- 25: 25e3,
9
- 30: 3e4,
10
- 50: 5e4,
11
- 60: 6e4
12
- };
13
- return commonRates[Math.round(frameRate)] || 9e4;
14
- }
15
- function parseCodecString(codec) {
16
- const parts = codec.split(".");
17
- const result = { codec: parts[0] };
18
- if (parts[0] === "avc1" || parts[0] === "hev1" || parts[0] === "hvc1") {
19
- if (parts[1]) {
20
- result.profile = parts[1].substring(0, 2);
21
- result.level = parts[1].substring(4, 6);
22
- }
23
- } else if (parts[0] === "mp4a") {
24
- if (parts[1]) result.objectType = parts[1];
25
- if (parts[2]) result.audioObjectType = parts[2];
26
- }
27
- return result;
28
- }
29
- export {
30
- calculateTimescale,
31
- parseCodecString,
32
- usToTimescale
33
- };
34
- //# sourceMappingURL=utils.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"utils.js","sources":["../../../src/stages/mux/utils.ts"],"sourcesContent":["import type { EncodedChunkData, MuxSample } from './types';\n\n/**\n * Convert WebCodecs EncodedChunk to MuxSample\n */\nexport function encodedChunkToMuxSample(data: EncodedChunkData): MuxSample {\n const { chunk, trackId, type } = data;\n\n // Create buffer for chunk data\n const buffer = new Uint8Array(chunk.byteLength);\n chunk.copyTo(buffer);\n\n return {\n trackId,\n data: buffer,\n timestamp: chunk.timestamp,\n duration: chunk.duration || 0,\n isKeyFrame: type === 'video' ? (chunk as EncodedVideoChunk).type === 'key' : true, // Audio samples are always \"key frames\"\n };\n}\n\n/**\n * Convert microseconds to timescale units\n */\nexport function usToTimescale(microseconds: number, timescale: number): number {\n return Math.round((microseconds * timescale) / 1_000_000);\n}\n\n/**\n * Convert timescale units to microseconds\n */\nexport function timescaleToUs(units: number, timescale: number): number {\n return Math.round((units * 1_000_000) / timescale);\n}\n\n/**\n * Calculate optimal timescale for given frame rate\n */\nexport function calculateTimescale(frameRate?: number): number {\n if (!frameRate) return 90000; // Default for video\n\n // Common frame rates and their optimal timescales\n const commonRates: Record<number, number> = {\n 24: 24000,\n 25: 25000,\n 30: 30000,\n 50: 50000,\n 60: 60000,\n };\n\n return commonRates[Math.round(frameRate)] || 90000;\n}\n\n/**\n * Parse codec string to extract codec parameters\n * e.g., 'avc1.42E01E' -> { codec: 'avc1', profile: '42', level: '1E' }\n */\nexport function parseCodecString(codec: string): {\n codec: string;\n profile?: string;\n level?: string;\n} {\n const parts = codec.split('.');\n const result: any = { codec: parts[0] };\n\n if (parts[0] === 'avc1' || parts[0] === 'hev1' || parts[0] === 'hvc1') {\n // H.264/H.265 codec string format\n if (parts[1]) {\n result.profile = parts[1].substring(0, 2);\n result.level = parts[1].substring(4, 6);\n }\n } else if (parts[0] === 'mp4a') {\n // AAC codec string format\n if (parts[1]) result.objectType = parts[1];\n if (parts[2]) result.audioObjectType = parts[2];\n }\n\n return result;\n}\n\n/**\n * Create MP4 file brands based on codec\n */\nexport function createBrands(codecs: string[]): string[] {\n const brands = new Set<string>(['isom', 'iso2']);\n\n for (const codec of codecs) {\n if (codec.startsWith('avc1')) {\n brands.add('avc1');\n brands.add('mp41');\n } else if (codec.startsWith('hev1') || codec.startsWith('hvc1')) {\n brands.add('hev1');\n brands.add('hvc1');\n } else if (codec.startsWith('av01')) {\n brands.add('av01');\n } else if (codec.startsWith('mp4a')) {\n brands.add('mp41');\n }\n }\n\n return Array.from(brands);\n}\n"],"names":[],"mappings":"AAwBO,SAAS,cAAc,cAAsB,WAA2B;AAC7E,SAAO,KAAK,MAAO,eAAe,YAAa,GAAS;AAC1D;AAYO,SAAS,mBAAmB,WAA4B;AAC7D,MAAI,CAAC,UAAW,QAAO;AAGvB,QAAM,cAAsC;AAAA,IAC1C,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,EAAA;AAGN,SAAO,YAAY,KAAK,MAAM,SAAS,CAAC,KAAK;AAC/C;AAMO,SAAS,iBAAiB,OAI/B;AACA,QAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,QAAM,SAAc,EAAE,OAAO,MAAM,CAAC,EAAA;AAEpC,MAAI,MAAM,CAAC,MAAM,UAAU,MAAM,CAAC,MAAM,UAAU,MAAM,CAAC,MAAM,QAAQ;AAErE,QAAI,MAAM,CAAC,GAAG;AACZ,aAAO,UAAU,MAAM,CAAC,EAAE,UAAU,GAAG,CAAC;AACxC,aAAO,QAAQ,MAAM,CAAC,EAAE,UAAU,GAAG,CAAC;AAAA,IACxC;AAAA,EACF,WAAW,MAAM,CAAC,MAAM,QAAQ;AAE9B,QAAI,MAAM,CAAC,EAAG,QAAO,aAAa,MAAM,CAAC;AACzC,QAAI,MAAM,CAAC,EAAG,QAAO,kBAAkB,MAAM,CAAC;AAAA,EAChD;AAEA,SAAO;AACT;"}