@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
@@ -45,7 +45,7 @@ class BaseEncoder {
45
45
  }
46
46
  const isSupported = await this.isConfigSupported(this.config);
47
47
  if (!isSupported.supported) {
48
- throw new Error(`Codec not supported: ${this.config.codec}`);
48
+ throw new Error(`Codec not supported: ${JSON.stringify(this.config)}`);
49
49
  }
50
50
  this.encoder = this.createEncoder({
51
51
  output: this.handleOutput.bind(this),
@@ -105,8 +105,16 @@ class BaseEncoder {
105
105
  get queueSize() {
106
106
  return this.encoder?.encodeQueueSize ?? 0;
107
107
  }
108
- handleOutput(chunk, _metadata) {
109
- this.controller?.enqueue(chunk);
108
+ handleOutput(chunk, metadata) {
109
+ if (this.controller) {
110
+ try {
111
+ this.controller.enqueue({ chunk, metadata });
112
+ } catch (error) {
113
+ if (!(error instanceof TypeError && error.message.includes("closed"))) {
114
+ throw error;
115
+ }
116
+ }
117
+ }
110
118
  }
111
119
  handleError(error) {
112
120
  console.error(`${this.getEncoderType()} encoder error:`, error);
@@ -144,7 +152,8 @@ class BaseEncoder {
144
152
  check();
145
153
  });
146
154
  }
147
- this.encode(input);
155
+ const frame = input.frame || input;
156
+ this.encode(frame);
148
157
  },
149
158
  flush: async () => {
150
159
  await this.flush();
@@ -207,12 +216,20 @@ class VideoChunkEncoder extends BaseEncoder {
207
216
  class AudioChunkEncoder extends BaseEncoder {
208
217
  static DEFAULT_HIGH_WATER_MARK = 4;
209
218
  static DEFAULT_ENCODE_QUEUE_THRESHOLD = 16;
219
+ static DEFAULT_CONFIG = {
220
+ codec: "mp4a.40.2",
221
+ // AAC-LC
222
+ sampleRate: 48e3,
223
+ numberOfChannels: 2,
224
+ bitrate: 128e3
225
+ };
210
226
  highWaterMark;
211
227
  encodeQueueThreshold;
212
228
  constructor(config) {
213
- super(config);
214
- this.highWaterMark = config.backpressure?.highWaterMark ?? AudioChunkEncoder.DEFAULT_HIGH_WATER_MARK;
215
- this.encodeQueueThreshold = config.backpressure?.encodeQueueThreshold ?? AudioChunkEncoder.DEFAULT_ENCODE_QUEUE_THRESHOLD;
229
+ const fullConfig = { ...AudioChunkEncoder.DEFAULT_CONFIG, ...config };
230
+ super(fullConfig);
231
+ this.highWaterMark = fullConfig.backpressure?.highWaterMark ?? AudioChunkEncoder.DEFAULT_HIGH_WATER_MARK;
232
+ this.encodeQueueThreshold = fullConfig.backpressure?.encodeQueueThreshold ?? AudioChunkEncoder.DEFAULT_ENCODE_QUEUE_THRESHOLD;
216
233
  }
217
234
  async isConfigSupported(config) {
218
235
  const result = await AudioEncoder.isConfigSupported(config);
@@ -228,20 +245,131 @@ class AudioChunkEncoder extends BaseEncoder {
228
245
  if (this.encoder?.state !== "configured") {
229
246
  throw new Error("Audio encoder not configured");
230
247
  }
231
- const config = this.getConfig();
232
- if (data.sampleRate !== config.sampleRate || data.numberOfChannels !== config.numberOfChannels) {
233
- throw new Error("AudioData requires resampling or channel remap before encoding");
234
- }
235
248
  this.encoder.encode(data);
236
249
  data.close();
237
250
  }
238
251
  }
252
+ class ClipEncoderManager {
253
+ videoEncoders = /* @__PURE__ */ new Map();
254
+ audioEncoders = /* @__PURE__ */ new Map();
255
+ videoCreationOrder = [];
256
+ audioCreationOrder = [];
257
+ maxEncoders;
258
+ constructor(maxEncoders = 6) {
259
+ this.maxEncoders = maxEncoders;
260
+ }
261
+ /**
262
+ * Acquire a video encoder for the given sessionId
263
+ * Creates new encoder if not exists; returns existing one if already created
264
+ */
265
+ async acquireVideo(sessionId, config) {
266
+ let encoder = this.videoEncoders.get(sessionId);
267
+ if (!encoder) {
268
+ if (this.videoEncoders.size >= this.maxEncoders) {
269
+ throw new Error(
270
+ `[ClipEncoderManager] Video encoder limit reached (${this.maxEncoders}). Active clips: ${Array.from(this.videoEncoders.keys()).join(", ")}. Please release unused encoders before acquiring new ones.`
271
+ );
272
+ }
273
+ encoder = new VideoChunkEncoder(config);
274
+ await encoder.initialize();
275
+ this.videoEncoders.set(sessionId, encoder);
276
+ this.videoCreationOrder.push(sessionId);
277
+ }
278
+ return encoder;
279
+ }
280
+ /**
281
+ * Acquire an audio encoder for the given sessionId
282
+ */
283
+ async acquireAudio(sessionId, config) {
284
+ let encoder = this.audioEncoders.get(sessionId);
285
+ if (!encoder) {
286
+ if (this.audioEncoders.size >= this.maxEncoders) {
287
+ throw new Error(
288
+ `[ClipEncoderManager] Audio encoder limit reached (${this.maxEncoders}). Active clips: ${Array.from(this.audioEncoders.keys()).join(", ")}. Please release unused encoders before acquiring new ones.`
289
+ );
290
+ }
291
+ encoder = new AudioChunkEncoder(config);
292
+ await encoder.initialize();
293
+ this.audioEncoders.set(sessionId, encoder);
294
+ this.audioCreationOrder.push(sessionId);
295
+ }
296
+ return encoder;
297
+ }
298
+ /**
299
+ * Release video encoder for the given sessionId
300
+ */
301
+ async releaseVideo(sessionId) {
302
+ const encoder = this.videoEncoders.get(sessionId);
303
+ if (encoder) {
304
+ await encoder.close();
305
+ this.videoEncoders.delete(sessionId);
306
+ const index = this.videoCreationOrder.indexOf(sessionId);
307
+ if (index !== -1) {
308
+ this.videoCreationOrder.splice(index, 1);
309
+ }
310
+ }
311
+ }
312
+ /**
313
+ * Release audio encoder for the given sessionId
314
+ */
315
+ async releaseAudio(sessionId) {
316
+ const encoder = this.audioEncoders.get(sessionId);
317
+ if (encoder) {
318
+ await encoder.close();
319
+ this.audioEncoders.delete(sessionId);
320
+ const index = this.audioCreationOrder.indexOf(sessionId);
321
+ if (index !== -1) {
322
+ this.audioCreationOrder.splice(index, 1);
323
+ }
324
+ }
325
+ }
326
+ /**
327
+ * Release both video and audio encoders for the given sessionId
328
+ */
329
+ async releaseClip(sessionId) {
330
+ await Promise.all([this.releaseVideo(sessionId), this.releaseAudio(sessionId)]);
331
+ }
332
+ /**
333
+ * Close all encoders and clear state
334
+ */
335
+ async closeAll() {
336
+ const promises = [
337
+ ...Array.from(this.videoEncoders.values()).map((e) => e.close()),
338
+ ...Array.from(this.audioEncoders.values()).map((e) => e.close())
339
+ ];
340
+ await Promise.all(promises);
341
+ this.videoEncoders.clear();
342
+ this.audioEncoders.clear();
343
+ this.videoCreationOrder = [];
344
+ this.audioCreationOrder = [];
345
+ }
346
+ /**
347
+ * Check if encoders exist for the given sessionId
348
+ */
349
+ has(sessionId) {
350
+ return this.videoEncoders.has(sessionId) || this.audioEncoders.has(sessionId);
351
+ }
352
+ /**
353
+ * Get statistics about current encoder state
354
+ */
355
+ getStats() {
356
+ return {
357
+ videoEncoders: this.videoEncoders.size,
358
+ audioEncoders: this.audioEncoders.size,
359
+ maxEncoders: this.maxEncoders,
360
+ videoCreationOrder: [...this.videoCreationOrder],
361
+ audioCreationOrder: [...this.audioCreationOrder]
362
+ };
363
+ }
364
+ }
239
365
  class EncodeWorker {
240
366
  channel;
241
- videoEncoder = null;
242
- audioEncoder = null;
367
+ encoderManager = new ClipEncoderManager(12);
368
+ // Increased to prevent eviction during active streams
369
+ // Default encoder configs
370
+ defaultVideoConfig = null;
371
+ defaultAudioConfig = null;
243
372
  // Connections to other workers
244
- cachePort = null;
245
373
  muxPort = null;
246
374
  composePorts = /* @__PURE__ */ new Map();
247
375
  // Connections from compose workers
@@ -256,9 +384,9 @@ class EncodeWorker {
256
384
  this.channel.registerHandler("configure", this.handleConfigure.bind(this));
257
385
  this.channel.registerHandler("connect", this.handleConnect.bind(this));
258
386
  this.channel.registerHandler("configure_video", this.handleConfigureVideo.bind(this));
259
- this.channel.registerHandler("configure_audio", this.handleConfigureAudio.bind(this));
260
387
  this.channel.registerHandler("flush", this.handleFlush.bind(this));
261
388
  this.channel.registerHandler("reset", this.handleReset.bind(this));
389
+ this.channel.registerHandler("release_clip_encoder", this.handleReleaseClipEncoder.bind(this));
262
390
  this.channel.registerHandler("get_stats", this.handleGetStats.bind(this));
263
391
  this.channel.registerHandler(WorkerMessageType.Dispose, this.handleDispose.bind(this));
264
392
  }
@@ -266,15 +394,19 @@ class EncodeWorker {
266
394
  * Connect handler used by stream pipeline
267
395
  */
268
396
  async handleConnect(payload) {
269
- const { port, streamType } = payload;
270
- if (streamType === "video") return this.handleConnectComposer({ composeType: "video", port });
271
- if (streamType === "audio") return this.handleConnectComposer({ composeType: "audio", port });
397
+ const { port, streamType, sessionId } = payload;
398
+ if (streamType === "video") {
399
+ return this.handleConnectComposer({ composeType: "video", port, sessionId });
400
+ }
401
+ if (streamType === "audio") {
402
+ return this.handleConnectComposer({ composeType: "audio", port, sessionId });
403
+ }
272
404
  if (streamType === "chunk") return this.handleConnectMux({ port });
273
405
  return { success: true };
274
406
  }
275
407
  /**
276
408
  * Handle configuration message from orchestrator
277
- * @param payload.initial - If true, initialize worker and recreate encoder instances; otherwise just update config
409
+ * @param payload.initial - If true, initialize worker; saves default encoder configs
278
410
  */
279
411
  async handleConfigure(payload) {
280
412
  const { config, initial = false } = payload;
@@ -282,51 +414,10 @@ class EncodeWorker {
282
414
  this.channel.state = WorkerState.Ready;
283
415
  }
284
416
  if (config.video) {
285
- if (initial || !this.videoEncoder) {
286
- if (this.videoEncoder) {
287
- await this.videoEncoder.close();
288
- }
289
- this.videoEncoder = new VideoChunkEncoder(config.video);
290
- await this.videoEncoder.initialize();
291
- const videoStream = config.video.stream?.pipeThrough(this.videoEncoder.createStream());
292
- if (videoStream && this.cachePort) {
293
- const cacheChannel = new WorkerChannel(this.cachePort, {
294
- name: "Encode-Cache-Video",
295
- timeout: 3e4
296
- });
297
- await cacheChannel.sendStream(videoStream, {
298
- type: "video",
299
- width: config.video.width,
300
- height: config.video.height,
301
- framerate: config.video.framerate
302
- });
303
- }
304
- } else {
305
- await this.videoEncoder.reconfigure(config.video);
306
- }
417
+ this.defaultVideoConfig = config.video;
307
418
  }
308
419
  if (config.audio) {
309
- if (initial || !this.audioEncoder) {
310
- if (this.audioEncoder) {
311
- await this.audioEncoder.close();
312
- }
313
- this.audioEncoder = new AudioChunkEncoder(config.audio);
314
- await this.audioEncoder.initialize();
315
- const audioStream = config.audio.stream?.pipeThrough(this.audioEncoder.createStream());
316
- if (audioStream && this.cachePort) {
317
- const cacheChannel = new WorkerChannel(this.cachePort, {
318
- name: "Encode-Cache-Audio",
319
- timeout: 3e4
320
- });
321
- await cacheChannel.sendStream(audioStream, {
322
- type: "audio",
323
- sampleRate: config.audio.sampleRate,
324
- numberOfChannels: config.audio.numberOfChannels
325
- });
326
- }
327
- } else {
328
- await this.audioEncoder.reconfigure(config.audio);
329
- }
420
+ this.defaultAudioConfig = config.audio;
330
421
  }
331
422
  return { success: true };
332
423
  }
@@ -334,55 +425,28 @@ class EncodeWorker {
334
425
  * Connect to a compose worker to receive frames/audio data
335
426
  */
336
427
  async handleConnectComposer(payload) {
337
- const { composeType, port } = payload;
428
+ const { composeType, port, sessionId } = payload;
338
429
  this.composePorts.set(composeType, port);
339
430
  const composeChannel = new WorkerChannel(port, {
340
431
  name: `Encode-${composeType}Compose`,
341
432
  timeout: 3e4
342
433
  });
343
434
  composeChannel.receiveStream(async (stream, metadata) => {
344
- if (metadata?.streamType === "video" && this.videoEncoder) {
345
- const reader = stream.getReader();
346
- try {
347
- while (true) {
348
- const { done, value } = await reader.read();
349
- if (done) break;
350
- const wrappedValue = value;
351
- const videoFrame = wrappedValue.frame || wrappedValue;
352
- try {
353
- const frame = videoFrame.clone();
354
- this.videoEncoder.encode(frame);
355
- } finally {
356
- videoFrame.close();
357
- }
358
- }
359
- } finally {
360
- reader.releaseLock();
361
- }
362
- } else if (metadata?.streamType === "audio" && this.audioEncoder) {
363
- const composedConfig = {
364
- sampleRate: metadata.sampleRate,
365
- numberOfChannels: metadata.numberOfChannels
366
- };
367
- const currentConfig = this.audioEncoder.getConfig();
368
- if (typeof composedConfig.sampleRate === "number" && composedConfig.sampleRate > 0 && composedConfig.sampleRate !== currentConfig.sampleRate) {
369
- await this.audioEncoder.reconfigure({ sampleRate: composedConfig.sampleRate });
370
- }
371
- if (typeof composedConfig.numberOfChannels === "number" && composedConfig.numberOfChannels > 0 && composedConfig.numberOfChannels !== currentConfig.numberOfChannels) {
372
- await this.audioEncoder.reconfigure({
373
- numberOfChannels: composedConfig.numberOfChannels
374
- });
375
- }
376
- const reader = stream.getReader();
377
- try {
378
- while (true) {
379
- const { done, value } = await reader.read();
380
- if (done) break;
381
- this.audioEncoder.encode(value);
382
- }
383
- } finally {
384
- reader.releaseLock();
385
- }
435
+ const streamSessionId = metadata?.sessionId || sessionId || "unknown";
436
+ if (metadata?.streamType === "video" && this.defaultVideoConfig) {
437
+ const encoder = await this.encoderManager.acquireVideo(
438
+ streamSessionId,
439
+ this.defaultVideoConfig
440
+ );
441
+ const encodingTransform = encoder.createStream();
442
+ const encodedStream = stream.pipeThrough(
443
+ encodingTransform
444
+ );
445
+ this.channel.sendStream(encodedStream, {
446
+ streamType: "video",
447
+ track: "video",
448
+ sessionId: streamSessionId
449
+ });
386
450
  }
387
451
  });
388
452
  return { success: true };
@@ -406,12 +470,7 @@ class EncodeWorker {
406
470
  */
407
471
  async handleConfigureVideo(config) {
408
472
  try {
409
- if (!this.videoEncoder) {
410
- this.videoEncoder = new VideoChunkEncoder(config);
411
- await this.videoEncoder.initialize();
412
- } else {
413
- await this.videoEncoder.reconfigure(config);
414
- }
473
+ this.defaultVideoConfig = config;
415
474
  this.channel.notify("video_configured", {
416
475
  codec: config.codec,
417
476
  width: config.width,
@@ -427,109 +486,46 @@ class EncodeWorker {
427
486
  }
428
487
  }
429
488
  /**
430
- * Configure audio encoder with specific settings
489
+ * Flush encoders (deprecated with per-clip encoder architecture)
431
490
  */
432
- async handleConfigureAudio(config) {
433
- try {
434
- if (!this.audioEncoder) {
435
- this.audioEncoder = new AudioChunkEncoder(config);
436
- await this.audioEncoder.initialize();
437
- } else {
438
- await this.audioEncoder.reconfigure(config);
439
- }
440
- this.channel.notify("audio_configured", {
441
- codec: config.codec,
442
- sampleRate: config.sampleRate,
443
- numberOfChannels: config.numberOfChannels,
444
- bitrate: config.bitrate
445
- });
446
- return { success: true };
447
- } catch (error) {
448
- throw {
449
- code: "AUDIO_CONFIG_ERROR",
450
- message: error.message
451
- };
452
- }
491
+ async handleFlush(_payload) {
492
+ console.warn("[EncodeWorker] handleFlush is deprecated with per-clip encoder architecture");
493
+ return {};
453
494
  }
454
495
  /**
455
- * Flush encoders and get buffered chunks
496
+ * Reset encoders (deprecated with per-clip encoder architecture)
456
497
  */
457
- async handleFlush(payload) {
458
- try {
459
- const result = {};
460
- if (!payload?.type || payload.type === "video") {
461
- const chunks = await this.videoEncoder?.flush();
462
- if (chunks) {
463
- result.videoChunks = chunks;
464
- }
465
- }
466
- if (!payload?.type || payload.type === "audio") {
467
- const chunks = await this.audioEncoder?.flush();
468
- if (chunks) {
469
- result.audioChunks = chunks;
470
- }
471
- }
472
- return result;
473
- } catch (error) {
474
- throw {
475
- code: "FLUSH_ERROR",
476
- message: error.message
477
- };
478
- }
498
+ async handleReset(payload) {
499
+ console.warn("[EncodeWorker] handleReset is deprecated with per-clip encoder architecture");
500
+ this.channel.notify("reset_complete", {
501
+ type: payload?.type || "all"
502
+ });
503
+ return { success: true };
479
504
  }
480
505
  /**
481
- * Reset encoders
506
+ * Release encoder for a specific clip
482
507
  */
483
- async handleReset(payload) {
484
- try {
485
- if (!payload?.type || payload.type === "video") {
486
- await this.videoEncoder?.reset();
487
- }
488
- if (!payload?.type || payload.type === "audio") {
489
- await this.audioEncoder?.reset();
490
- }
491
- this.channel.notify("reset_complete", {
492
- type: payload?.type || "all"
493
- });
494
- return { success: true };
495
- } catch (error) {
496
- throw {
497
- code: "RESET_ERROR",
498
- message: error.message
499
- };
500
- }
508
+ async handleReleaseClipEncoder(payload) {
509
+ const { sessionId } = payload;
510
+ await this.encoderManager.releaseClip(sessionId);
511
+ return { success: true };
501
512
  }
502
513
  /**
503
514
  * Get encoder statistics
504
515
  */
505
516
  async handleGetStats() {
506
- const stats = {};
507
- if (this.videoEncoder) {
508
- stats.video = {
509
- configured: this.videoEncoder.isReady,
510
- queueSize: this.videoEncoder.queueSize
511
- };
512
- }
513
- if (this.audioEncoder) {
514
- stats.audio = {
515
- configured: this.audioEncoder.isReady,
516
- queueSize: this.audioEncoder.queueSize
517
- };
518
- }
517
+ const stats = {
518
+ encoders: this.encoderManager.getStats()
519
+ };
519
520
  return stats;
520
521
  }
521
- // Output and error handling is done via streams in the encoder itself
522
- // These placeholder methods can be implemented when needed for direct callback handling
523
522
  /**
524
523
  * Dispose worker and cleanup resources
525
524
  */
526
525
  async handleDispose() {
527
- await this.videoEncoder?.close();
528
- await this.audioEncoder?.close();
529
- this.videoEncoder = null;
530
- this.audioEncoder = null;
531
- this.cachePort?.close();
532
- this.cachePort = null;
526
+ await this.encoderManager.closeAll();
527
+ this.defaultVideoConfig = null;
528
+ this.defaultAudioConfig = null;
533
529
  this.muxPort?.close();
534
530
  this.muxPort = null;
535
531
  for (const port of this.composePorts.values()) {