@meframe/core 0.0.17 → 0.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/Meframe.d.ts.map +1 -1
  2. package/dist/Meframe.js +2 -2
  3. package/dist/Meframe.js.map +1 -1
  4. package/dist/controllers/PlaybackController.d.ts +1 -1
  5. package/dist/controllers/PlaybackController.d.ts.map +1 -1
  6. package/dist/controllers/PlaybackController.js.map +1 -1
  7. package/dist/controllers/PreRenderService.d.ts +1 -4
  8. package/dist/controllers/PreRenderService.d.ts.map +1 -1
  9. package/dist/controllers/PreRenderService.js +7 -40
  10. package/dist/controllers/PreRenderService.js.map +1 -1
  11. package/dist/controllers/types.d.ts +0 -4
  12. package/dist/controllers/types.d.ts.map +1 -1
  13. package/dist/model/CompositionModel.d.ts.map +1 -1
  14. package/dist/model/CompositionModel.js +2 -0
  15. package/dist/model/CompositionModel.js.map +1 -1
  16. package/dist/model/patch.js +1 -25
  17. package/dist/model/patch.js.map +1 -1
  18. package/dist/{stages/compose → orchestrator}/GlobalAudioSession.d.ts +13 -7
  19. package/dist/orchestrator/GlobalAudioSession.d.ts.map +1 -0
  20. package/dist/{stages/compose → orchestrator}/GlobalAudioSession.js +55 -4
  21. package/dist/orchestrator/GlobalAudioSession.js.map +1 -0
  22. package/dist/orchestrator/Orchestrator.d.ts +2 -8
  23. package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
  24. package/dist/orchestrator/Orchestrator.js +17 -9
  25. package/dist/orchestrator/Orchestrator.js.map +1 -1
  26. package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
  27. package/dist/orchestrator/VideoClipSession.js +14 -2
  28. package/dist/orchestrator/VideoClipSession.js.map +1 -1
  29. package/dist/stages/compose/OfflineAudioMixer.d.ts.map +1 -1
  30. package/dist/stages/compose/OfflineAudioMixer.js +1 -9
  31. package/dist/stages/compose/OfflineAudioMixer.js.map +1 -1
  32. package/dist/stages/decode/AudioChunkDecoder.js +169 -0
  33. package/dist/stages/decode/AudioChunkDecoder.js.map +1 -0
  34. package/dist/stages/demux/MP4Demuxer.d.ts +5 -0
  35. package/dist/stages/demux/MP4Demuxer.d.ts.map +1 -1
  36. package/dist/stages/demux/types.d.ts +6 -0
  37. package/dist/stages/demux/types.d.ts.map +1 -1
  38. package/dist/stages/mux/MuxManager.d.ts +1 -1
  39. package/dist/stages/mux/MuxManager.d.ts.map +1 -1
  40. package/dist/stages/mux/MuxManager.js +10 -12
  41. package/dist/stages/mux/MuxManager.js.map +1 -1
  42. package/dist/workers/MP4Demuxer.js +8 -1
  43. package/dist/workers/MP4Demuxer.js.map +1 -1
  44. package/dist/workers/stages/demux/video-demux.worker.js +92 -54
  45. package/dist/workers/stages/demux/video-demux.worker.js.map +1 -1
  46. package/package.json +1 -1
  47. package/dist/stages/compose/GlobalAudioSession.d.ts.map +0 -1
  48. package/dist/stages/compose/GlobalAudioSession.js.map +0 -1
  49. package/dist/stages/demux/aac-esds-extractor.d.ts +0 -7
  50. package/dist/stages/demux/aac-esds-extractor.d.ts.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AudioChunkDecoder.js","sources":["../../../src/stages/decode/AudioChunkDecoder.ts"],"sourcesContent":["import { AudioDecoderConfig } from './types';\nimport { BaseDecoder } from './BaseDecoder';\n\n/**\n * Audio decoder with streaming support\n * Extends BaseDecoder for common WebCodecs operations\n */\nexport class AudioChunkDecoder extends BaseDecoder<\n AudioDecoder,\n AudioDecoderConfig,\n EncodedAudioChunk,\n AudioData\n> {\n // Default values\n private static readonly DEFAULT_HIGH_WATER_MARK = 20;\n private static readonly DEFAULT_DECODE_QUEUE_THRESHOLD = 16;\n\n // Exposed properties\n readonly trackId: string;\n\n // Backpressure configuration\n protected readonly highWaterMark: number;\n protected readonly decodeQueueThreshold: number;\n\n // Buffering support for delayed configuration\n private bufferedChunks: EncodedAudioChunk[] = [];\n private isProcessingBuffer: boolean = false;\n\n constructor(trackId: string, config?: Partial<AudioDecoderConfig>) {\n // Initialize with empty config, will be configured later\n super(config as AudioDecoderConfig);\n\n this.trackId = trackId;\n\n // Set backpressure configuration\n this.highWaterMark =\n config?.backpressure?.highWaterMark ?? AudioChunkDecoder.DEFAULT_HIGH_WATER_MARK;\n this.decodeQueueThreshold = AudioChunkDecoder.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<AudioDecoderConfig>): Promise<void> {\n // If decoder is not ready and we have codec info, configure it\n if (!this.isReady && config.codec) {\n await this.configure(config as AudioDecoderConfig);\n await this.processBufferedChunks();\n return;\n }\n\n // Note: AudioDecoder doesn't have many runtime-configurable options\n // Backpressure settings are readonly in this implementation\n // if (config.backpressure) {\n // console.warn('Backpressure settings cannot be changed at runtime');\n // }\n }\n\n // Override createStream to handle buffering\n override createStream(): TransformStream<EncodedAudioChunk, AudioData> {\n return new TransformStream<EncodedAudioChunk, AudioData>(\n {\n start: async (controller) => {\n this.controller = controller;\n // Don't initialize if no codec config yet\n if (this.config?.codec && !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: EncodedAudioChunk): Promise<void> {\n if (!this.decoder) {\n throw new Error('Decoder not initialized');\n }\n\n if (this.decoder.state !== 'configured') {\n console.error('[AudioChunkDecoder] 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(`[AudioChunkDecoder] decode error:`, error);\n throw error; // Re-throw to propagate\n }\n }\n\n // Implement abstract methods\n protected async isConfigSupported(config: AudioDecoderConfig): Promise<{ supported: boolean }> {\n const result = await AudioDecoder.isConfigSupported({\n codec: config.codec,\n sampleRate: config.sampleRate,\n numberOfChannels: config.numberOfChannels,\n });\n return { supported: result.supported ?? false };\n }\n\n protected createDecoder(init: {\n output: (data: AudioData) => void;\n error: (error: DOMException) => void;\n }): AudioDecoder {\n return new AudioDecoder(init);\n }\n\n protected getDecoderType(): string {\n return 'Audio';\n }\n\n protected async configureDecoder(config: AudioDecoderConfig): Promise<void> {\n if (!this.decoder) return;\n\n await this.decoder.configure({\n codec: config.codec,\n sampleRate: config.sampleRate,\n numberOfChannels: config.numberOfChannels,\n ...(config.description && { description: config.description }),\n });\n }\n\n protected decode(chunk: EncodedAudioChunk): 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: AudioDecoderConfig): Promise<void> {\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 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('[AudioChunkDecoder] 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"],"names":[],"mappings":"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"}
@@ -1,5 +1,10 @@
1
1
  import { DemuxConfig, TrackInfo } from './types';
2
2
 
3
+ /**
4
+ * Normalize audio codec string for WebCodecs compatibility
5
+ * Fixes incomplete AAC codec strings from mp4box.js
6
+ */
7
+ export declare function normalizeAudioCodec(codec?: string): string;
3
8
  /**
4
9
  * MP4 Demuxer - Extract encoded chunks from MP4 container
5
10
  * Simplified implementation following Stream API pattern
@@ -1 +1 @@
1
- {"version":3,"file":"MP4Demuxer.d.ts","sourceRoot":"","sources":["../../../src/stages/demux/MP4Demuxer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEtD;;;GAGG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,UAAU,CAAM;IACxB,MAAM,yBAAgC;IACtC,OAAO,UAAS;IAChB,OAAO,CAAC,eAAe,CAAC,CAAqD;IAC7E,OAAO,CAAC,eAAe,CAAC,CAAqD;IAC7E,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,eAAe,CAAC,CAAa;IACrC,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,oBAAoB,CAAuB;IACnD,OAAO,CAAC,oBAAoB,CAAuB;IACnD,OAAO,CAAC,SAAS,CAAkB;gBAEvB,MAAM,GAAE,WAAW,GAAG;QAAE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;KAAO;IAY/D,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAIvC,OAAO,CAAC,aAAa;IA4BrB,OAAO,CAAC,aAAa;IA8BrB,OAAO,CAAC,cAAc;IA2DtB,OAAO,CAAC,mBAAmB;IA0B3B,OAAO,CAAC,mBAAmB;IA0C3B;;OAEG;IACH,iBAAiB,IAAI,cAAc,CAAC,iBAAiB,CAAC;IAiBtD;;OAEG;IACH,iBAAiB,IAAI,cAAc,CAAC,iBAAiB,CAAC,GAAG,IAAI;IAqB7D;;;OAGG;IACH,iBAAiB,IAAI,cAAc,CAAC,UAAU,GAAG,WAAW,CAAC;IA2B7D,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAOrC;;OAEG;IACH,IAAI,cAAc,IAAI,SAAS,GAAG,SAAS,CAE1C;IAED;;OAEG;IACH,IAAI,cAAc,IAAI,SAAS,GAAG,SAAS,CAE1C;IAED,OAAO,IAAI,IAAI;CAQhB"}
1
+ {"version":3,"file":"MP4Demuxer.d.ts","sourceRoot":"","sources":["../../../src/stages/demux/MP4Demuxer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEtD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAU1D;AAED;;;GAGG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,UAAU,CAAM;IACxB,MAAM,yBAAgC;IACtC,OAAO,UAAS;IAChB,OAAO,CAAC,eAAe,CAAC,CAAqD;IAC7E,OAAO,CAAC,eAAe,CAAC,CAAqD;IAC7E,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,eAAe,CAAC,CAAa;IACrC,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,oBAAoB,CAAuB;IACnD,OAAO,CAAC,oBAAoB,CAAuB;IACnD,OAAO,CAAC,SAAS,CAAkB;gBAEvB,MAAM,GAAE,WAAW,GAAG;QAAE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;KAAO;IAY/D,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAIvC,OAAO,CAAC,aAAa;IA4BrB,OAAO,CAAC,aAAa;IA8BrB,OAAO,CAAC,cAAc;IA2DtB,OAAO,CAAC,mBAAmB;IA0B3B,OAAO,CAAC,mBAAmB;IA0C3B;;OAEG;IACH,iBAAiB,IAAI,cAAc,CAAC,iBAAiB,CAAC;IAiBtD;;OAEG;IACH,iBAAiB,IAAI,cAAc,CAAC,iBAAiB,CAAC,GAAG,IAAI;IAqB7D;;;OAGG;IACH,iBAAiB,IAAI,cAAc,CAAC,UAAU,GAAG,WAAW,CAAC;IA2B7D,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAOrC;;OAEG;IACH,IAAI,cAAc,IAAI,SAAS,GAAG,SAAS,CAE1C;IAED;;OAEG;IACH,IAAI,cAAc,IAAI,SAAS,GAAG,SAAS,CAE1C;IAED,OAAO,IAAI,IAAI;CAQhB"}
@@ -2,6 +2,12 @@ export interface DemuxConfig {
2
2
  codec?: string;
3
3
  trackId?: string;
4
4
  container?: 'mp4' | 'webm';
5
+ /**
6
+ * When true, the demux worker will emit the audio stream directly to the main thread
7
+ * via the worker channel, even if there is no audio downstream port connected.
8
+ * This is used for L2 audio caching without spinning up an AudioDecode worker.
9
+ */
10
+ emitAudioToMain?: boolean;
5
11
  highWaterMark?: number;
6
12
  streaming?: {
7
13
  batchSize?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/stages/demux/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/stages/demux/types.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { CompositionModel } from '../../model/CompositionModel';
2
2
  import { ExportOptions } from '../../types';
3
- import { GlobalAudioSession } from '../compose/GlobalAudioSession';
3
+ import { GlobalAudioSession } from '../../orchestrator/GlobalAudioSession';
4
4
  import { CacheManager } from '../../cache/CacheManager';
5
5
 
6
6
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"MuxManager.d.ts","sourceRoot":"","sources":["../../../src/stages/mux/MuxManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAEnE,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD;;;GAGG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,kBAAkB,CAAkD;IAC5E,OAAO,CAAC,eAAe,CAAkC;gBAGvD,YAAY,EAAE,YAAY,EAC1B,YAAY,EAAE,kBAAkB,EAChC,kBAAkB,EAAE,kBAAkB;IAOlC,MAAM,CAAC,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IA+C5E;;;OAGG;YACW,iBAAiB;YAoCjB,gBAAgB;YAkDhB,gBAAgB;YAwDhB,eAAe;CAkB9B"}
1
+ {"version":3,"file":"MuxManager.d.ts","sourceRoot":"","sources":["../../../src/stages/mux/MuxManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAE3E,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD;;;GAGG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,kBAAkB,CAAkD;IAC5E,OAAO,CAAC,eAAe,CAAkC;gBAGvD,YAAY,EAAE,YAAY,EAC1B,YAAY,EAAE,kBAAkB,EAChC,kBAAkB,EAAE,kBAAkB;IAOlC,MAAM,CAAC,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IA8C5E;;;OAGG;YACW,iBAAiB;YAoCjB,gBAAgB;YAkDhB,gBAAgB;YAuDhB,eAAe;CAkB9B"}
@@ -15,20 +15,20 @@ class MuxManager {
15
15
  if (!videoTrack || videoTrack.clips.length === 0) {
16
16
  throw new Error("No video clips in composition");
17
17
  }
18
- const sortedClips = [...videoTrack.clips].sort((a, b) => a.startUs - b.startUs);
19
- await this.checkL2Coverage(sortedClips);
18
+ const videoClips = videoTrack.clips;
19
+ await this.checkL2Coverage(videoClips);
20
20
  const width = options.width || model.renderConfig?.width || 720;
21
21
  const height = options.height || model.renderConfig?.height || 1280;
22
22
  const fps = options.fps || model.fps || 30;
23
- const videoChunkMeta = sortedClips[0] ? await this.cacheManager.getL2Metadata(sortedClips[0].id, "video") : null;
23
+ const videoChunkMeta = videoClips[0] ? await this.cacheManager.getL2Metadata(videoClips[0].id, "video") : null;
24
24
  if (!videoChunkMeta) {
25
25
  console.warn("[MuxManager] No videoChunkMeta available, export may fail");
26
26
  }
27
- const audioTrack = model.tracks.find((t) => t.kind === "audio");
28
27
  let audioChunkMeta = void 0;
29
- if (audioTrack?.clips.length) {
30
- audioChunkMeta = await this.getAudioChunkMeta();
28
+ for (const clip of videoClips) {
29
+ await this.audioSession.ensureClipAudioFromL2(clip.id);
31
30
  }
31
+ audioChunkMeta = await this.getAudioChunkMeta();
32
32
  const muxer = new MP4Muxer({
33
33
  width,
34
34
  height,
@@ -37,10 +37,8 @@ class MuxManager {
37
37
  videoChunkMeta,
38
38
  audioChunkMeta
39
39
  });
40
- await this.writeVideoChunks(muxer, sortedClips);
41
- if (audioTrack?.clips.length) {
42
- await this.writeAudioChunks(muxer);
43
- }
40
+ await this.writeVideoChunks(muxer, videoClips);
41
+ await this.writeAudioChunks(muxer);
44
42
  return muxer.finalize();
45
43
  }
46
44
  /**
@@ -76,10 +74,10 @@ class MuxManager {
76
74
  }
77
75
  return metadata;
78
76
  }
79
- async writeVideoChunks(muxer, sortedClips) {
77
+ async writeVideoChunks(muxer, clips) {
80
78
  let firstKeyframeWritten = false;
81
79
  let exportTimeUs = 0;
82
- for (const clip of sortedClips) {
80
+ for (const clip of clips) {
83
81
  const stream = await this.cacheManager.createL2ReadStream(clip.id, "video");
84
82
  if (!stream) {
85
83
  console.warn(`[MuxManager] No video stream for clip ${clip.id}`);
@@ -1 +1 @@
1
- {"version":3,"file":"MuxManager.js","sources":["../../../src/stages/mux/MuxManager.ts"],"sourcesContent":["import type { CompositionModel } from '../../model/CompositionModel';\nimport type { ExportOptions } from '../../types';\nimport { GlobalAudioSession } from '../compose/GlobalAudioSession';\nimport { MP4Muxer } from './MP4Muxer';\nimport { CacheManager } from '@/cache/CacheManager';\n\n/**\n * MuxManager: Main thread muxing service\n * Reads encoded chunks from L2 cache and muxes into final video file\n */\nexport class MuxManager {\n private cacheManager: CacheManager;\n private audioSession: GlobalAudioSession;\n private audioEncoderConfig: AudioEncoderConfig;\n private audioEncodedStream: ReadableStream<EncodedAudioChunk> | null = null;\n private audioFirstChunk: EncodedAudioChunk | null = null;\n\n constructor(\n cacheManager: CacheManager,\n audioSession: GlobalAudioSession,\n audioEncoderConfig: AudioEncoderConfig\n ) {\n this.cacheManager = cacheManager;\n this.audioSession = audioSession;\n this.audioEncoderConfig = audioEncoderConfig;\n }\n\n async export(model: CompositionModel, options: ExportOptions): Promise<Blob> {\n const videoTrack = model.tracks.find((t) => t.kind === 'video');\n if (!videoTrack || videoTrack.clips.length === 0) {\n throw new Error('No video clips in composition');\n }\n\n const sortedClips = [...videoTrack.clips].sort((a, b) => a.startUs - b.startUs);\n await this.checkL2Coverage(sortedClips);\n\n const width = options.width || (model as any).renderConfig?.width || 720;\n const height = options.height || (model as any).renderConfig?.height || 1280;\n const fps = options.fps || model.fps || 30;\n\n // Get video metadata from L2 cache (first clip)\n const videoChunkMeta = sortedClips[0]\n ? await this.cacheManager.getL2Metadata(sortedClips[0].id, 'video')\n : null;\n\n if (!videoChunkMeta) {\n console.warn('[MuxManager] No videoChunkMeta available, export may fail');\n }\n\n const audioTrack = model.tracks.find((t) => t.kind === 'audio');\n\n // Get audio metadata from first chunk if audio exists\n let audioChunkMeta: any = undefined;\n if (audioTrack?.clips.length) {\n audioChunkMeta = await this.getAudioChunkMeta();\n }\n\n const muxer = new MP4Muxer({\n width,\n height,\n fps,\n fastStart: 'in-memory',\n videoChunkMeta,\n audioChunkMeta,\n });\n\n await this.writeVideoChunks(muxer, sortedClips);\n if (audioTrack?.clips.length) {\n await this.writeAudioChunks(muxer);\n }\n\n return muxer.finalize();\n }\n\n /**\n * Get audio chunk metadata by reading first chunk from encoder\n * Caches the stream and first chunk for later use\n */\n private async getAudioChunkMeta(): Promise<any> {\n const audioEncoderConfig = this.audioEncoderConfig;\n let metadata: any = null;\n\n this.audioEncodedStream = await this.audioSession.createExportEncodedStream(\n audioEncoderConfig,\n (meta) => {\n // Extract decoderConfig from first chunk metadata\n if (meta.decoderConfig) {\n metadata = meta.decoderConfig;\n }\n }\n );\n\n if (!this.audioEncodedStream) {\n return null;\n }\n\n // Read first chunk to trigger metadata extraction and cache it\n const reader = this.audioEncodedStream.getReader();\n try {\n const { done, value } = await reader.read();\n if (!done && value) {\n this.audioFirstChunk = value;\n }\n reader.releaseLock();\n } catch (error) {\n console.error('[MuxManager] Failed to read first audio chunk:', error);\n reader.releaseLock();\n this.audioEncodedStream = null;\n return null;\n }\n\n return metadata;\n }\n\n private async writeVideoChunks(muxer: MP4Muxer, sortedClips: any[]): Promise<void> {\n let firstKeyframeWritten = false;\n let exportTimeUs = 0;\n\n for (const clip of sortedClips) {\n const stream = await this.cacheManager.createL2ReadStream(clip.id, 'video');\n if (!stream) {\n console.warn(`[MuxManager] No video stream for clip ${clip.id}`);\n continue;\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 const originalChunk = value as EncodedVideoChunk;\n\n // Copy chunk data\n const buffer = new ArrayBuffer(originalChunk.byteLength);\n originalChunk.copyTo(buffer);\n\n // Rewrite timestamp to export timeline (tight concatenation)\n const remappedChunk = new EncodedVideoChunk({\n type: originalChunk.type,\n timestamp: exportTimeUs,\n duration: originalChunk.duration ?? undefined,\n data: buffer,\n });\n\n // Ensure first chunk in export is a keyframe\n if (!firstKeyframeWritten) {\n if (remappedChunk.type !== 'key') {\n console.warn(`[MuxManager] Skipping non-keyframe at start of clip ${clip.id}`);\n exportTimeUs += originalChunk.duration || 0;\n continue;\n }\n firstKeyframeWritten = true;\n }\n\n muxer.writeVideoChunk(remappedChunk);\n exportTimeUs += originalChunk.duration || 0;\n }\n } finally {\n reader.releaseLock();\n }\n }\n }\n\n private async writeAudioChunks(muxer: MP4Muxer): Promise<void> {\n // Use cached stream from getAudioChunkMeta()\n if (!this.audioEncodedStream) {\n console.warn('[MuxManager] No audio stream available');\n return;\n }\n\n let exportTimeUs = 0;\n\n // Write the cached first chunk with remapped timestamp\n if (this.audioFirstChunk) {\n const buffer = new ArrayBuffer(this.audioFirstChunk.byteLength);\n this.audioFirstChunk.copyTo(buffer);\n\n const remappedChunk = new EncodedAudioChunk({\n type: this.audioFirstChunk.type,\n timestamp: exportTimeUs,\n duration: this.audioFirstChunk.duration ?? undefined,\n data: buffer,\n });\n muxer.writeAudioChunk(remappedChunk);\n exportTimeUs += this.audioFirstChunk.duration || 0;\n }\n\n // Continue reading remaining chunks\n const reader = this.audioEncodedStream.getReader();\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n const originalChunk = value as EncodedAudioChunk;\n\n // Copy chunk data\n const buffer = new ArrayBuffer(originalChunk.byteLength);\n originalChunk.copyTo(buffer);\n\n // Rewrite timestamp to export timeline\n const remappedChunk = new EncodedAudioChunk({\n type: originalChunk.type,\n timestamp: exportTimeUs,\n duration: originalChunk.duration ?? undefined,\n data: buffer,\n });\n\n muxer.writeAudioChunk(remappedChunk);\n exportTimeUs += originalChunk.duration || 0;\n }\n } finally {\n reader.releaseLock();\n // Clean up cached state\n this.audioEncodedStream = null;\n this.audioFirstChunk = null;\n }\n }\n\n private async checkL2Coverage(clips: any[]): Promise<void> {\n const missingClips: string[] = [];\n for (const clip of clips) {\n const inL2 = await this.cacheManager.hasClipInL2(clip.id, 'video');\n if (!inL2) {\n missingClips.push(clip.id);\n }\n }\n\n if (missingClips.length > 0) {\n const clipList = missingClips.slice(0, 3).join(', ');\n const moreText = missingClips.length > 3 ? ` and ${missingClips.length - 3} more` : '';\n throw new Error(\n `Export failed: ${missingClips.length} clip(s) not cached (${clipList}${moreText}). ` +\n `Please start PreRenderService and wait for background caching to complete.`\n );\n }\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"MuxManager.js","sources":["../../../src/stages/mux/MuxManager.ts"],"sourcesContent":["import type { CompositionModel } from '../../model/CompositionModel';\nimport type { ExportOptions } from '../../types';\nimport { GlobalAudioSession } from '../../orchestrator/GlobalAudioSession';\nimport { MP4Muxer } from './MP4Muxer';\nimport { CacheManager } from '@/cache/CacheManager';\n\n/**\n * MuxManager: Main thread muxing service\n * Reads encoded chunks from L2 cache and muxes into final video file\n */\nexport class MuxManager {\n private cacheManager: CacheManager;\n private audioSession: GlobalAudioSession;\n private audioEncoderConfig: AudioEncoderConfig;\n private audioEncodedStream: ReadableStream<EncodedAudioChunk> | null = null;\n private audioFirstChunk: EncodedAudioChunk | null = null;\n\n constructor(\n cacheManager: CacheManager,\n audioSession: GlobalAudioSession,\n audioEncoderConfig: AudioEncoderConfig\n ) {\n this.cacheManager = cacheManager;\n this.audioSession = audioSession;\n this.audioEncoderConfig = audioEncoderConfig;\n }\n\n async export(model: CompositionModel, options: ExportOptions): Promise<Blob> {\n const videoTrack = model.tracks.find((t) => t.kind === 'video');\n if (!videoTrack || videoTrack.clips.length === 0) {\n throw new Error('No video clips in composition');\n }\n\n const videoClips = videoTrack.clips;\n await this.checkL2Coverage(videoClips);\n\n const width = options.width || (model as any).renderConfig?.width || 720;\n const height = options.height || (model as any).renderConfig?.height || 1280;\n const fps = options.fps || model.fps || 30;\n\n // Get video metadata from L2 cache (first clip)\n const videoChunkMeta = videoClips[0]\n ? await this.cacheManager.getL2Metadata(videoClips[0].id, 'video')\n : null;\n\n if (!videoChunkMeta) {\n console.warn('[MuxManager] No videoChunkMeta available, export may fail');\n }\n\n let audioChunkMeta: any = undefined;\n\n // Ensure all video clip audio is decoded to PCM for mixing\n for (const clip of videoClips) {\n await this.audioSession.ensureClipAudioFromL2(clip.id);\n }\n audioChunkMeta = await this.getAudioChunkMeta();\n\n const muxer = new MP4Muxer({\n width,\n height,\n fps,\n fastStart: 'in-memory',\n videoChunkMeta,\n audioChunkMeta,\n });\n\n await this.writeVideoChunks(muxer, videoClips);\n\n await this.writeAudioChunks(muxer);\n\n return muxer.finalize();\n }\n\n /**\n * Get audio chunk metadata by reading first chunk from encoder\n * Caches the stream and first chunk for later use\n */\n private async getAudioChunkMeta(): Promise<any> {\n const audioEncoderConfig = this.audioEncoderConfig;\n let metadata: any = null;\n\n this.audioEncodedStream = await this.audioSession.createExportEncodedStream(\n audioEncoderConfig,\n (meta) => {\n // Extract decoderConfig from first chunk metadata\n if (meta.decoderConfig) {\n metadata = meta.decoderConfig;\n }\n }\n );\n\n if (!this.audioEncodedStream) {\n return null;\n }\n\n // Read first chunk to trigger metadata extraction and cache it\n const reader = this.audioEncodedStream.getReader();\n try {\n const { done, value } = await reader.read();\n if (!done && value) {\n this.audioFirstChunk = value;\n }\n reader.releaseLock();\n } catch (error) {\n console.error('[MuxManager] Failed to read first audio chunk:', error);\n reader.releaseLock();\n this.audioEncodedStream = null;\n return null;\n }\n\n return metadata;\n }\n\n private async writeVideoChunks(muxer: MP4Muxer, clips: any[]): Promise<void> {\n let firstKeyframeWritten = false;\n let exportTimeUs = 0;\n\n for (const clip of clips) {\n const stream = await this.cacheManager.createL2ReadStream(clip.id, 'video');\n if (!stream) {\n console.warn(`[MuxManager] No video stream for clip ${clip.id}`);\n continue;\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 const originalChunk = value as EncodedVideoChunk;\n\n // Copy chunk data\n const buffer = new ArrayBuffer(originalChunk.byteLength);\n originalChunk.copyTo(buffer);\n\n // Rewrite timestamp to export timeline (tight concatenation)\n const remappedChunk = new EncodedVideoChunk({\n type: originalChunk.type,\n timestamp: exportTimeUs,\n duration: originalChunk.duration ?? undefined,\n data: buffer,\n });\n\n // Ensure first chunk in export is a keyframe\n if (!firstKeyframeWritten) {\n if (remappedChunk.type !== 'key') {\n console.warn(`[MuxManager] Skipping non-keyframe at start of clip ${clip.id}`);\n exportTimeUs += originalChunk.duration || 0;\n continue;\n }\n firstKeyframeWritten = true;\n }\n\n muxer.writeVideoChunk(remappedChunk);\n exportTimeUs += originalChunk.duration || 0;\n }\n } finally {\n reader.releaseLock();\n }\n }\n }\n\n private async writeAudioChunks(muxer: MP4Muxer): Promise<void> {\n // Use cached stream from getAudioChunkMeta()\n if (!this.audioEncodedStream) {\n console.warn('[MuxManager] No audio stream available');\n return;\n }\n\n let exportTimeUs = 0;\n\n // Write the cached first chunk with remapped timestamp\n if (this.audioFirstChunk) {\n const buffer = new ArrayBuffer(this.audioFirstChunk.byteLength);\n this.audioFirstChunk.copyTo(buffer);\n\n const remappedChunk = new EncodedAudioChunk({\n type: this.audioFirstChunk.type,\n timestamp: exportTimeUs,\n duration: this.audioFirstChunk.duration ?? undefined,\n data: buffer,\n });\n muxer.writeAudioChunk(remappedChunk);\n exportTimeUs += this.audioFirstChunk.duration || 0;\n }\n\n // Continue reading remaining chunks\n const reader = this.audioEncodedStream.getReader();\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n const originalChunk = value as EncodedAudioChunk;\n\n // Copy chunk data\n const buffer = new ArrayBuffer(originalChunk.byteLength);\n originalChunk.copyTo(buffer);\n\n // Rewrite timestamp to export timeline\n const remappedChunk = new EncodedAudioChunk({\n type: originalChunk.type,\n timestamp: exportTimeUs,\n duration: originalChunk.duration ?? undefined,\n data: buffer,\n });\n muxer.writeAudioChunk(remappedChunk);\n exportTimeUs += originalChunk.duration || 0;\n }\n } finally {\n reader.releaseLock();\n // Clean up cached state\n this.audioEncodedStream = null;\n this.audioFirstChunk = null;\n }\n }\n\n private async checkL2Coverage(clips: any[]): Promise<void> {\n const missingClips: string[] = [];\n for (const clip of clips) {\n const inL2 = await this.cacheManager.hasClipInL2(clip.id, 'video');\n if (!inL2) {\n missingClips.push(clip.id);\n }\n }\n\n if (missingClips.length > 0) {\n const clipList = missingClips.slice(0, 3).join(', ');\n const moreText = missingClips.length > 3 ? ` and ${missingClips.length - 3} more` : '';\n throw new Error(\n `Export failed: ${missingClips.length} clip(s) not cached (${clipList}${moreText}). ` +\n `Please start PreRenderService and wait for background caching to complete.`\n );\n }\n }\n}\n"],"names":[],"mappings":"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"}
@@ -7043,6 +7043,13 @@ var mp4box_all = {};
7043
7043
  exports.createFile = MP4Box.createFile;
7044
7044
  }
7045
7045
  })(mp4box_all);
7046
+ function normalizeAudioCodec(codec) {
7047
+ if (!codec) return "mp4a.40.2";
7048
+ if (codec === "mp4a") {
7049
+ return "mp4a.40.2";
7050
+ }
7051
+ return codec;
7052
+ }
7046
7053
  class MP4Demuxer {
7047
7054
  mp4boxFile;
7048
7055
  tracks = /* @__PURE__ */ new Map();
@@ -7090,7 +7097,7 @@ class MP4Demuxer {
7090
7097
  const trackInfo = {
7091
7098
  id: track.id,
7092
7099
  type: track.type === "video" ? "video" : "audio",
7093
- codec: track.codec,
7100
+ codec: track.type === "audio" ? normalizeAudioCodec(track.codec) : track.codec,
7094
7101
  timescale: track.timescale
7095
7102
  };
7096
7103
  if (track.type === "video") {