@meframe/core 0.0.4 → 0.0.6

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 (115) hide show
  1. package/dist/Meframe.d.ts +16 -7
  2. package/dist/Meframe.d.ts.map +1 -1
  3. package/dist/Meframe.js +106 -86
  4. package/dist/Meframe.js.map +1 -1
  5. package/dist/cache/CacheManager.d.ts +27 -10
  6. package/dist/cache/CacheManager.d.ts.map +1 -1
  7. package/dist/cache/CacheManager.js +92 -30
  8. package/dist/cache/CacheManager.js.map +1 -1
  9. package/dist/cache/L2Cache.d.ts +31 -2
  10. package/dist/cache/L2Cache.d.ts.map +1 -1
  11. package/dist/cache/L2Cache.js +242 -44
  12. package/dist/cache/L2Cache.js.map +1 -1
  13. package/dist/cache/l1/VideoL1Cache.d.ts +1 -1
  14. package/dist/cache/l1/VideoL1Cache.js.map +1 -1
  15. package/dist/controllers/PreRenderService.d.ts +31 -4
  16. package/dist/controllers/PreRenderService.d.ts.map +1 -1
  17. package/dist/controllers/PreRenderService.js +130 -10
  18. package/dist/controllers/PreRenderService.js.map +1 -1
  19. package/dist/event/events.d.ts +12 -3
  20. package/dist/event/events.d.ts.map +1 -1
  21. package/dist/event/events.js +1 -0
  22. package/dist/event/events.js.map +1 -1
  23. package/dist/model/CompositionModel.d.ts +1 -1
  24. package/dist/model/CompositionModel.js +1 -1
  25. package/dist/model/CompositionModel.js.map +1 -1
  26. package/dist/model/patch.d.ts +1 -1
  27. package/dist/model/patch.js.map +1 -1
  28. package/dist/node_modules/.pnpm/mp4-muxer@5.2.2/node_modules/mp4-muxer/build/mp4-muxer.js +1858 -0
  29. package/dist/node_modules/.pnpm/mp4-muxer@5.2.2/node_modules/mp4-muxer/build/mp4-muxer.js.map +1 -0
  30. package/dist/orchestrator/ClipSessionManager.d.ts +1 -2
  31. package/dist/orchestrator/ClipSessionManager.d.ts.map +1 -1
  32. package/dist/orchestrator/ClipSessionManager.js +1 -0
  33. package/dist/orchestrator/ClipSessionManager.js.map +1 -1
  34. package/dist/orchestrator/CompositionPlanner.d.ts +1 -1
  35. package/dist/orchestrator/CompositionPlanner.js +1 -1
  36. package/dist/orchestrator/CompositionPlanner.js.map +1 -1
  37. package/dist/orchestrator/Orchestrator.d.ts +10 -1
  38. package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
  39. package/dist/orchestrator/Orchestrator.js +83 -38
  40. package/dist/orchestrator/Orchestrator.js.map +1 -1
  41. package/dist/orchestrator/VideoClipSession.d.ts +11 -4
  42. package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
  43. package/dist/orchestrator/VideoClipSession.js +70 -28
  44. package/dist/orchestrator/VideoClipSession.js.map +1 -1
  45. package/dist/stages/compose/GlobalAudioSession.d.ts +28 -1
  46. package/dist/stages/compose/GlobalAudioSession.d.ts.map +1 -1
  47. package/dist/stages/compose/GlobalAudioSession.js +133 -5
  48. package/dist/stages/compose/GlobalAudioSession.js.map +1 -1
  49. package/dist/stages/compose/VideoComposer.d.ts +1 -0
  50. package/dist/stages/compose/VideoComposer.d.ts.map +1 -1
  51. package/dist/stages/demux/MP4Demuxer.d.ts.map +1 -1
  52. package/dist/stages/encode/AudioChunkEncoder.d.ts +2 -1
  53. package/dist/stages/encode/AudioChunkEncoder.d.ts.map +1 -1
  54. package/dist/stages/encode/AudioChunkEncoder.js +41 -0
  55. package/dist/stages/encode/AudioChunkEncoder.js.map +1 -0
  56. package/dist/stages/encode/BaseEncoder.d.ts +7 -3
  57. package/dist/stages/encode/BaseEncoder.d.ts.map +1 -1
  58. package/dist/stages/encode/BaseEncoder.js +173 -0
  59. package/dist/stages/encode/BaseEncoder.js.map +1 -0
  60. package/dist/stages/encode/ClipEncoderManager.d.ts +64 -0
  61. package/dist/stages/encode/ClipEncoderManager.d.ts.map +1 -0
  62. package/dist/stages/encode/index.d.ts +1 -1
  63. package/dist/stages/encode/index.d.ts.map +1 -1
  64. package/dist/stages/load/ResourceLoader.d.ts.map +1 -1
  65. package/dist/stages/load/ResourceLoader.js +17 -12
  66. package/dist/stages/load/ResourceLoader.js.map +1 -1
  67. package/dist/stages/load/TaskManager.d.ts +1 -1
  68. package/dist/stages/load/TaskManager.d.ts.map +1 -1
  69. package/dist/stages/load/TaskManager.js +2 -2
  70. package/dist/stages/load/TaskManager.js.map +1 -1
  71. package/dist/stages/load/types.d.ts +2 -2
  72. package/dist/stages/load/types.d.ts.map +1 -1
  73. package/dist/stages/mux/MP4Muxer.d.ts +19 -38
  74. package/dist/stages/mux/MP4Muxer.d.ts.map +1 -1
  75. package/dist/stages/mux/MP4Muxer.js +60 -0
  76. package/dist/stages/mux/MP4Muxer.js.map +1 -0
  77. package/dist/stages/mux/MuxManager.d.ts +27 -0
  78. package/dist/stages/mux/MuxManager.d.ts.map +1 -0
  79. package/dist/stages/mux/MuxManager.js +148 -0
  80. package/dist/stages/mux/MuxManager.js.map +1 -0
  81. package/dist/stages/mux/index.d.ts +1 -0
  82. package/dist/stages/mux/index.d.ts.map +1 -1
  83. package/dist/stages/mux/types.d.ts +1 -0
  84. package/dist/stages/mux/types.d.ts.map +1 -1
  85. package/dist/types.d.ts +1 -1
  86. package/dist/types.d.ts.map +1 -1
  87. package/dist/worker/WorkerPool.d.ts.map +1 -1
  88. package/dist/worker/WorkerPool.js +2 -4
  89. package/dist/worker/WorkerPool.js.map +1 -1
  90. package/dist/worker/types.d.ts +1 -4
  91. package/dist/worker/types.d.ts.map +1 -1
  92. package/dist/worker/types.js +0 -3
  93. package/dist/worker/types.js.map +1 -1
  94. package/dist/worker/worker-event-whitelist.d.ts.map +1 -1
  95. package/dist/workers/MP4Demuxer.js +7049 -6
  96. package/dist/workers/MP4Demuxer.js.map +1 -1
  97. package/dist/workers/WorkerChannel.js +0 -3
  98. package/dist/workers/WorkerChannel.js.map +1 -1
  99. package/dist/workers/stages/compose/video-compose.worker.js +25 -14
  100. package/dist/workers/stages/compose/video-compose.worker.js.map +1 -1
  101. package/dist/workers/stages/decode/decode.worker.js +37 -28
  102. package/dist/workers/stages/decode/decode.worker.js.map +1 -1
  103. package/dist/workers/stages/demux/audio-demux.worker.js +4 -4
  104. package/dist/workers/stages/demux/audio-demux.worker.js.map +1 -1
  105. package/dist/workers/stages/demux/video-demux.worker.js +9 -7
  106. package/dist/workers/stages/demux/video-demux.worker.js.map +1 -1
  107. package/dist/workers/stages/encode/encode.worker.js +192 -196
  108. package/dist/workers/stages/encode/encode.worker.js.map +1 -1
  109. package/package.json +2 -1
  110. package/dist/stages/encode/EncoderPool.d.ts +0 -28
  111. package/dist/stages/encode/EncoderPool.d.ts.map +0 -1
  112. package/dist/workers/mp4box.all.js +0 -7049
  113. package/dist/workers/mp4box.all.js.map +0 -1
  114. package/dist/workers/stages/mux/mux.worker.js +0 -501
  115. package/dist/workers/stages/mux/mux.worker.js.map +0 -1
@@ -1,5 +1,6 @@
1
1
  class VideoClipSession {
2
2
  clipId;
3
+ sessionId;
3
4
  planner;
4
5
  workerPool;
5
6
  cacheManager;
@@ -22,6 +23,7 @@ class VideoClipSession {
22
23
  }
23
24
  constructor(config) {
24
25
  this.clipId = config.clipId;
26
+ this.sessionId = config.sessionId ?? config.clipId;
25
27
  this.planner = config.planner;
26
28
  this.workerPool = config.workerPool;
27
29
  this.cacheManager = config.cacheManager;
@@ -30,28 +32,34 @@ class VideoClipSession {
30
32
  this.callbacks = config.callbacks;
31
33
  }
32
34
  async activate() {
33
- console.log(">>>>>>>>>>>> activate", this.clipId);
34
35
  if (this.isActive || this.isDisposed) return;
35
36
  const clip = this.getClip();
36
37
  const resource = this.getResource();
37
38
  if (!clip || !resource) {
39
+ console.warn("[VideoClipSession] activate: clip or resource not found", this.sessionId);
38
40
  return;
39
41
  }
40
42
  if (resource.type === "video") {
41
43
  await this.setupVideoPipeline(clip, resource);
42
44
  } else if (resource.type === "image") {
43
45
  await this.setupImagePipeline(clip);
46
+ } else {
47
+ console.warn(
48
+ "[VideoClipSession] Unknown resource type:",
49
+ resource.type,
50
+ "for",
51
+ this.sessionId
52
+ );
44
53
  }
45
54
  await this.ensureInstructions(clip);
46
55
  this.isActive = true;
47
56
  if (this.callbacks.onPipelineReady) {
48
- await this.callbacks.onPipelineReady(this.clipId);
57
+ await this.callbacks.onPipelineReady();
49
58
  }
50
59
  }
51
60
  async deactivate() {
52
61
  if (!this.isActive || this.isDisposed) return;
53
62
  this.isActive = false;
54
- await this.invalidateClipCache();
55
63
  await this.releasePipeline();
56
64
  }
57
65
  async dispose() {
@@ -78,7 +86,6 @@ class VideoClipSession {
78
86
  return;
79
87
  }
80
88
  if (this.isActive) {
81
- console.log(">>>>>>>>>>>> restartPipeline", this.clipId);
82
89
  await this.restartPipeline(clip, resource, instructions);
83
90
  } else {
84
91
  await this.activate();
@@ -104,13 +111,13 @@ class VideoClipSession {
104
111
  }
105
112
  async setupImagePipeline(clip) {
106
113
  await this.acquireWorkers(clip, false);
107
- await this.connectImagePipeline(clip);
114
+ await this.connectImagePipeline();
108
115
  }
109
- async connectImagePipeline(clip) {
116
+ async connectImagePipeline() {
110
117
  if (!this.visualWorker || !this.encodeWorker) {
111
118
  throw new Error("Pipeline workers not ready");
112
119
  }
113
- const clipId = clip.id;
120
+ const sessionId = this.sessionId;
114
121
  this.visualToEncodeChannel = new MessageChannel();
115
122
  await this.visualWorker.send(
116
123
  "connect",
@@ -118,7 +125,7 @@ class VideoClipSession {
118
125
  direction: "downstream",
119
126
  port: this.visualToEncodeChannel.port1,
120
127
  streamType: "video",
121
- clipId
128
+ sessionId
122
129
  },
123
130
  { transfer: [this.visualToEncodeChannel.port1] }
124
131
  );
@@ -128,28 +135,46 @@ class VideoClipSession {
128
135
  direction: "upstream",
129
136
  port: this.visualToEncodeChannel.port2,
130
137
  streamType: "video",
131
- clipId
138
+ sessionId
132
139
  },
133
140
  { transfer: [this.visualToEncodeChannel.port2] }
134
141
  );
135
142
  this.visualWorker.receiveStream((stream) => {
136
143
  const visualConfig = this.workerConfigs.videoCompose ?? {};
137
144
  const fps = this.compositionModel.fps ?? visualConfig.fps ?? 30;
138
- this.callbacks.onStreamReady(this.clipId, stream, fps);
145
+ this.callbacks.onComposedStreamReady(stream, fps);
146
+ });
147
+ this.encodeWorker.receiveStream((stream, metadata) => {
148
+ const streamSessionId = metadata?.sessionId;
149
+ if (streamSessionId !== this.sessionId) {
150
+ return;
151
+ }
152
+ if (metadata?.streamType === "video") {
153
+ this.callbacks.onEncodedStreamReady(
154
+ stream,
155
+ "video"
156
+ );
157
+ } else if (metadata?.streamType === "audio") {
158
+ this.callbacks.onEncodedStreamReady(
159
+ stream,
160
+ "audio"
161
+ );
162
+ }
139
163
  });
140
164
  }
141
165
  async setupVideoPipeline(clip, resource) {
142
- console.log(">>>>>>>>>>>> setupVideoPipeline", this.clipId);
143
166
  await this.acquireWorkers(clip, true);
144
- await this.connectVideoPipeline(clip, resource.id);
167
+ await this.connectVideoPipeline(resource.id);
145
168
  }
146
169
  async acquireWorkers(clip, isVideo) {
147
170
  if (isVideo) {
148
171
  this.decodeWorker = await this.workerPool.get("decode");
149
- this.videoDemuxWorker = await this.workerPool.get("videoDemux", clip.id, { lazy: true });
172
+ this.videoDemuxWorker = await this.workerPool.get("videoDemux", this.sessionId, {
173
+ lazy: true
174
+ });
150
175
  }
151
176
  this.encodeWorker = await this.workerPool.get("encode");
152
- this.visualWorker = await this.workerPool.get("videoCompose", clip.id, { lazy: true });
177
+ this.visualWorker = await this.workerPool.get("videoCompose", this.sessionId, { lazy: true });
153
178
  const visualConfig = this.workerConfigs.videoCompose ?? {};
154
179
  const renderOverrides = this.compositionModel.renderConfig ?? {};
155
180
  const timeline = {
@@ -164,11 +189,11 @@ class VideoClipSession {
164
189
  config: { ...visualConfig, ...renderOverrides, timeline }
165
190
  });
166
191
  }
167
- async connectVideoPipeline(clip, resourceId) {
192
+ async connectVideoPipeline(resourceId) {
168
193
  if (!this.visualWorker || !this.decodeWorker || !this.encodeWorker || !this.videoDemuxWorker) {
169
194
  throw new Error("Pipeline workers not ready");
170
195
  }
171
- const clipId = clip.id;
196
+ const sessionId = this.sessionId;
172
197
  this.visualToEncodeChannel = new MessageChannel();
173
198
  await this.visualWorker.send(
174
199
  "connect",
@@ -176,7 +201,7 @@ class VideoClipSession {
176
201
  direction: "downstream",
177
202
  port: this.visualToEncodeChannel.port1,
178
203
  streamType: "video",
179
- clipId
204
+ sessionId
180
205
  },
181
206
  { transfer: [this.visualToEncodeChannel.port1] }
182
207
  );
@@ -186,7 +211,7 @@ class VideoClipSession {
186
211
  direction: "upstream",
187
212
  port: this.visualToEncodeChannel.port2,
188
213
  streamType: "video",
189
- clipId
214
+ sessionId
190
215
  },
191
216
  { transfer: [this.visualToEncodeChannel.port2] }
192
217
  );
@@ -197,7 +222,7 @@ class VideoClipSession {
197
222
  direction: "downstream",
198
223
  port: this.decodeToVisualChannel.port1,
199
224
  streamType: "video",
200
- clipId
225
+ sessionId
201
226
  },
202
227
  { transfer: [this.decodeToVisualChannel.port1] }
203
228
  );
@@ -207,7 +232,7 @@ class VideoClipSession {
207
232
  direction: "upstream",
208
233
  port: this.decodeToVisualChannel.port2,
209
234
  streamType: "video",
210
- clipId
235
+ sessionId
211
236
  },
212
237
  { transfer: [this.decodeToVisualChannel.port2] }
213
238
  );
@@ -218,7 +243,7 @@ class VideoClipSession {
218
243
  direction: "downstream",
219
244
  port: this.demuxToDecodeChannel.port1,
220
245
  streamType: "video",
221
- clipId
246
+ sessionId
222
247
  },
223
248
  { transfer: [this.demuxToDecodeChannel.port1] }
224
249
  );
@@ -228,7 +253,7 @@ class VideoClipSession {
228
253
  direction: "upstream",
229
254
  port: this.demuxToDecodeChannel.port2,
230
255
  streamType: "video",
231
- clipId
256
+ sessionId
232
257
  },
233
258
  { transfer: [this.demuxToDecodeChannel.port2] }
234
259
  );
@@ -236,13 +261,30 @@ class VideoClipSession {
236
261
  await this.videoDemuxWorker.send("configure", {
237
262
  initial: true,
238
263
  resourceId,
239
- clipId,
264
+ sessionId,
240
265
  config: demuxConfig
241
266
  });
242
267
  this.visualWorker.receiveStream((stream) => {
243
268
  const visualConfig = this.workerConfigs.videoCompose ?? {};
244
269
  const fps = this.compositionModel.fps ?? visualConfig.fps ?? 30;
245
- this.callbacks.onStreamReady(this.clipId, stream, fps);
270
+ this.callbacks.onComposedStreamReady(stream, fps);
271
+ });
272
+ this.encodeWorker.receiveStream((stream, metadata) => {
273
+ const streamSessionId = metadata?.sessionId;
274
+ if (streamSessionId !== this.sessionId) {
275
+ return;
276
+ }
277
+ if (metadata?.streamType === "video") {
278
+ this.callbacks.onEncodedStreamReady(
279
+ stream,
280
+ "video"
281
+ );
282
+ } else if (metadata?.streamType === "audio") {
283
+ this.callbacks.onEncodedStreamReady(
284
+ stream,
285
+ "audio"
286
+ );
287
+ }
246
288
  });
247
289
  }
248
290
  async installInstructions(instructions) {
@@ -267,7 +309,7 @@ class VideoClipSession {
267
309
  async releasePipeline() {
268
310
  if (this.visualWorker && this.instructionContext) {
269
311
  await this.visualWorker.notify("dispose_clip", {
270
- clipId: this.clipId,
312
+ sessionId: this.sessionId,
271
313
  revision: this.instructionContext.revision
272
314
  });
273
315
  }
@@ -281,15 +323,15 @@ class VideoClipSession {
281
323
  this.decodeToVisualChannel = null;
282
324
  this.demuxToDecodeChannel = null;
283
325
  if (this.visualStream && this.callbacks.onStreamDisposed) {
284
- this.callbacks.onStreamDisposed(this.clipId);
326
+ this.callbacks.onStreamDisposed();
285
327
  }
286
328
  this.visualStream = null;
287
329
  if (this.videoDemuxWorker) {
288
- this.workerPool.terminate("videoDemux", this.clipId);
330
+ this.workerPool.terminate("videoDemux", this.sessionId);
289
331
  this.videoDemuxWorker = null;
290
332
  }
291
333
  if (this.visualWorker) {
292
- this.workerPool.terminate("videoCompose", this.clipId);
334
+ this.workerPool.terminate("videoCompose", this.sessionId);
293
335
  this.visualWorker = null;
294
336
  }
295
337
  this.decodeWorker = null;
@@ -1 +1 @@
1
- {"version":3,"file":"VideoClipSession.js","sources":["../../src/orchestrator/VideoClipSession.ts"],"sourcesContent":["import type { WorkerPool } from '../worker/WorkerPool';\nimport type { CacheManager } from '../cache/CacheManager';\nimport type { CompositionModel, Clip } from '../model';\nimport type { CompositionPlanner, ClipUpdateResult } from './CompositionPlanner';\nimport type { ClipInstructionSet } from '../stages/compose/instructions';\nimport type { BaseWorker } from '../worker/BaseWorker';\nimport type { WorkerType } from '../worker/types';\n\ninterface VideoClipSessionCallbacks {\n onStreamReady(clipId: string, stream: ReadableStream<VideoFrame>, fps: number): void;\n onStreamDisposed?(clipId: string): void;\n onPipelineReady?(clipId: string): Promise<void>;\n}\n\ninterface VideoClipSessionConfig {\n clipId: string;\n planner: CompositionPlanner;\n workerPool: WorkerPool;\n cacheManager: CacheManager;\n compositionModel: CompositionModel;\n workerConfigs: Record<WorkerType, any>;\n callbacks: VideoClipSessionCallbacks;\n}\n\ninterface InstructionContext {\n revision: number;\n instructions: ClipInstructionSet;\n status: ClipInstructionSet['status'];\n}\n\nexport class VideoClipSession {\n private readonly clipId: string;\n private readonly planner: CompositionPlanner;\n private readonly workerPool: WorkerPool;\n private readonly cacheManager: CacheManager;\n private readonly compositionModel: CompositionModel;\n private readonly workerConfigs: Record<WorkerType, any>;\n private readonly callbacks: VideoClipSessionCallbacks;\n\n private instructionContext: InstructionContext | null = null;\n private visualWorker: BaseWorker | null = null;\n private decodeWorker: BaseWorker | null = null;\n private encodeWorker: BaseWorker | null = null;\n private videoDemuxWorker: BaseWorker | null = null;\n private visualStream: ReadableStream<VideoFrame> | null = null;\n private visualToEncodeChannel: MessageChannel | null = null;\n private decodeToVisualChannel: MessageChannel | null = null;\n private demuxToDecodeChannel: MessageChannel | null = null;\n private isActive = false;\n private isDisposed = false;\n\n static async create(config: VideoClipSessionConfig): Promise<VideoClipSession> {\n return new VideoClipSession(config);\n }\n\n private constructor(config: VideoClipSessionConfig) {\n this.clipId = config.clipId;\n this.planner = config.planner;\n this.workerPool = config.workerPool;\n this.cacheManager = config.cacheManager;\n this.compositionModel = config.compositionModel;\n this.workerConfigs = config.workerConfigs;\n this.callbacks = config.callbacks;\n }\n\n async activate(): Promise<void> {\n console.log('>>>>>>>>>>>> activate', this.clipId);\n if (this.isActive || this.isDisposed) return;\n\n const clip = this.getClip();\n const resource = this.getResource();\n if (!clip || !resource) {\n return;\n }\n\n if (resource.type === 'video') {\n await this.setupVideoPipeline(clip, resource);\n } else if (resource.type === 'image') {\n await this.setupImagePipeline(clip);\n }\n\n await this.ensureInstructions(clip);\n\n this.isActive = true;\n\n // Notify that pipeline is ready - triggers resource loading\n if (this.callbacks.onPipelineReady) {\n await this.callbacks.onPipelineReady(this.clipId);\n }\n }\n\n async deactivate(): Promise<void> {\n if (!this.isActive || this.isDisposed) return;\n this.isActive = false;\n\n await this.invalidateClipCache();\n await this.releasePipeline();\n }\n\n async dispose(): Promise<void> {\n if (this.isDisposed) return;\n await this.deactivate();\n this.planner.releaseClip(this.clipId);\n this.isDisposed = true;\n }\n\n async handlePlannerUpdate(update: ClipUpdateResult): Promise<void> {\n if (this.isDisposed) {\n return;\n }\n\n if (update.type === 'remove') {\n await this.dispose();\n return;\n }\n\n const instructions = update.instructions;\n if (!instructions) {\n return;\n }\n\n const clip = this.getClip();\n const resource = this.getResource();\n if (!clip || !resource || (resource.type !== 'video' && resource.type !== 'image')) {\n return;\n }\n\n // Any update requires pipeline restart (stream is closed after cache eviction)\n if (this.isActive) {\n console.log('>>>>>>>>>>>> restartPipeline', this.clipId);\n await this.restartPipeline(clip, resource, instructions);\n } else {\n await this.activate();\n }\n }\n\n get visualWorkerHandle(): BaseWorker | null {\n return this.visualWorker;\n }\n\n private getClip(): Clip | null {\n return this.compositionModel?.findClip?.(this.clipId) ?? null;\n }\n\n private getResource() {\n const clip = this.getClip();\n if (!clip) return null;\n return this.compositionModel.resources.get(clip.resourceId) || null;\n }\n\n private async ensureInstructions(_clip: Clip): Promise<void> {\n const instructions = this.planner.getInstructions(this.clipId);\n if (!instructions) {\n throw new Error(`No instructions for clip ${this.clipId}`);\n }\n await this.installInstructions(instructions);\n }\n\n private async setupImagePipeline(clip: Clip): Promise<void> {\n await this.acquireWorkers(clip, false);\n await this.connectImagePipeline(clip);\n }\n\n private async connectImagePipeline(clip: Clip): Promise<void> {\n if (!this.visualWorker || !this.encodeWorker) {\n throw new Error('Pipeline workers not ready');\n }\n\n const clipId = clip.id;\n\n this.visualToEncodeChannel = new MessageChannel();\n await this.visualWorker.send(\n 'connect',\n {\n direction: 'downstream',\n port: this.visualToEncodeChannel.port1,\n streamType: 'video',\n clipId,\n },\n { transfer: [this.visualToEncodeChannel.port1] }\n );\n await this.encodeWorker.send(\n 'connect',\n {\n direction: 'upstream',\n port: this.visualToEncodeChannel.port2,\n streamType: 'video',\n clipId,\n },\n { transfer: [this.visualToEncodeChannel.port2] }\n );\n\n this.visualWorker.receiveStream((stream) => {\n const visualConfig = this.workerConfigs.videoCompose ?? {};\n const fps = this.compositionModel.fps ?? visualConfig.fps ?? 30;\n this.callbacks.onStreamReady(this.clipId, stream, fps);\n });\n }\n\n private async setupVideoPipeline(clip: Clip, resource: { id: string }): Promise<void> {\n console.log('>>>>>>>>>>>> setupVideoPipeline', this.clipId);\n await this.acquireWorkers(clip, true);\n await this.connectVideoPipeline(clip, resource.id);\n }\n\n private async acquireWorkers(clip: Clip, isVideo: boolean): Promise<void> {\n if (isVideo) {\n this.decodeWorker = await this.workerPool.get('decode');\n this.videoDemuxWorker = await this.workerPool.get('videoDemux', clip.id, { lazy: true });\n }\n\n this.encodeWorker = await this.workerPool.get('encode');\n this.visualWorker = await this.workerPool.get('videoCompose', clip.id, { lazy: true });\n\n const visualConfig = this.workerConfigs.videoCompose ?? {};\n const renderOverrides = this.compositionModel.renderConfig ?? {};\n const timeline = {\n clipId: clip.id,\n clipStartUs: clip.startUs,\n clipEndUs: clip.startUs + clip.durationUs,\n compositionFps: this.compositionModel.fps ?? visualConfig.fps ?? 30,\n };\n await this.visualWorker.send('configure', {\n initial: true,\n clipId: clip.id,\n config: { ...visualConfig, ...renderOverrides, timeline },\n });\n }\n\n private async connectVideoPipeline(clip: Clip, resourceId: string): Promise<void> {\n if (!this.visualWorker || !this.decodeWorker || !this.encodeWorker || !this.videoDemuxWorker) {\n throw new Error('Pipeline workers not ready');\n }\n\n const clipId = clip.id;\n\n this.visualToEncodeChannel = new MessageChannel();\n await this.visualWorker.send(\n 'connect',\n {\n direction: 'downstream',\n port: this.visualToEncodeChannel.port1,\n streamType: 'video',\n clipId,\n },\n { transfer: [this.visualToEncodeChannel.port1] }\n );\n await this.encodeWorker.send(\n 'connect',\n {\n direction: 'upstream',\n port: this.visualToEncodeChannel.port2,\n streamType: 'video',\n clipId,\n },\n { transfer: [this.visualToEncodeChannel.port2] }\n );\n\n this.decodeToVisualChannel = new MessageChannel();\n await this.decodeWorker.send(\n 'connect',\n {\n direction: 'downstream',\n port: this.decodeToVisualChannel.port1,\n streamType: 'video',\n clipId,\n },\n { transfer: [this.decodeToVisualChannel.port1] }\n );\n await this.visualWorker.send(\n 'connect',\n {\n direction: 'upstream',\n port: this.decodeToVisualChannel.port2,\n streamType: 'video',\n clipId,\n },\n { transfer: [this.decodeToVisualChannel.port2] }\n );\n\n this.demuxToDecodeChannel = new MessageChannel();\n await this.videoDemuxWorker.send(\n 'connect',\n {\n direction: 'downstream',\n port: this.demuxToDecodeChannel.port1,\n streamType: 'video',\n clipId,\n },\n { transfer: [this.demuxToDecodeChannel.port1] }\n );\n await this.decodeWorker.send(\n 'connect',\n {\n direction: 'upstream',\n port: this.demuxToDecodeChannel.port2,\n streamType: 'video',\n clipId,\n },\n { transfer: [this.demuxToDecodeChannel.port2] }\n );\n\n const demuxConfig = this.workerConfigs.videoDemux ?? {};\n await this.videoDemuxWorker.send('configure', {\n initial: true,\n resourceId,\n clipId,\n config: demuxConfig,\n });\n\n this.visualWorker.receiveStream((stream) => {\n const visualConfig = this.workerConfigs.videoCompose ?? {};\n const fps = this.compositionModel.fps ?? visualConfig.fps ?? 30;\n this.callbacks.onStreamReady(this.clipId, stream, fps);\n });\n }\n\n private async installInstructions(instructions: ClipInstructionSet): Promise<void> {\n this.instructionContext = {\n revision: instructions.revision,\n instructions,\n status: instructions.status,\n };\n\n if (this.visualWorker) {\n await this.visualWorker.send('install_instructions', instructions);\n }\n }\n\n private async restartPipeline(\n clip: Clip,\n resource: { id: string; type: string },\n instructions: ClipInstructionSet\n ): Promise<void> {\n await this.releasePipeline();\n\n if (resource.type === 'video') {\n await this.setupVideoPipeline(clip, resource);\n } else if (resource.type === 'image') {\n await this.setupImagePipeline(clip);\n }\n\n await this.installInstructions(instructions);\n }\n\n private async releasePipeline(): Promise<void> {\n if (this.visualWorker && this.instructionContext) {\n await this.visualWorker.notify('dispose_clip', {\n clipId: this.clipId,\n revision: this.instructionContext.revision,\n });\n }\n\n this.visualToEncodeChannel?.port1.close();\n this.visualToEncodeChannel?.port2.close();\n this.decodeToVisualChannel?.port1.close();\n this.decodeToVisualChannel?.port2.close();\n this.demuxToDecodeChannel?.port1.close();\n this.demuxToDecodeChannel?.port2.close();\n\n this.visualToEncodeChannel = null;\n this.decodeToVisualChannel = null;\n this.demuxToDecodeChannel = null;\n\n if (this.visualStream && this.callbacks.onStreamDisposed) {\n this.callbacks.onStreamDisposed(this.clipId);\n }\n this.visualStream = null;\n\n if (this.videoDemuxWorker) {\n this.workerPool.terminate('videoDemux', this.clipId);\n this.videoDemuxWorker = null;\n }\n\n if (this.visualWorker) {\n this.workerPool.terminate('videoCompose', this.clipId);\n this.visualWorker = null;\n }\n\n // Shared workers are not terminated here (decode / encode)\n this.decodeWorker = null;\n this.encodeWorker = null;\n }\n\n private async invalidateClipCache(): Promise<void> {\n await this.cacheManager.invalidateClip(this.clipId);\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"VideoClipSession.js","sources":["../../src/orchestrator/VideoClipSession.ts"],"sourcesContent":["import type { WorkerPool } from '../worker/WorkerPool';\nimport type { CacheManager } from '../cache/CacheManager';\nimport type { CompositionModel, Clip } from '../model';\nimport type { CompositionPlanner, ClipUpdateResult } from './CompositionPlanner';\nimport type { ClipInstructionSet } from '../stages/compose/instructions';\nimport type { BaseWorker } from '../worker/BaseWorker';\nimport type { WorkerType } from '../worker/types';\n\ninterface VideoClipSessionCallbacks {\n onComposedStreamReady(stream: ReadableStream<VideoFrame>, fps: number): void;\n onEncodedStreamReady(\n stream: ReadableStream<{ chunk: EncodedVideoChunk; metadata: EncodedVideoChunkMetadata }>,\n track: 'video' | 'audio'\n ): void;\n onStreamDisposed?(): void;\n onPipelineReady?(): Promise<void>;\n}\n\ninterface VideoClipSessionConfig {\n clipId: string;\n sessionId?: string;\n forL2Only?: boolean;\n planner: CompositionPlanner;\n workerPool: WorkerPool;\n cacheManager: CacheManager;\n compositionModel: CompositionModel;\n workerConfigs: Record<WorkerType, any>;\n callbacks: VideoClipSessionCallbacks;\n}\n\ninterface InstructionContext {\n revision: number;\n instructions: ClipInstructionSet;\n status: ClipInstructionSet['status'];\n}\n\nexport class VideoClipSession {\n private readonly clipId: string;\n private readonly sessionId: string;\n private readonly planner: CompositionPlanner;\n private readonly workerPool: WorkerPool;\n private readonly cacheManager: CacheManager;\n private readonly compositionModel: CompositionModel;\n private readonly workerConfigs: Record<WorkerType, any>;\n private readonly callbacks: VideoClipSessionCallbacks;\n\n private instructionContext: InstructionContext | null = null;\n private visualWorker: BaseWorker | null = null;\n private decodeWorker: BaseWorker | null = null;\n private encodeWorker: BaseWorker | null = null;\n private videoDemuxWorker: BaseWorker | null = null;\n private visualStream: ReadableStream<VideoFrame> | null = null;\n private visualToEncodeChannel: MessageChannel | null = null;\n private decodeToVisualChannel: MessageChannel | null = null;\n private demuxToDecodeChannel: MessageChannel | null = null;\n private isActive = false;\n private isDisposed = false;\n\n static async create(config: VideoClipSessionConfig): Promise<VideoClipSession> {\n return new VideoClipSession(config);\n }\n\n private constructor(config: VideoClipSessionConfig) {\n this.clipId = config.clipId;\n this.sessionId = config.sessionId ?? config.clipId;\n this.planner = config.planner;\n this.workerPool = config.workerPool;\n this.cacheManager = config.cacheManager;\n this.compositionModel = config.compositionModel;\n this.workerConfigs = config.workerConfigs;\n this.callbacks = config.callbacks;\n }\n\n async activate(): Promise<void> {\n if (this.isActive || this.isDisposed) return;\n\n const clip = this.getClip();\n const resource = this.getResource();\n if (!clip || !resource) {\n console.warn('[VideoClipSession] activate: clip or resource not found', this.sessionId);\n return;\n }\n\n if (resource.type === 'video') {\n await this.setupVideoPipeline(clip, resource);\n } else if (resource.type === 'image') {\n await this.setupImagePipeline(clip);\n } else {\n console.warn(\n '[VideoClipSession] Unknown resource type:',\n resource.type,\n 'for',\n this.sessionId\n );\n }\n\n await this.ensureInstructions(clip);\n\n this.isActive = true;\n\n // Notify that pipeline is ready - triggers resource loading\n if (this.callbacks.onPipelineReady) {\n await this.callbacks.onPipelineReady();\n }\n }\n\n async deactivate(): Promise<void> {\n if (!this.isActive || this.isDisposed) return;\n this.isActive = false;\n\n await this.releasePipeline();\n }\n\n async dispose(): Promise<void> {\n if (this.isDisposed) return;\n await this.deactivate();\n this.planner.releaseClip(this.clipId);\n this.isDisposed = true;\n }\n\n async handlePlannerUpdate(update: ClipUpdateResult): Promise<void> {\n if (this.isDisposed) {\n return;\n }\n\n if (update.type === 'remove') {\n await this.dispose();\n return;\n }\n\n const instructions = update.instructions;\n if (!instructions) {\n return;\n }\n\n const clip = this.getClip();\n const resource = this.getResource();\n if (!clip || !resource || (resource.type !== 'video' && resource.type !== 'image')) {\n return;\n }\n\n // Any update requires pipeline restart (stream is closed after cache eviction)\n if (this.isActive) {\n await this.restartPipeline(clip, resource, instructions);\n } else {\n await this.activate();\n }\n }\n\n get visualWorkerHandle(): BaseWorker | null {\n return this.visualWorker;\n }\n\n private getClip(): Clip | null {\n return this.compositionModel?.findClip?.(this.clipId) ?? null;\n }\n\n private getResource() {\n const clip = this.getClip();\n if (!clip) return null;\n return this.compositionModel.resources.get(clip.resourceId) || null;\n }\n\n private async ensureInstructions(_clip: Clip): Promise<void> {\n const instructions = this.planner.getInstructions(this.clipId);\n if (!instructions) {\n throw new Error(`No instructions for clip ${this.clipId}`);\n }\n await this.installInstructions(instructions);\n }\n\n private async setupImagePipeline(clip: Clip): Promise<void> {\n await this.acquireWorkers(clip, false);\n await this.connectImagePipeline();\n }\n\n private async connectImagePipeline(): Promise<void> {\n if (!this.visualWorker || !this.encodeWorker) {\n throw new Error('Pipeline workers not ready');\n }\n\n // Use sessionId for worker communication\n const sessionId = this.sessionId;\n\n this.visualToEncodeChannel = new MessageChannel();\n await this.visualWorker.send(\n 'connect',\n {\n direction: 'downstream',\n port: this.visualToEncodeChannel.port1,\n streamType: 'video',\n sessionId,\n },\n { transfer: [this.visualToEncodeChannel.port1] }\n );\n await this.encodeWorker.send(\n 'connect',\n {\n direction: 'upstream',\n port: this.visualToEncodeChannel.port2,\n streamType: 'video',\n sessionId,\n },\n { transfer: [this.visualToEncodeChannel.port2] }\n );\n\n this.visualWorker.receiveStream((stream) => {\n const visualConfig = this.workerConfigs.videoCompose ?? {};\n const fps = this.compositionModel.fps ?? visualConfig.fps ?? 30;\n this.callbacks.onComposedStreamReady(stream, fps);\n });\n\n // Receive encoded chunks from EncodeWorker and forward to callback\n // Note: encodeWorker is shared across sessions\n // Each session registers its own callback with sessionId filtering\n this.encodeWorker.receiveStream((stream, metadata) => {\n // Filter: only process streams for this session\n const streamSessionId = (metadata as any)?.sessionId;\n\n if (streamSessionId !== this.sessionId) {\n return;\n }\n\n if (metadata?.streamType === 'video') {\n this.callbacks.onEncodedStreamReady(\n stream as ReadableStream<{\n chunk: EncodedVideoChunk;\n metadata: EncodedVideoChunkMetadata;\n }>,\n 'video'\n );\n } else if (metadata?.streamType === 'audio') {\n this.callbacks.onEncodedStreamReady(\n stream as ReadableStream<{\n chunk: EncodedVideoChunk;\n metadata: EncodedVideoChunkMetadata;\n }>,\n 'audio'\n );\n }\n });\n }\n\n private async setupVideoPipeline(clip: Clip, resource: { id: string }): Promise<void> {\n await this.acquireWorkers(clip, true);\n await this.connectVideoPipeline(resource.id);\n }\n\n private async acquireWorkers(clip: Clip, isVideo: boolean): Promise<void> {\n if (isVideo) {\n this.decodeWorker = await this.workerPool.get('decode');\n this.videoDemuxWorker = await this.workerPool.get('videoDemux', this.sessionId, {\n lazy: true,\n });\n }\n\n this.encodeWorker = await this.workerPool.get('encode');\n this.visualWorker = await this.workerPool.get('videoCompose', this.sessionId, { lazy: true });\n\n const visualConfig = this.workerConfigs.videoCompose ?? {};\n const renderOverrides = this.compositionModel.renderConfig ?? {};\n const timeline = {\n clipId: clip.id,\n clipStartUs: clip.startUs,\n clipEndUs: clip.startUs + clip.durationUs,\n compositionFps: this.compositionModel.fps ?? visualConfig.fps ?? 30,\n };\n await this.visualWorker.send('configure', {\n initial: true,\n clipId: clip.id,\n config: { ...visualConfig, ...renderOverrides, timeline },\n });\n }\n\n private async connectVideoPipeline(resourceId: string): Promise<void> {\n if (!this.visualWorker || !this.decodeWorker || !this.encodeWorker || !this.videoDemuxWorker) {\n throw new Error('Pipeline workers not ready');\n }\n\n // Use sessionId for worker communication\n const sessionId = this.sessionId;\n\n this.visualToEncodeChannel = new MessageChannel();\n await this.visualWorker.send(\n 'connect',\n {\n direction: 'downstream',\n port: this.visualToEncodeChannel.port1,\n streamType: 'video',\n sessionId,\n },\n { transfer: [this.visualToEncodeChannel.port1] }\n );\n await this.encodeWorker.send(\n 'connect',\n {\n direction: 'upstream',\n port: this.visualToEncodeChannel.port2,\n streamType: 'video',\n sessionId,\n },\n { transfer: [this.visualToEncodeChannel.port2] }\n );\n\n this.decodeToVisualChannel = new MessageChannel();\n await this.decodeWorker.send(\n 'connect',\n {\n direction: 'downstream',\n port: this.decodeToVisualChannel.port1,\n streamType: 'video',\n sessionId,\n },\n { transfer: [this.decodeToVisualChannel.port1] }\n );\n await this.visualWorker.send(\n 'connect',\n {\n direction: 'upstream',\n port: this.decodeToVisualChannel.port2,\n streamType: 'video',\n sessionId,\n },\n { transfer: [this.decodeToVisualChannel.port2] }\n );\n\n this.demuxToDecodeChannel = new MessageChannel();\n await this.videoDemuxWorker.send(\n 'connect',\n {\n direction: 'downstream',\n port: this.demuxToDecodeChannel.port1,\n streamType: 'video',\n sessionId,\n },\n { transfer: [this.demuxToDecodeChannel.port1] }\n );\n await this.decodeWorker.send(\n 'connect',\n {\n direction: 'upstream',\n port: this.demuxToDecodeChannel.port2,\n streamType: 'video',\n sessionId,\n },\n { transfer: [this.demuxToDecodeChannel.port2] }\n );\n\n const demuxConfig = this.workerConfigs.videoDemux ?? {};\n await this.videoDemuxWorker.send('configure', {\n initial: true,\n resourceId,\n sessionId,\n config: demuxConfig,\n });\n\n this.visualWorker.receiveStream((stream) => {\n const visualConfig = this.workerConfigs.videoCompose ?? {};\n const fps = this.compositionModel.fps ?? visualConfig.fps ?? 30;\n this.callbacks.onComposedStreamReady(stream, fps);\n });\n\n // Receive encoded chunks from EncodeWorker and forward to callback\n // Note: encodeWorker is shared across sessions\n // Each session registers its own callback with sessionId filtering\n this.encodeWorker.receiveStream((stream, metadata) => {\n // Filter: only process streams for this session\n const streamSessionId = (metadata as any)?.sessionId;\n\n if (streamSessionId !== this.sessionId) {\n return;\n }\n\n if (metadata?.streamType === 'video') {\n this.callbacks.onEncodedStreamReady(\n stream as ReadableStream<{\n chunk: EncodedVideoChunk;\n metadata: EncodedVideoChunkMetadata;\n }>,\n 'video'\n );\n } else if (metadata?.streamType === 'audio') {\n this.callbacks.onEncodedStreamReady(\n stream as ReadableStream<{\n chunk: EncodedVideoChunk;\n metadata: EncodedVideoChunkMetadata;\n }>,\n 'audio'\n );\n }\n });\n }\n\n private async installInstructions(instructions: ClipInstructionSet): Promise<void> {\n this.instructionContext = {\n revision: instructions.revision,\n instructions,\n status: instructions.status,\n };\n\n if (this.visualWorker) {\n await this.visualWorker.send('install_instructions', instructions);\n }\n }\n\n private async restartPipeline(\n clip: Clip,\n resource: { id: string; type: string },\n instructions: ClipInstructionSet\n ): Promise<void> {\n await this.releasePipeline();\n\n if (resource.type === 'video') {\n await this.setupVideoPipeline(clip, resource);\n } else if (resource.type === 'image') {\n await this.setupImagePipeline(clip);\n }\n\n await this.installInstructions(instructions);\n }\n\n private async releasePipeline(): Promise<void> {\n if (this.visualWorker && this.instructionContext) {\n await this.visualWorker.notify('dispose_clip', {\n sessionId: this.sessionId,\n revision: this.instructionContext.revision,\n });\n }\n\n this.visualToEncodeChannel?.port1.close();\n this.visualToEncodeChannel?.port2.close();\n this.decodeToVisualChannel?.port1.close();\n this.decodeToVisualChannel?.port2.close();\n this.demuxToDecodeChannel?.port1.close();\n this.demuxToDecodeChannel?.port2.close();\n\n this.visualToEncodeChannel = null;\n this.decodeToVisualChannel = null;\n this.demuxToDecodeChannel = null;\n\n if (this.visualStream && this.callbacks.onStreamDisposed) {\n this.callbacks.onStreamDisposed();\n }\n this.visualStream = null;\n\n if (this.videoDemuxWorker) {\n this.workerPool.terminate('videoDemux', this.sessionId);\n this.videoDemuxWorker = null;\n }\n\n if (this.visualWorker) {\n this.workerPool.terminate('videoCompose', this.sessionId);\n this.visualWorker = null;\n }\n\n // Shared workers are not terminated here (decode / encode)\n this.decodeWorker = null;\n this.encodeWorker = null;\n }\n\n async invalidateClipCache(): Promise<void> {\n await this.cacheManager.invalidateClip(this.clipId);\n }\n}\n"],"names":[],"mappings":"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"}
@@ -7,7 +7,7 @@ import { EventPayloadMap } from '../../event/events';
7
7
  import { CacheManager } from '../../cache/CacheManager';
8
8
 
9
9
  interface AudioDataMessage {
10
- clipId: string;
10
+ sessionId: string;
11
11
  audioData: AudioData;
12
12
  clipStartUs: TimeUs;
13
13
  clipDurationUs: TimeUs;
@@ -37,6 +37,33 @@ export declare class GlobalAudioSession {
37
37
  activateAllAudioClips(): Promise<void>;
38
38
  handleAudioStream(stream: ReadableStream<AudioData>, metadata: Record<string, any>): void;
39
39
  reset(): void;
40
+ /**
41
+ * Create export encoded audio stream with metadata callback
42
+ * Mixes all audio clips and encodes to EncodedAudioChunk for export
43
+ * Extracts metadata from first chunk and invokes callback
44
+ */
45
+ createExportEncodedStream(config?: Partial<AudioEncoderConfig>, onFirstMetadata?: (metadata: EncodedAudioChunkMetadata) => void): Promise<ReadableStream<EncodedAudioChunk> | null>;
46
+ /**
47
+ * Create export audio stream
48
+ * Mixes all audio clips for the entire composition and outputs AudioData stream
49
+ */
50
+ createExportAudioStream(): Promise<ReadableStream<AudioData> | null>;
51
+ /**
52
+ * Wait for audio clips to be decoded and cached
53
+ */
54
+ private waitForAudioClipsReady;
55
+ /**
56
+ * Wait for clip PCM data to be available
57
+ */
58
+ private waitForClipPCM;
59
+ /**
60
+ * Convert AudioBuffer to AudioData for encoding
61
+ */
62
+ private audioBufferToAudioData;
63
+ /**
64
+ * Interleave planar audio data for AudioData constructor
65
+ */
66
+ private interleavePlanarData;
40
67
  private setupAudioPipeline;
41
68
  private alignToWindow;
42
69
  }
@@ -1 +1 @@
1
- {"version":3,"file":"GlobalAudioSession.d.ts","sourceRoot":"","sources":["../../../src/stages/compose/GlobalAudioSession.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,KAAK,EAAE,gBAAgB,EAAQ,MAAM,aAAa,CAAC;AAC1D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAE1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAE7D,UAAU,gBAAgB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,SAAS,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,WAAW,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,UAAU,gBAAgB;IACxB,YAAY,EAAE,YAAY,CAAC;IAC3B,OAAO,EAAE,UAAU,CAAC;IACpB,cAAc,EAAE,cAAc,CAAC;IAC/B,QAAQ,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;IACpC,QAAQ,EAAE,MAAM,gBAAgB,GAAG,IAAI,CAAC;IACxC,kBAAkB,EAAE,MAAM,GAAG,CAAC;CAC/B;AAED,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,KAAK,CAAoB;IACjC,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,IAAI,CAAmB;gBAEnB,IAAI,EAAE,gBAAgB;IAKlC,WAAW,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI;IAKtC,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAwB5D,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAkB3E,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IA0B5C,iBAAiB,CAAC,MAAM,EAAE,cAAc,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI;IA+BzF,KAAK,IAAI,IAAI;YAKC,kBAAkB;IAiChC,OAAO,CAAC,aAAa;CAGtB"}
1
+ {"version":3,"file":"GlobalAudioSession.d.ts","sourceRoot":"","sources":["../../../src/stages/compose/GlobalAudioSession.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,KAAK,EAAE,gBAAgB,EAAQ,MAAM,aAAa,CAAC;AAC1D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAE1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAG7D,UAAU,gBAAgB;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,SAAS,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,WAAW,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,UAAU,gBAAgB;IACxB,YAAY,EAAE,YAAY,CAAC;IAC3B,OAAO,EAAE,UAAU,CAAC;IACpB,cAAc,EAAE,cAAc,CAAC;IAC/B,QAAQ,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;IACpC,QAAQ,EAAE,MAAM,gBAAgB,GAAG,IAAI,CAAC;IACxC,kBAAkB,EAAE,MAAM,GAAG,CAAC;CAC/B;AAED,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,KAAK,CAAoB;IACjC,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,IAAI,CAAmB;gBAEnB,IAAI,EAAE,gBAAgB;IAKlC,WAAW,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI;IAKtC,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAwB5D,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAkB3E,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IA0B5C,iBAAiB,CAAC,MAAM,EAAE,cAAc,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI;IA+BzF,KAAK,IAAI,IAAI;IAKb;;;;OAIG;IACG,yBAAyB,CAC7B,MAAM,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,EACpC,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,yBAAyB,KAAK,IAAI,GAC9D,OAAO,CAAC,cAAc,CAAC,iBAAiB,CAAC,GAAG,IAAI,CAAC;IA6BpD;;;OAGG;IACG,uBAAuB,IAAI,OAAO,CAAC,cAAc,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC;IAuC1E;;OAEG;YACW,sBAAsB;IAgBpC;;OAEG;IACH,OAAO,CAAC,cAAc;IAwBtB;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAqB9B;;OAEG;IACH,OAAO,CAAC,oBAAoB;YAgBd,kBAAkB;IAiChC,OAAO,CAAC,aAAa;CAGtB"}
@@ -1,5 +1,6 @@
1
1
  import { OfflineAudioMixer } from "./OfflineAudioMixer.js";
2
2
  import { MeframeEvent } from "../../event/events.js";
3
+ import { AudioChunkEncoder } from "../encode/AudioChunkEncoder.js";
3
4
  class GlobalAudioSession {
4
5
  mixWindowUs = 3e6;
5
6
  mixer;
@@ -10,8 +11,8 @@ class GlobalAudioSession {
10
11
  this.mixer = new OfflineAudioMixer(deps.cacheManager, deps.getModel);
11
12
  }
12
13
  onAudioData(message) {
13
- const { clipId, audioData, clipStartUs, clipDurationUs } = message;
14
- this.deps.cacheManager.putClipAudioData(clipId, audioData, clipStartUs, clipDurationUs);
14
+ const { sessionId, audioData, clipStartUs, clipDurationUs } = message;
15
+ this.deps.cacheManager.putClipAudioData(sessionId, audioData, clipStartUs, clipDurationUs);
15
16
  }
16
17
  async ensureMixedPCM(startUs) {
17
18
  const model = this.deps.getModel();
@@ -61,7 +62,7 @@ class GlobalAudioSession {
61
62
  this.activeClips.add(clip.id);
62
63
  await this.deps.resourceLoader.fetch(clip.resourceId, {
63
64
  priority: "high",
64
- clipId: clip.id,
65
+ sessionId: clip.id,
65
66
  trackId: track.id
66
67
  });
67
68
  this.deps.eventBus.emit(MeframeEvent.ClipActivated, { clipId: clip.id });
@@ -70,7 +71,7 @@ class GlobalAudioSession {
70
71
  }
71
72
  }
72
73
  handleAudioStream(stream, metadata) {
73
- const clipId = metadata.clipId || "unknown";
74
+ const sessionId = metadata.sessionId || "unknown";
74
75
  const clipStartUs = metadata.clipStartUs ?? 0;
75
76
  const clipDurationUs = metadata.clipDurationUs ?? 0;
76
77
  const reader = stream.getReader();
@@ -82,7 +83,7 @@ class GlobalAudioSession {
82
83
  return;
83
84
  }
84
85
  this.onAudioData({
85
- clipId,
86
+ sessionId,
86
87
  audioData: value,
87
88
  clipStartUs,
88
89
  clipDurationUs
@@ -99,6 +100,133 @@ class GlobalAudioSession {
99
100
  this.deps.cacheManager.resetAudioCache();
100
101
  this.activeClips.clear();
101
102
  }
103
+ /**
104
+ * Create export encoded audio stream with metadata callback
105
+ * Mixes all audio clips and encodes to EncodedAudioChunk for export
106
+ * Extracts metadata from first chunk and invokes callback
107
+ */
108
+ async createExportEncodedStream(config, onFirstMetadata) {
109
+ const audioDataStream = await this.createExportAudioStream();
110
+ if (!audioDataStream) {
111
+ return null;
112
+ }
113
+ const encoder = new AudioChunkEncoder(config);
114
+ await encoder.initialize();
115
+ const encodingTransform = encoder.createStream();
116
+ const encodedStream = audioDataStream.pipeThrough(encodingTransform);
117
+ let firstMetadataExtracted = false;
118
+ return encodedStream.pipeThrough(
119
+ new TransformStream({
120
+ transform(encoderChunk, controller) {
121
+ if (!firstMetadataExtracted && onFirstMetadata) {
122
+ onFirstMetadata(encoderChunk.metadata);
123
+ firstMetadataExtracted = true;
124
+ }
125
+ controller.enqueue(encoderChunk.chunk);
126
+ }
127
+ })
128
+ );
129
+ }
130
+ /**
131
+ * Create export audio stream
132
+ * Mixes all audio clips for the entire composition and outputs AudioData stream
133
+ */
134
+ async createExportAudioStream() {
135
+ const model = this.deps.getModel();
136
+ if (!model) {
137
+ return null;
138
+ }
139
+ const totalDurationUs = model.durationUs;
140
+ await this.activateAllAudioClips();
141
+ await this.waitForAudioClipsReady();
142
+ return new ReadableStream({
143
+ start: async (controller) => {
144
+ const windowSize = 3e6;
145
+ let currentUs = 0;
146
+ while (currentUs < totalDurationUs) {
147
+ const windowEndUs = Math.min(currentUs + windowSize, totalDurationUs);
148
+ const mixedBuffer = await this.mixer.mix(currentUs, windowEndUs);
149
+ const audioData = this.audioBufferToAudioData(mixedBuffer, currentUs);
150
+ if (audioData) {
151
+ controller.enqueue(audioData);
152
+ }
153
+ currentUs = windowEndUs;
154
+ }
155
+ controller.close();
156
+ }
157
+ });
158
+ }
159
+ /**
160
+ * Wait for audio clips to be decoded and cached
161
+ */
162
+ async waitForAudioClipsReady() {
163
+ const model = this.deps.getModel();
164
+ if (!model) return;
165
+ const audioClips = model.tracks.filter((track) => track.kind === "audio").flatMap((track) => track.clips);
166
+ const waitPromises = audioClips.map(
167
+ (clip) => this.waitForClipPCM(clip.id, 1e3)
168
+ // Wait up to 1s per clip
169
+ );
170
+ await Promise.allSettled(waitPromises);
171
+ }
172
+ /**
173
+ * Wait for clip PCM data to be available
174
+ */
175
+ waitForClipPCM(clipId, timeoutMs) {
176
+ return new Promise((resolve) => {
177
+ const checkInterval = 50;
178
+ let elapsed = 0;
179
+ const check = () => {
180
+ if (this.deps.cacheManager.hasClipPCM(clipId)) {
181
+ resolve(true);
182
+ return;
183
+ }
184
+ elapsed += checkInterval;
185
+ if (elapsed >= timeoutMs) {
186
+ resolve(false);
187
+ return;
188
+ }
189
+ setTimeout(check, checkInterval);
190
+ };
191
+ check();
192
+ });
193
+ }
194
+ /**
195
+ * Convert AudioBuffer to AudioData for encoding
196
+ */
197
+ audioBufferToAudioData(buffer, timestampUs) {
198
+ const sampleRate = buffer.sampleRate;
199
+ const numberOfChannels = buffer.numberOfChannels;
200
+ const numberOfFrames = buffer.length;
201
+ const planes = [];
202
+ for (let channel = 0; channel < numberOfChannels; channel++) {
203
+ planes.push(buffer.getChannelData(channel));
204
+ }
205
+ return new AudioData({
206
+ format: "f32",
207
+ // interleaved format
208
+ sampleRate,
209
+ numberOfFrames,
210
+ numberOfChannels,
211
+ timestamp: timestampUs,
212
+ data: this.interleavePlanarData(planes)
213
+ });
214
+ }
215
+ /**
216
+ * Interleave planar audio data for AudioData constructor
217
+ */
218
+ interleavePlanarData(planes) {
219
+ const numberOfChannels = planes.length;
220
+ const numberOfFrames = planes[0]?.length ?? 0;
221
+ const totalSamples = numberOfChannels * numberOfFrames;
222
+ const interleaved = new Float32Array(totalSamples);
223
+ for (let frame = 0; frame < numberOfFrames; frame++) {
224
+ for (let channel = 0; channel < numberOfChannels; channel++) {
225
+ interleaved[frame * numberOfChannels + channel] = planes[channel][frame];
226
+ }
227
+ }
228
+ return interleaved.buffer;
229
+ }
102
230
  async setupAudioPipeline(clip) {
103
231
  const { id: clipId, resourceId, startUs, durationUs } = clip;
104
232
  const audioDemuxWorker = await this.deps.workers.get("audioDemux", clipId, { lazy: true });
@@ -1 +1 @@
1
- {"version":3,"file":"GlobalAudioSession.js","sources":["../../../src/stages/compose/GlobalAudioSession.ts"],"sourcesContent":["import type { TimeUs } from '../../model/types';\nimport { OfflineAudioMixer } from './OfflineAudioMixer';\nimport type { CompositionModel, Clip } from '../../model';\nimport type { WorkerPool } from '../../worker/WorkerPool';\nimport type { ResourceLoader } from '../load/ResourceLoader';\nimport type { EventBus } from '../../event/EventBus';\nimport type { EventPayloadMap } from '../../event/events';\nimport { MeframeEvent } from '../../event/events';\nimport type { CacheManager } from '../../cache/CacheManager';\n\ninterface AudioDataMessage {\n clipId: string;\n audioData: AudioData;\n clipStartUs: TimeUs;\n clipDurationUs: TimeUs;\n}\n\nexport interface AudioPlaybackInfo {\n buffer: AudioBuffer;\n offsetSeconds: number;\n durationSeconds: number;\n}\n\ninterface AudioSessionDeps {\n cacheManager: CacheManager;\n workers: WorkerPool;\n resourceLoader: ResourceLoader;\n eventBus: EventBus<EventPayloadMap>;\n getModel: () => CompositionModel | null;\n buildWorkerConfigs: () => any;\n}\n\nexport class GlobalAudioSession {\n private mixWindowUs = 3_000_000;\n private mixer: OfflineAudioMixer;\n private activeClips = new Set<string>();\n private deps: AudioSessionDeps;\n\n constructor(deps: AudioSessionDeps) {\n this.deps = deps;\n this.mixer = new OfflineAudioMixer(deps.cacheManager, deps.getModel);\n }\n\n onAudioData(message: AudioDataMessage): void {\n const { clipId, audioData, clipStartUs, clipDurationUs } = message;\n this.deps.cacheManager.putClipAudioData(clipId, audioData, clipStartUs, clipDurationUs);\n }\n\n async ensureMixedPCM(startUs: TimeUs): Promise<AudioBuffer | null> {\n const model = this.deps.getModel();\n if (!model) {\n return null;\n }\n\n const windowStartUs = this.alignToWindow(startUs);\n const windowEndUs = windowStartUs + this.mixWindowUs;\n\n const cached = this.deps.cacheManager.getMixedAudio(windowStartUs, windowEndUs);\n if (cached) {\n return cached;\n }\n\n try {\n const mixedBuffer = await this.mixer.mix(windowStartUs, windowEndUs);\n this.deps.cacheManager.putMixedAudio(windowStartUs, windowEndUs, mixedBuffer);\n return mixedBuffer;\n } catch (error) {\n console.error('[GlobalAudioSession] Mix failed:', error);\n return null;\n }\n }\n\n async prepareAudioForPlayback(startUs: TimeUs): Promise<AudioPlaybackInfo | null> {\n const buffer = await this.ensureMixedPCM(startUs);\n if (!buffer) {\n return null;\n }\n\n const windowStartUs = this.alignToWindow(startUs);\n const offsetUs = startUs - windowStartUs;\n const offsetSeconds = offsetUs / 1_000_000;\n const durationSeconds = buffer.duration - offsetSeconds;\n\n return {\n buffer,\n offsetSeconds,\n durationSeconds,\n };\n }\n\n async activateAllAudioClips(): Promise<void> {\n const model = this.deps.getModel();\n if (!model) {\n return;\n }\n\n const audioTracks = model.tracks.filter((track) => track.kind === 'audio');\n\n for (const track of audioTracks) {\n for (const clip of track.clips) {\n if (!this.activeClips.has(clip.id)) {\n await this.setupAudioPipeline(clip);\n this.activeClips.add(clip.id);\n\n await this.deps.resourceLoader.fetch(clip.resourceId, {\n priority: 'high',\n clipId: clip.id,\n trackId: track.id,\n });\n\n this.deps.eventBus.emit(MeframeEvent.ClipActivated, { clipId: clip.id });\n }\n }\n }\n }\n\n handleAudioStream(stream: ReadableStream<AudioData>, metadata: Record<string, any>): void {\n const clipId = metadata.clipId || 'unknown';\n const clipStartUs = metadata.clipStartUs ?? 0;\n const clipDurationUs = metadata.clipDurationUs ?? 0;\n\n const reader = stream.getReader();\n const pump = async (): Promise<void> => {\n try {\n const { done, value } = await reader.read();\n if (done) {\n reader.releaseLock();\n return;\n }\n\n this.onAudioData({\n clipId,\n audioData: value,\n clipStartUs,\n clipDurationUs,\n });\n\n await pump();\n } catch (error) {\n console.error('[GlobalAudioSession] Audio stream error:', error);\n reader.releaseLock();\n }\n };\n\n pump();\n }\n\n reset(): void {\n this.deps.cacheManager.resetAudioCache();\n this.activeClips.clear();\n }\n\n private async setupAudioPipeline(clip: Clip): Promise<void> {\n const { id: clipId, resourceId, startUs, durationUs } = clip;\n const audioDemuxWorker = await this.deps.workers.get('audioDemux', clipId, { lazy: true });\n const decodeWorker = await this.deps.workers.get('decode');\n\n const demuxToDecodeChannel = new MessageChannel();\n await audioDemuxWorker.send(\n 'connect',\n { direction: 'downstream', port: demuxToDecodeChannel.port1, streamType: 'audio', clipId },\n { transfer: [demuxToDecodeChannel.port1] }\n );\n await decodeWorker.send(\n 'connect',\n {\n direction: 'upstream',\n port: demuxToDecodeChannel.port2,\n streamType: 'audio',\n clipId,\n clipStartUs: startUs || 0,\n clipDurationUs: durationUs || 0,\n },\n { transfer: [demuxToDecodeChannel.port2] }\n );\n\n const demuxConfig = this.deps.buildWorkerConfigs().audioDemux;\n await audioDemuxWorker.send('configure', {\n initial: true,\n resourceId,\n clipId,\n config: demuxConfig,\n });\n }\n\n private alignToWindow(timeUs: TimeUs): TimeUs {\n return Math.floor(timeUs / this.mixWindowUs) * this.mixWindowUs;\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"GlobalAudioSession.js","sources":["../../../src/stages/compose/GlobalAudioSession.ts"],"sourcesContent":["import type { TimeUs } from '../../model/types';\nimport { OfflineAudioMixer } from './OfflineAudioMixer';\nimport type { CompositionModel, Clip } from '../../model';\nimport type { WorkerPool } from '../../worker/WorkerPool';\nimport type { ResourceLoader } from '../load/ResourceLoader';\nimport type { EventBus } from '../../event/EventBus';\nimport type { EventPayloadMap } from '../../event/events';\nimport { MeframeEvent } from '../../event/events';\nimport type { CacheManager } from '../../cache/CacheManager';\nimport { AudioChunkEncoder } from '../encode/AudioChunkEncoder';\n\ninterface AudioDataMessage {\n sessionId: string;\n audioData: AudioData;\n clipStartUs: TimeUs;\n clipDurationUs: TimeUs;\n}\n\nexport interface AudioPlaybackInfo {\n buffer: AudioBuffer;\n offsetSeconds: number;\n durationSeconds: number;\n}\n\ninterface AudioSessionDeps {\n cacheManager: CacheManager;\n workers: WorkerPool;\n resourceLoader: ResourceLoader;\n eventBus: EventBus<EventPayloadMap>;\n getModel: () => CompositionModel | null;\n buildWorkerConfigs: () => any;\n}\n\nexport class GlobalAudioSession {\n private mixWindowUs = 3_000_000;\n private mixer: OfflineAudioMixer;\n private activeClips = new Set<string>();\n private deps: AudioSessionDeps;\n\n constructor(deps: AudioSessionDeps) {\n this.deps = deps;\n this.mixer = new OfflineAudioMixer(deps.cacheManager, deps.getModel);\n }\n\n onAudioData(message: AudioDataMessage): void {\n const { sessionId, audioData, clipStartUs, clipDurationUs } = message;\n this.deps.cacheManager.putClipAudioData(sessionId, audioData, clipStartUs, clipDurationUs);\n }\n\n async ensureMixedPCM(startUs: TimeUs): Promise<AudioBuffer | null> {\n const model = this.deps.getModel();\n if (!model) {\n return null;\n }\n\n const windowStartUs = this.alignToWindow(startUs);\n const windowEndUs = windowStartUs + this.mixWindowUs;\n\n const cached = this.deps.cacheManager.getMixedAudio(windowStartUs, windowEndUs);\n if (cached) {\n return cached;\n }\n\n try {\n const mixedBuffer = await this.mixer.mix(windowStartUs, windowEndUs);\n this.deps.cacheManager.putMixedAudio(windowStartUs, windowEndUs, mixedBuffer);\n return mixedBuffer;\n } catch (error) {\n console.error('[GlobalAudioSession] Mix failed:', error);\n return null;\n }\n }\n\n async prepareAudioForPlayback(startUs: TimeUs): Promise<AudioPlaybackInfo | null> {\n const buffer = await this.ensureMixedPCM(startUs);\n if (!buffer) {\n return null;\n }\n\n const windowStartUs = this.alignToWindow(startUs);\n const offsetUs = startUs - windowStartUs;\n const offsetSeconds = offsetUs / 1_000_000;\n const durationSeconds = buffer.duration - offsetSeconds;\n\n return {\n buffer,\n offsetSeconds,\n durationSeconds,\n };\n }\n\n async activateAllAudioClips(): Promise<void> {\n const model = this.deps.getModel();\n if (!model) {\n return;\n }\n\n const audioTracks = model.tracks.filter((track) => track.kind === 'audio');\n\n for (const track of audioTracks) {\n for (const clip of track.clips) {\n if (!this.activeClips.has(clip.id)) {\n await this.setupAudioPipeline(clip);\n this.activeClips.add(clip.id);\n\n await this.deps.resourceLoader.fetch(clip.resourceId, {\n priority: 'high',\n sessionId: clip.id,\n trackId: track.id,\n });\n\n this.deps.eventBus.emit(MeframeEvent.ClipActivated, { clipId: clip.id });\n }\n }\n }\n }\n\n handleAudioStream(stream: ReadableStream<AudioData>, metadata: Record<string, any>): void {\n const sessionId = metadata.sessionId || 'unknown';\n const clipStartUs = metadata.clipStartUs ?? 0;\n const clipDurationUs = metadata.clipDurationUs ?? 0;\n\n const reader = stream.getReader();\n const pump = async (): Promise<void> => {\n try {\n const { done, value } = await reader.read();\n if (done) {\n reader.releaseLock();\n return;\n }\n\n this.onAudioData({\n sessionId,\n audioData: value,\n clipStartUs,\n clipDurationUs,\n });\n\n await pump();\n } catch (error) {\n console.error('[GlobalAudioSession] Audio stream error:', error);\n reader.releaseLock();\n }\n };\n\n pump();\n }\n\n reset(): void {\n this.deps.cacheManager.resetAudioCache();\n this.activeClips.clear();\n }\n\n /**\n * Create export encoded audio stream with metadata callback\n * Mixes all audio clips and encodes to EncodedAudioChunk for export\n * Extracts metadata from first chunk and invokes callback\n */\n async createExportEncodedStream(\n config?: Partial<AudioEncoderConfig>,\n onFirstMetadata?: (metadata: EncodedAudioChunkMetadata) => void\n ): Promise<ReadableStream<EncodedAudioChunk> | null> {\n const audioDataStream = await this.createExportAudioStream();\n if (!audioDataStream) {\n return null;\n }\n\n const encoder = new AudioChunkEncoder(config);\n await encoder.initialize();\n\n const encodingTransform = encoder.createStream();\n const encodedStream = audioDataStream.pipeThrough(encodingTransform);\n\n let firstMetadataExtracted = false;\n\n // Extract chunk from EncoderChunk and capture first metadata\n return encodedStream.pipeThrough(\n new TransformStream({\n transform(encoderChunk, controller) {\n // Extract metadata from first chunk\n if (!firstMetadataExtracted && onFirstMetadata) {\n onFirstMetadata(encoderChunk.metadata as EncodedAudioChunkMetadata);\n firstMetadataExtracted = true;\n }\n controller.enqueue(encoderChunk.chunk as EncodedAudioChunk);\n },\n })\n );\n }\n\n /**\n * Create export audio stream\n * Mixes all audio clips for the entire composition and outputs AudioData stream\n */\n async createExportAudioStream(): Promise<ReadableStream<AudioData> | null> {\n const model = this.deps.getModel();\n if (!model) {\n return null;\n }\n\n const totalDurationUs = model.durationUs;\n\n // Ensure all audio clips are active and cached\n await this.activateAllAudioClips();\n\n // Wait for PCM data to be available\n await this.waitForAudioClipsReady();\n\n return new ReadableStream<AudioData>({\n start: async (controller) => {\n const windowSize = 3_000_000; // 3 seconds per chunk\n let currentUs = 0;\n\n while (currentUs < totalDurationUs) {\n const windowEndUs = Math.min(currentUs + windowSize, totalDurationUs);\n\n // Mix this window\n const mixedBuffer = await this.mixer.mix(currentUs, windowEndUs);\n\n // Convert AudioBuffer to AudioData\n const audioData = this.audioBufferToAudioData(mixedBuffer, currentUs);\n if (audioData) {\n controller.enqueue(audioData);\n }\n\n currentUs = windowEndUs;\n }\n\n controller.close();\n },\n });\n }\n\n /**\n * Wait for audio clips to be decoded and cached\n */\n private async waitForAudioClipsReady(): Promise<void> {\n const model = this.deps.getModel();\n if (!model) return;\n\n const audioClips = model.tracks\n .filter((track) => track.kind === 'audio')\n .flatMap((track) => track.clips);\n\n // Wait for each clip to have PCM data\n const waitPromises = audioClips.map(\n (clip) => this.waitForClipPCM(clip.id, 1000) // Wait up to 1s per clip\n );\n\n await Promise.allSettled(waitPromises);\n }\n\n /**\n * Wait for clip PCM data to be available\n */\n private waitForClipPCM(clipId: string, timeoutMs: number): Promise<boolean> {\n return new Promise((resolve) => {\n const checkInterval = 50;\n let elapsed = 0;\n\n const check = () => {\n if (this.deps.cacheManager.hasClipPCM(clipId)) {\n resolve(true);\n return;\n }\n\n elapsed += checkInterval;\n if (elapsed >= timeoutMs) {\n resolve(false);\n return;\n }\n\n setTimeout(check, checkInterval);\n };\n\n check();\n });\n }\n\n /**\n * Convert AudioBuffer to AudioData for encoding\n */\n private audioBufferToAudioData(buffer: AudioBuffer, timestampUs: TimeUs): AudioData | null {\n const sampleRate = buffer.sampleRate;\n const numberOfChannels = buffer.numberOfChannels;\n const numberOfFrames = buffer.length;\n\n // Interleave channels into planar format\n const planes: Float32Array[] = [];\n for (let channel = 0; channel < numberOfChannels; channel++) {\n planes.push(buffer.getChannelData(channel));\n }\n\n return new AudioData({\n format: 'f32', // interleaved format\n sampleRate,\n numberOfFrames,\n numberOfChannels,\n timestamp: timestampUs,\n data: this.interleavePlanarData(planes),\n });\n }\n\n /**\n * Interleave planar audio data for AudioData constructor\n */\n private interleavePlanarData(planes: Float32Array[]): ArrayBuffer {\n const numberOfChannels = planes.length;\n const numberOfFrames = planes[0]?.length ?? 0;\n const totalSamples = numberOfChannels * numberOfFrames;\n\n const interleaved = new Float32Array(totalSamples);\n\n for (let frame = 0; frame < numberOfFrames; frame++) {\n for (let channel = 0; channel < numberOfChannels; channel++) {\n interleaved[frame * numberOfChannels + channel] = planes[channel]![frame]!;\n }\n }\n\n return interleaved.buffer;\n }\n\n private async setupAudioPipeline(clip: Clip): Promise<void> {\n const { id: clipId, resourceId, startUs, durationUs } = clip;\n const audioDemuxWorker = await this.deps.workers.get('audioDemux', clipId, { lazy: true });\n const decodeWorker = await this.deps.workers.get('decode');\n\n const demuxToDecodeChannel = new MessageChannel();\n await audioDemuxWorker.send(\n 'connect',\n { direction: 'downstream', port: demuxToDecodeChannel.port1, streamType: 'audio', clipId },\n { transfer: [demuxToDecodeChannel.port1] }\n );\n await decodeWorker.send(\n 'connect',\n {\n direction: 'upstream',\n port: demuxToDecodeChannel.port2,\n streamType: 'audio',\n clipId,\n clipStartUs: startUs || 0,\n clipDurationUs: durationUs || 0,\n },\n { transfer: [demuxToDecodeChannel.port2] }\n );\n\n const demuxConfig = this.deps.buildWorkerConfigs().audioDemux;\n await audioDemuxWorker.send('configure', {\n initial: true,\n resourceId,\n clipId,\n config: demuxConfig,\n });\n }\n\n private alignToWindow(timeUs: TimeUs): TimeUs {\n return Math.floor(timeUs / this.mixWindowUs) * this.mixWindowUs;\n }\n}\n"],"names":[],"mappings":"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"}