node-av 3.1.3 → 5.0.0

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 (192) hide show
  1. package/README.md +88 -52
  2. package/binding.gyp +23 -11
  3. package/dist/api/audio-frame-buffer.d.ts +201 -0
  4. package/dist/api/audio-frame-buffer.js +275 -0
  5. package/dist/api/audio-frame-buffer.js.map +1 -0
  6. package/dist/api/bitstream-filter.d.ts +320 -78
  7. package/dist/api/bitstream-filter.js +684 -151
  8. package/dist/api/bitstream-filter.js.map +1 -1
  9. package/dist/api/constants.d.ts +44 -0
  10. package/dist/api/constants.js +45 -0
  11. package/dist/api/constants.js.map +1 -0
  12. package/dist/api/data/test_av1.ivf +0 -0
  13. package/dist/api/data/test_mjpeg.mjpeg +0 -0
  14. package/dist/api/data/test_vp8.ivf +0 -0
  15. package/dist/api/data/test_vp9.ivf +0 -0
  16. package/dist/api/decoder.d.ts +454 -77
  17. package/dist/api/decoder.js +1081 -271
  18. package/dist/api/decoder.js.map +1 -1
  19. package/dist/api/{media-input.d.ts → demuxer.d.ts} +295 -45
  20. package/dist/api/demuxer.js +1965 -0
  21. package/dist/api/demuxer.js.map +1 -0
  22. package/dist/api/encoder.d.ts +423 -132
  23. package/dist/api/encoder.js +1089 -240
  24. package/dist/api/encoder.js.map +1 -1
  25. package/dist/api/filter-complex.d.ts +769 -0
  26. package/dist/api/filter-complex.js +1596 -0
  27. package/dist/api/filter-complex.js.map +1 -0
  28. package/dist/api/filter-presets.d.ts +80 -5
  29. package/dist/api/filter-presets.js +117 -7
  30. package/dist/api/filter-presets.js.map +1 -1
  31. package/dist/api/filter.d.ts +561 -125
  32. package/dist/api/filter.js +1083 -274
  33. package/dist/api/filter.js.map +1 -1
  34. package/dist/api/{fmp4.d.ts → fmp4-stream.d.ts} +141 -140
  35. package/dist/api/fmp4-stream.js +539 -0
  36. package/dist/api/fmp4-stream.js.map +1 -0
  37. package/dist/api/hardware.d.ts +58 -6
  38. package/dist/api/hardware.js +127 -11
  39. package/dist/api/hardware.js.map +1 -1
  40. package/dist/api/index.d.ts +8 -4
  41. package/dist/api/index.js +17 -8
  42. package/dist/api/index.js.map +1 -1
  43. package/dist/api/io-stream.d.ts +6 -6
  44. package/dist/api/io-stream.js +5 -4
  45. package/dist/api/io-stream.js.map +1 -1
  46. package/dist/api/{media-output.d.ts → muxer.d.ts} +280 -66
  47. package/dist/api/muxer.js +1934 -0
  48. package/dist/api/muxer.js.map +1 -0
  49. package/dist/api/pipeline.d.ts +77 -29
  50. package/dist/api/pipeline.js +449 -439
  51. package/dist/api/pipeline.js.map +1 -1
  52. package/dist/api/rtp-stream.d.ts +312 -0
  53. package/dist/api/rtp-stream.js +630 -0
  54. package/dist/api/rtp-stream.js.map +1 -0
  55. package/dist/api/types.d.ts +533 -56
  56. package/dist/api/utilities/async-queue.d.ts +91 -0
  57. package/dist/api/utilities/async-queue.js +162 -0
  58. package/dist/api/utilities/async-queue.js.map +1 -0
  59. package/dist/api/utilities/audio-sample.d.ts +11 -1
  60. package/dist/api/utilities/audio-sample.js +10 -0
  61. package/dist/api/utilities/audio-sample.js.map +1 -1
  62. package/dist/api/utilities/channel-layout.d.ts +1 -0
  63. package/dist/api/utilities/channel-layout.js +1 -0
  64. package/dist/api/utilities/channel-layout.js.map +1 -1
  65. package/dist/api/utilities/image.d.ts +39 -1
  66. package/dist/api/utilities/image.js +38 -0
  67. package/dist/api/utilities/image.js.map +1 -1
  68. package/dist/api/utilities/index.d.ts +3 -0
  69. package/dist/api/utilities/index.js +6 -0
  70. package/dist/api/utilities/index.js.map +1 -1
  71. package/dist/api/utilities/media-type.d.ts +2 -1
  72. package/dist/api/utilities/media-type.js +1 -0
  73. package/dist/api/utilities/media-type.js.map +1 -1
  74. package/dist/api/utilities/pixel-format.d.ts +4 -1
  75. package/dist/api/utilities/pixel-format.js +3 -0
  76. package/dist/api/utilities/pixel-format.js.map +1 -1
  77. package/dist/api/utilities/sample-format.d.ts +6 -1
  78. package/dist/api/utilities/sample-format.js +5 -0
  79. package/dist/api/utilities/sample-format.js.map +1 -1
  80. package/dist/api/utilities/scheduler.d.ts +138 -0
  81. package/dist/api/utilities/scheduler.js +98 -0
  82. package/dist/api/utilities/scheduler.js.map +1 -0
  83. package/dist/api/utilities/streaming.d.ts +105 -15
  84. package/dist/api/utilities/streaming.js +201 -12
  85. package/dist/api/utilities/streaming.js.map +1 -1
  86. package/dist/api/utilities/timestamp.d.ts +15 -1
  87. package/dist/api/utilities/timestamp.js +14 -0
  88. package/dist/api/utilities/timestamp.js.map +1 -1
  89. package/dist/api/utilities/whisper-model.d.ts +310 -0
  90. package/dist/api/utilities/whisper-model.js +528 -0
  91. package/dist/api/utilities/whisper-model.js.map +1 -0
  92. package/dist/api/webrtc-stream.d.ts +288 -0
  93. package/dist/api/webrtc-stream.js +440 -0
  94. package/dist/api/webrtc-stream.js.map +1 -0
  95. package/dist/api/whisper.d.ts +324 -0
  96. package/dist/api/whisper.js +362 -0
  97. package/dist/api/whisper.js.map +1 -0
  98. package/dist/constants/constants.d.ts +54 -2
  99. package/dist/constants/constants.js +48 -1
  100. package/dist/constants/constants.js.map +1 -1
  101. package/dist/constants/encoders.d.ts +2 -1
  102. package/dist/constants/encoders.js +4 -3
  103. package/dist/constants/encoders.js.map +1 -1
  104. package/dist/constants/hardware.d.ts +26 -0
  105. package/dist/constants/hardware.js +27 -0
  106. package/dist/constants/hardware.js.map +1 -0
  107. package/dist/constants/index.d.ts +1 -0
  108. package/dist/constants/index.js +1 -0
  109. package/dist/constants/index.js.map +1 -1
  110. package/dist/ffmpeg/index.d.ts +3 -3
  111. package/dist/ffmpeg/index.js +3 -3
  112. package/dist/ffmpeg/utils.d.ts +27 -0
  113. package/dist/ffmpeg/utils.js +28 -16
  114. package/dist/ffmpeg/utils.js.map +1 -1
  115. package/dist/lib/binding.d.ts +22 -11
  116. package/dist/lib/binding.js.map +1 -1
  117. package/dist/lib/codec-context.d.ts +87 -0
  118. package/dist/lib/codec-context.js +125 -4
  119. package/dist/lib/codec-context.js.map +1 -1
  120. package/dist/lib/codec-parameters.d.ts +229 -1
  121. package/dist/lib/codec-parameters.js +264 -0
  122. package/dist/lib/codec-parameters.js.map +1 -1
  123. package/dist/lib/codec-parser.d.ts +23 -0
  124. package/dist/lib/codec-parser.js +25 -0
  125. package/dist/lib/codec-parser.js.map +1 -1
  126. package/dist/lib/codec.d.ts +26 -4
  127. package/dist/lib/codec.js +35 -0
  128. package/dist/lib/codec.js.map +1 -1
  129. package/dist/lib/dictionary.js +1 -0
  130. package/dist/lib/dictionary.js.map +1 -1
  131. package/dist/lib/error.js +1 -1
  132. package/dist/lib/error.js.map +1 -1
  133. package/dist/lib/fifo.d.ts +416 -0
  134. package/dist/lib/fifo.js +453 -0
  135. package/dist/lib/fifo.js.map +1 -0
  136. package/dist/lib/filter-context.d.ts +52 -11
  137. package/dist/lib/filter-context.js +56 -12
  138. package/dist/lib/filter-context.js.map +1 -1
  139. package/dist/lib/filter-graph.d.ts +9 -0
  140. package/dist/lib/filter-graph.js +13 -0
  141. package/dist/lib/filter-graph.js.map +1 -1
  142. package/dist/lib/filter.d.ts +21 -0
  143. package/dist/lib/filter.js +28 -0
  144. package/dist/lib/filter.js.map +1 -1
  145. package/dist/lib/format-context.d.ts +48 -14
  146. package/dist/lib/format-context.js +76 -7
  147. package/dist/lib/format-context.js.map +1 -1
  148. package/dist/lib/frame.d.ts +264 -1
  149. package/dist/lib/frame.js +351 -1
  150. package/dist/lib/frame.js.map +1 -1
  151. package/dist/lib/hardware-device-context.d.ts +3 -2
  152. package/dist/lib/hardware-device-context.js.map +1 -1
  153. package/dist/lib/index.d.ts +2 -0
  154. package/dist/lib/index.js +4 -0
  155. package/dist/lib/index.js.map +1 -1
  156. package/dist/lib/input-format.d.ts +21 -0
  157. package/dist/lib/input-format.js +42 -2
  158. package/dist/lib/input-format.js.map +1 -1
  159. package/dist/lib/native-types.d.ts +76 -27
  160. package/dist/lib/option.d.ts +25 -13
  161. package/dist/lib/option.js +28 -0
  162. package/dist/lib/option.js.map +1 -1
  163. package/dist/lib/output-format.d.ts +22 -1
  164. package/dist/lib/output-format.js +28 -0
  165. package/dist/lib/output-format.js.map +1 -1
  166. package/dist/lib/packet.d.ts +35 -0
  167. package/dist/lib/packet.js +52 -2
  168. package/dist/lib/packet.js.map +1 -1
  169. package/dist/lib/rational.d.ts +18 -0
  170. package/dist/lib/rational.js +19 -0
  171. package/dist/lib/rational.js.map +1 -1
  172. package/dist/lib/stream.d.ts +126 -0
  173. package/dist/lib/stream.js +188 -5
  174. package/dist/lib/stream.js.map +1 -1
  175. package/dist/lib/sync-queue.d.ts +179 -0
  176. package/dist/lib/sync-queue.js +197 -0
  177. package/dist/lib/sync-queue.js.map +1 -0
  178. package/dist/lib/types.d.ts +49 -1
  179. package/dist/lib/utilities.d.ts +281 -53
  180. package/dist/lib/utilities.js +298 -55
  181. package/dist/lib/utilities.js.map +1 -1
  182. package/install/check.js +2 -2
  183. package/package.json +37 -26
  184. package/dist/api/fmp4.js +0 -710
  185. package/dist/api/fmp4.js.map +0 -1
  186. package/dist/api/media-input.js +0 -1075
  187. package/dist/api/media-input.js.map +0 -1
  188. package/dist/api/media-output.js +0 -1040
  189. package/dist/api/media-output.js.map +0 -1
  190. package/dist/api/webrtc.d.ts +0 -664
  191. package/dist/api/webrtc.js +0 -1132
  192. package/dist/api/webrtc.js.map +0 -1
@@ -1,8 +1,55 @@
1
- /**
2
- * @module pipeline
3
- * High-level media processing pipeline for FFmpeg operations.
4
- * Provides a fluent API for building transcoding, filtering, and stream processing pipelines.
5
- */
1
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
2
+ if (value !== null && value !== void 0) {
3
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
4
+ var dispose, inner;
5
+ if (async) {
6
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
7
+ dispose = value[Symbol.asyncDispose];
8
+ }
9
+ if (dispose === void 0) {
10
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
11
+ dispose = value[Symbol.dispose];
12
+ if (async) inner = dispose;
13
+ }
14
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
15
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
16
+ env.stack.push({ value: value, dispose: dispose, async: async });
17
+ }
18
+ else if (async) {
19
+ env.stack.push({ async: true });
20
+ }
21
+ return value;
22
+ };
23
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
24
+ return function (env) {
25
+ function fail(e) {
26
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
27
+ env.hasError = true;
28
+ }
29
+ var r, s = 0;
30
+ function next() {
31
+ while (r = env.stack.pop()) {
32
+ try {
33
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
34
+ if (r.dispose) {
35
+ var result = r.dispose.call(r.value);
36
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
37
+ }
38
+ else s |= 1;
39
+ }
40
+ catch (e) {
41
+ fail(e);
42
+ }
43
+ }
44
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
45
+ if (env.hasError) throw env.error;
46
+ }
47
+ return next();
48
+ };
49
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
50
+ var e = new Error(message);
51
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
+ });
6
53
  // ============================================================================
7
54
  // Implementation
8
55
  // ============================================================================
@@ -43,6 +90,26 @@
43
90
  export function pipeline(...args) {
44
91
  // Detect pipeline type based on first argument
45
92
  const firstArg = args[0];
93
+ const secondArg = args[1];
94
+ // Check for shared Demuxer + NamedStages pattern
95
+ if (isDemuxer(firstArg) && isNamedStages(secondArg)) {
96
+ // Convert shared input to NamedInputs based on stages keys
97
+ const sharedInput = firstArg;
98
+ const stages = secondArg;
99
+ const namedInputs = {};
100
+ // Create NamedInputs with shared input for all streams in stages
101
+ for (const streamName of Object.keys(stages)) {
102
+ namedInputs[streamName] = sharedInput;
103
+ }
104
+ if (args.length === 3) {
105
+ // Full named pipeline with output(s)
106
+ return runNamedPipeline(namedInputs, stages, args[2]);
107
+ }
108
+ else {
109
+ // Partial named pipeline
110
+ return runNamedPartialPipeline(namedInputs, stages);
111
+ }
112
+ }
46
113
  if (isNamedInputs(firstArg)) {
47
114
  // Named pipeline (2 or 3 arguments)
48
115
  if (args.length === 2) {
@@ -54,14 +121,14 @@ export function pipeline(...args) {
54
121
  return runNamedPipeline(args[0], args[1], args[2]);
55
122
  }
56
123
  }
57
- else if (isMediaInput(firstArg)) {
58
- // Check if this is a stream copy (MediaInputMediaOutput)
59
- if (args.length === 2 && isMediaOutput(args[1])) {
124
+ else if (isDemuxer(firstArg)) {
125
+ // Check if this is a stream copy (DemuxerMuxer)
126
+ if (args.length === 2 && isMuxer(args[1])) {
60
127
  // Stream copy all streams
61
- return runMediaInputPipeline(args[0], args[1]);
128
+ return runDemuxerPipeline(args[0], args[1]);
62
129
  }
63
130
  else {
64
- // Simple pipeline starting with MediaInput
131
+ // Simple pipeline starting with Demuxer
65
132
  return runSimplePipeline(args);
66
133
  }
67
134
  }
@@ -128,10 +195,10 @@ class PipelineControlImpl {
128
195
  }
129
196
  }
130
197
  // ============================================================================
131
- // MediaInput Pipeline Implementation
198
+ // Demuxer Pipeline Implementation
132
199
  // ============================================================================
133
200
  /**
134
- * Run a media input pipeline for stream copy.
201
+ * Run a demuxer pipeline for stream copy.
135
202
  *
136
203
  * @param input - Media input source
137
204
  *
@@ -141,14 +208,14 @@ class PipelineControlImpl {
141
208
  *
142
209
  * @internal
143
210
  */
144
- function runMediaInputPipeline(input, output) {
211
+ function runDemuxerPipeline(input, output) {
145
212
  let control;
146
213
  // eslint-disable-next-line prefer-const
147
- control = new PipelineControlImpl(runMediaInputPipelineAsync(input, output, () => control.isStopped()));
214
+ control = new PipelineControlImpl(runDemuxerPipelineAsync(input, output, () => control?.isStopped() ?? false));
148
215
  return control;
149
216
  }
150
217
  /**
151
- * Run media input pipeline asynchronously.
218
+ * Run demuxer pipeline asynchronously.
152
219
  *
153
220
  * @param input - Media input source
154
221
  *
@@ -158,7 +225,7 @@ function runMediaInputPipeline(input, output) {
158
225
  *
159
226
  * @internal
160
227
  */
161
- async function runMediaInputPipelineAsync(input, output, shouldStop) {
228
+ async function runDemuxerPipelineAsync(input, output, shouldStop) {
162
229
  // Get all streams from input
163
230
  const videoStream = input.video();
164
231
  const audioStream = input.audio();
@@ -183,18 +250,35 @@ async function runMediaInputPipelineAsync(input, output, shouldStop) {
183
250
  }
184
251
  }
185
252
  // Copy all packets
186
- for await (const packet of input.packets()) {
187
- // Check if we should stop
188
- if (shouldStop()) {
189
- packet.free();
190
- break;
253
+ for await (const packet_1 of input.packets()) {
254
+ const env_1 = { stack: [], error: void 0, hasError: false };
255
+ try {
256
+ const packet = __addDisposableResource(env_1, packet_1, false);
257
+ // Check if we should stop
258
+ if (shouldStop()) {
259
+ break;
260
+ }
261
+ // Handle EOF signal (null packet from input means all streams are done)
262
+ if (packet === null) {
263
+ // Signal EOF for all streams
264
+ for (const mapping of streams) {
265
+ await output.writePacket(null, mapping.index);
266
+ }
267
+ break;
268
+ }
269
+ // Find the corresponding output stream index
270
+ const mapping = streams.find((s) => s.stream.index === packet.streamIndex);
271
+ if (mapping) {
272
+ await output.writePacket(packet, mapping.index);
273
+ }
191
274
  }
192
- // Find the corresponding output stream index
193
- const mapping = streams.find((s) => s.stream.index === packet.streamIndex);
194
- if (mapping) {
195
- await output.writePacket(packet, mapping.index);
275
+ catch (e_1) {
276
+ env_1.error = e_1;
277
+ env_1.hasError = true;
278
+ }
279
+ finally {
280
+ __disposeResources(env_1);
196
281
  }
197
- packet.free(); // Free packet after processing
198
282
  }
199
283
  await output.close();
200
284
  }
@@ -212,14 +296,14 @@ async function runMediaInputPipelineAsync(input, output, shouldStop) {
212
296
  */
213
297
  function runSimplePipeline(args) {
214
298
  const [source, ...stages] = args;
215
- // Check if last stage is MediaOutput (consumes stream)
299
+ // Check if last stage is Muxer (consumes stream)
216
300
  const lastStage = stages[stages.length - 1];
217
- const isOutput = isMediaOutput(lastStage);
301
+ const isOutput = isMuxer(lastStage);
218
302
  // Track metadata through pipeline
219
303
  const metadata = {};
220
- // Store MediaInput reference if we have one
221
- if (isMediaInput(source)) {
222
- metadata.mediaInput = source;
304
+ // Store Demuxer reference if we have one
305
+ if (isDemuxer(source)) {
306
+ metadata.demuxer = source;
223
307
  }
224
308
  // Build the pipeline generator
225
309
  // If output is present, exclude it from stages for processing
@@ -236,10 +320,10 @@ function runSimplePipeline(args) {
236
320
  metadata.bitStreamFilter = stage;
237
321
  }
238
322
  }
239
- // Convert MediaInput to packet stream if needed
323
+ // Convert Demuxer to packet stream if needed
240
324
  // If we have a decoder or BSF, filter packets by stream index
241
325
  let actualSource;
242
- if (isMediaInput(source)) {
326
+ if (isDemuxer(source)) {
243
327
  if (metadata.decoder) {
244
328
  // Filter packets for the decoder's stream
245
329
  const streamIndex = metadata.decoder.getStream().index;
@@ -263,7 +347,7 @@ function runSimplePipeline(args) {
263
347
  if (isOutput) {
264
348
  let control;
265
349
  // eslint-disable-next-line prefer-const
266
- control = new PipelineControlImpl(consumeSimplePipeline(generator, lastStage, metadata, () => control.isStopped()));
350
+ control = new PipelineControlImpl(consumeSimplePipeline(generator, lastStage, metadata, () => control?.isStopped() ?? false));
267
351
  return control;
268
352
  }
269
353
  // Otherwise return the generator for further processing
@@ -284,25 +368,25 @@ async function* buildSimplePipeline(source, stages) {
284
368
  let stream = source;
285
369
  for (const stage of stages) {
286
370
  if (isDecoder(stage)) {
287
- stream = decodeStream(stream, stage);
371
+ stream = stage.frames(stream);
288
372
  }
289
373
  else if (isEncoder(stage)) {
290
- stream = encodeStream(stream, stage);
374
+ stream = stage.packets(stream);
291
375
  }
292
376
  else if (isFilterAPI(stage)) {
293
- stream = filterStream(stream, stage);
377
+ stream = stage.frames(stream);
294
378
  }
295
379
  else if (isBitStreamFilterAPI(stage)) {
296
- stream = bitStreamFilterStream(stream, stage);
380
+ stream = stage.packets(stream);
297
381
  }
298
382
  else if (Array.isArray(stage)) {
299
383
  // Chain multiple filters or BSFs
300
384
  for (const filter of stage) {
301
385
  if (isFilterAPI(filter)) {
302
- stream = filterStream(stream, filter);
386
+ stream = filter.frames(stream);
303
387
  }
304
388
  else if (isBitStreamFilterAPI(filter)) {
305
- stream = bitStreamFilterStream(stream, filter);
389
+ stream = filter.packets(stream);
306
390
  }
307
391
  }
308
392
  }
@@ -326,7 +410,16 @@ async function consumeSimplePipeline(stream, output, metadata, shouldStop) {
326
410
  // Add stream to output if we have encoder or decoder info
327
411
  let streamIndex = 0;
328
412
  if (metadata.encoder) {
329
- streamIndex = output.addStream(metadata.encoder);
413
+ // Encoding path
414
+ if (metadata.decoder) {
415
+ // Have decoder - use its stream for metadata/properties
416
+ const originalStream = metadata.decoder.getStream();
417
+ streamIndex = output.addStream(originalStream, { encoder: metadata.encoder });
418
+ }
419
+ else {
420
+ // Encoder-only mode (e.g., frame generator) - no input stream
421
+ streamIndex = output.addStream(metadata.encoder);
422
+ }
330
423
  }
331
424
  else if (metadata.decoder) {
332
425
  // Stream copy - use decoder's original stream
@@ -339,29 +432,37 @@ async function consumeSimplePipeline(stream, output, metadata, shouldStop) {
339
432
  streamIndex = output.addStream(originalStream);
340
433
  }
341
434
  else {
342
- // For direct MediaInputMediaOutput, we redirect to runMediaInputPipeline
435
+ // For direct DemuxerMuxer, we redirect to runDemuxerPipeline
343
436
  // This case shouldn't happen in simple pipeline
344
437
  throw new Error('Cannot determine stream configuration. This is likely a bug in the pipeline.');
345
438
  }
346
439
  // Process stream
347
- for await (const item of stream) {
348
- // Check if we should stop
349
- if (shouldStop()) {
350
- if (isPacket(item)) {
351
- item.free();
440
+ for await (const item_1 of stream) {
441
+ const env_2 = { stack: [], error: void 0, hasError: false };
442
+ try {
443
+ const item = __addDisposableResource(env_2, item_1, false);
444
+ // Check if we should stop
445
+ if (shouldStop()) {
446
+ break;
447
+ }
448
+ // Handle EOF signal
449
+ if (item === null) {
450
+ await output.writePacket(null, streamIndex);
451
+ break;
452
+ }
453
+ if (isPacket(item) || item === null) {
454
+ await output.writePacket(item, streamIndex);
352
455
  }
353
456
  else {
354
- item.free();
457
+ throw new Error('Cannot write frames directly to Muxer. Use an encoder first.');
355
458
  }
356
- break;
357
459
  }
358
- if (isPacket(item)) {
359
- await output.writePacket(item, streamIndex);
360
- // Free the packet after writing
361
- item.free();
460
+ catch (e_2) {
461
+ env_2.error = e_2;
462
+ env_2.hasError = true;
362
463
  }
363
- else {
364
- throw new Error('Cannot write frames directly to MediaOutput. Use an encoder first.');
464
+ finally {
465
+ __disposeResources(env_2);
365
466
  }
366
467
  }
367
468
  await output.close();
@@ -403,7 +504,19 @@ function runNamedPartialPipeline(inputs, stages) {
403
504
  if (!stream) {
404
505
  throw new Error(`No ${streamName} stream found in input.`);
405
506
  }
406
- if (streamStages === 'passthrough') {
507
+ // Normalize stages: if array contains only undefined, treat as passthrough
508
+ // Also filter out undefined entries from the array
509
+ let normalizedStages = streamStages;
510
+ if (Array.isArray(streamStages)) {
511
+ const definedStages = streamStages.filter((stage) => stage !== undefined);
512
+ if (definedStages.length === 0) {
513
+ normalizedStages = 'passthrough';
514
+ }
515
+ else {
516
+ normalizedStages = definedStages;
517
+ }
518
+ }
519
+ if (normalizedStages === 'passthrough') {
407
520
  // Direct passthrough - return input packets for this specific stream
408
521
  result[streamName] = (async function* () {
409
522
  for await (const packet of input.packets(stream.index)) {
@@ -415,7 +528,8 @@ function runNamedPartialPipeline(inputs, stages) {
415
528
  // Process the stream - pass packets for this specific stream only
416
529
  // Build pipeline for this stream (can return frames or packets)
417
530
  const metadata = {};
418
- result[streamName] = buildFlexibleNamedStreamPipeline(input.packets(stream.index), streamStages, metadata);
531
+ const stages = normalizedStages;
532
+ result[streamName] = buildFlexibleNamedStreamPipeline(input.packets(stream.index), stages, metadata);
419
533
  }
420
534
  }
421
535
  return result;
@@ -436,7 +550,7 @@ function runNamedPartialPipeline(inputs, stages) {
436
550
  function runNamedPipeline(inputs, stages, output) {
437
551
  let control;
438
552
  // eslint-disable-next-line prefer-const
439
- control = new PipelineControlImpl(runNamedPipelineAsync(inputs, stages, output, () => control.isStopped()));
553
+ control = new PipelineControlImpl(runNamedPipelineAsync(inputs, stages, output, () => control?.isStopped() ?? false));
440
554
  return control;
441
555
  }
442
556
  /**
@@ -453,84 +567,210 @@ function runNamedPipeline(inputs, stages, output) {
453
567
  * @internal
454
568
  */
455
569
  async function runNamedPipelineAsync(inputs, stages, output, shouldStop) {
570
+ // Check if all inputs reference the same Demuxer instance
571
+ const inputValues = Object.values(inputs);
572
+ const allSameInput = inputValues.length > 1 && inputValues.every((input) => input === inputValues[0]);
456
573
  // Track metadata for each stream
457
574
  const streamMetadata = {};
458
575
  // Process each named stream into generators
459
576
  const processedStreams = {};
460
- for (const [streamName, streamStages] of Object.entries(stages)) {
461
- const metadata = {};
462
- streamMetadata[streamName] = metadata;
463
- const input = inputs[streamName];
464
- if (!input) {
465
- throw new Error(`No input found for stream: ${streamName}`);
466
- }
467
- if (streamStages === 'passthrough') {
468
- // Direct passthrough - no processing
469
- let stream = null;
470
- switch (streamName) {
471
- case 'video':
472
- stream = input.video() ?? null;
473
- metadata.type = 'video';
474
- break;
475
- case 'audio':
476
- stream = input.audio() ?? null;
477
- metadata.type = 'audio';
478
- break;
479
- }
480
- if (!stream) {
481
- throw new Error(`No ${streamName} stream found in input for passthrough.`);
577
+ // If all inputs are the same instance, use Demuxer's built-in parallel packet generators
578
+ if (allSameInput) {
579
+ const sharedInput = inputValues[0];
580
+ // Single pass: collect metadata and build pipelines directly using input.packets(streamIndex)
581
+ for (const [streamName, streamStages] of Object.entries(stages)) {
582
+ const metadata = {};
583
+ streamMetadata[streamName] = metadata;
584
+ // Normalize stages
585
+ let normalizedStages = streamStages;
586
+ if (Array.isArray(streamStages)) {
587
+ const definedStages = streamStages.filter((stage) => stage !== undefined);
588
+ if (definedStages.length === 0) {
589
+ normalizedStages = 'passthrough';
590
+ }
591
+ else {
592
+ normalizedStages = definedStages;
593
+ }
482
594
  }
483
- processedStreams[streamName] = input.packets(stream.index);
484
- metadata.mediaInput = input; // Track MediaInput for passthrough
485
- }
486
- else {
487
- // Process the stream
488
- // Pre-populate metadata by walking through stages
489
- for (const stage of streamStages) {
490
- if (isDecoder(stage)) {
491
- metadata.decoder = stage;
595
+ // Determine stream index and build pipeline
596
+ let streamIndex;
597
+ if (normalizedStages !== 'passthrough') {
598
+ const stages = normalizedStages;
599
+ // Set stream type
600
+ metadata.type = streamName;
601
+ // Populate metadata by walking through ALL stages
602
+ for (const stage of stages) {
603
+ if (isDecoder(stage)) {
604
+ metadata.decoder = stage;
605
+ streamIndex ??= stage.getStream().index;
606
+ }
607
+ else if (isBitStreamFilterAPI(stage)) {
608
+ metadata.bitStreamFilter = stage;
609
+ streamIndex ??= stage.getStream().index;
610
+ }
611
+ else if (isEncoder(stage)) {
612
+ metadata.encoder = stage;
613
+ }
492
614
  }
493
- else if (isEncoder(stage)) {
494
- metadata.encoder = stage;
615
+ // If no decoder/BSF, use stream name to determine index
616
+ if (streamIndex === undefined) {
617
+ const stream = streamName === 'video' ? sharedInput.video() : sharedInput.audio();
618
+ if (!stream) {
619
+ throw new Error(`No ${streamName} stream found in input.`);
620
+ }
621
+ streamIndex = stream.index;
495
622
  }
496
- else if (isBitStreamFilterAPI(stage)) {
497
- metadata.bitStreamFilter = stage;
623
+ // Build pipeline with packets from this specific stream
624
+ processedStreams[streamName] = buildNamedStreamPipeline(sharedInput.packets(streamIndex), stages, metadata);
625
+ }
626
+ else {
627
+ // Passthrough - use Demuxer's built-in stream filtering
628
+ metadata.type = streamName;
629
+ metadata.demuxer = sharedInput;
630
+ const stream = streamName === 'video' ? sharedInput.video() : sharedInput.audio();
631
+ if (!stream) {
632
+ throw new Error(`No ${streamName} stream found in input for passthrough.`);
498
633
  }
634
+ streamIndex = stream.index;
635
+ // Direct passthrough using input.packets(streamIndex)
636
+ processedStreams[streamName] = sharedInput.packets(streamIndex);
499
637
  }
500
- // Get packets - filter by stream index based on decoder, BSF, or stream type
501
- let packets;
502
- if (metadata.decoder) {
503
- const streamIndex = metadata.decoder.getStream().index;
504
- packets = input.packets(streamIndex);
638
+ }
639
+ }
640
+ else {
641
+ // Original logic: separate inputs or single input
642
+ for (const [streamName, streamStages] of Object.entries(stages)) {
643
+ const metadata = {};
644
+ streamMetadata[streamName] = metadata;
645
+ const input = inputs[streamName];
646
+ if (!input) {
647
+ throw new Error(`No input found for stream: ${streamName}`);
505
648
  }
506
- else if (metadata.bitStreamFilter) {
507
- const streamIndex = metadata.bitStreamFilter.getStream().index;
508
- packets = input.packets(streamIndex);
649
+ // Normalize stages: if array contains only undefined, treat as passthrough
650
+ // Also filter out undefined entries from the array
651
+ let normalizedStages = streamStages;
652
+ if (Array.isArray(streamStages)) {
653
+ const definedStages = streamStages.filter((stage) => stage !== undefined);
654
+ if (definedStages.length === 0) {
655
+ normalizedStages = 'passthrough';
656
+ }
657
+ else {
658
+ normalizedStages = definedStages;
659
+ }
509
660
  }
510
- else {
511
- // No decoder or BSF - determine stream by name
661
+ if (normalizedStages === 'passthrough') {
662
+ // Direct passthrough - no processing
512
663
  let stream = null;
513
664
  switch (streamName) {
514
665
  case 'video':
515
666
  stream = input.video() ?? null;
667
+ metadata.type = 'video';
516
668
  break;
517
669
  case 'audio':
518
670
  stream = input.audio() ?? null;
671
+ metadata.type = 'audio';
519
672
  break;
520
673
  }
521
674
  if (!stream) {
522
- throw new Error(`No ${streamName} stream found in input.`);
675
+ throw new Error(`No ${streamName} stream found in input for passthrough.`);
523
676
  }
524
- packets = input.packets(stream.index);
677
+ processedStreams[streamName] = input.packets(stream.index);
678
+ metadata.demuxer = input; // Track Demuxer for passthrough
679
+ }
680
+ else {
681
+ // Process the stream - normalizedStages is guaranteed to be an array here
682
+ const stages = normalizedStages;
683
+ // Pre-populate metadata by walking through stages
684
+ for (const stage of stages) {
685
+ if (isDecoder(stage)) {
686
+ metadata.decoder = stage;
687
+ }
688
+ else if (isEncoder(stage)) {
689
+ metadata.encoder = stage;
690
+ }
691
+ else if (isBitStreamFilterAPI(stage)) {
692
+ metadata.bitStreamFilter = stage;
693
+ }
694
+ }
695
+ // Get packets - filter by stream index based on decoder, BSF, or stream type
696
+ let packets;
697
+ if (metadata.decoder) {
698
+ const streamIndex = metadata.decoder.getStream().index;
699
+ packets = input.packets(streamIndex);
700
+ }
701
+ else if (metadata.bitStreamFilter) {
702
+ const streamIndex = metadata.bitStreamFilter.getStream().index;
703
+ packets = input.packets(streamIndex);
704
+ }
705
+ else {
706
+ // No decoder or BSF - determine stream by name
707
+ let stream = null;
708
+ switch (streamName) {
709
+ case 'video':
710
+ stream = input.video() ?? null;
711
+ break;
712
+ case 'audio':
713
+ stream = input.audio() ?? null;
714
+ break;
715
+ }
716
+ if (!stream) {
717
+ throw new Error(`No ${streamName} stream found in input.`);
718
+ }
719
+ packets = input.packets(stream.index);
720
+ }
721
+ // Build pipeline for this stream
722
+ processedStreams[streamName] = buildNamedStreamPipeline(packets, stages, metadata);
525
723
  }
526
- // Build pipeline for this stream
527
- processedStreams[streamName] = buildNamedStreamPipeline(packets, streamStages, metadata);
528
724
  }
529
725
  }
530
726
  // Write to output(s)
531
- if (isMediaOutput(output)) {
532
- // Single output - properly interleave all streams
533
- await interleaveToOutput(processedStreams, output, streamMetadata, shouldStop);
727
+ if (isMuxer(output)) {
728
+ // Always write streams in parallel - Muxer's SyncQueue handles interleaving internally
729
+ const streamIndices = {};
730
+ // Add all streams to output first
731
+ for (const [name, meta] of Object.entries(streamMetadata)) {
732
+ if (meta.encoder) {
733
+ // Encoding path
734
+ if (meta.decoder) {
735
+ // Have decoder - use its stream for metadata/properties
736
+ const originalStream = meta.decoder.getStream();
737
+ streamIndices[name] = output.addStream(originalStream, { encoder: meta.encoder });
738
+ }
739
+ else {
740
+ // Encoder-only mode (e.g., frame generator) - no input stream
741
+ streamIndices[name] = output.addStream(meta.encoder);
742
+ }
743
+ }
744
+ else if (meta.decoder) {
745
+ // Stream copy - use decoder's original stream
746
+ const originalStream = meta.decoder.getStream();
747
+ streamIndices[name] = output.addStream(originalStream);
748
+ }
749
+ else if (meta.bitStreamFilter) {
750
+ // BSF - use BSF's original stream
751
+ const originalStream = meta.bitStreamFilter.getStream();
752
+ streamIndices[name] = output.addStream(originalStream);
753
+ }
754
+ else if (meta.demuxer) {
755
+ // Passthrough from Demuxer
756
+ const stream = name.includes('video') ? meta.demuxer.video() : meta.demuxer.audio();
757
+ if (!stream) {
758
+ throw new Error(`No matching stream found in Demuxer for ${name}`);
759
+ }
760
+ streamIndices[name] = output.addStream(stream);
761
+ }
762
+ else {
763
+ throw new Error(`Cannot determine stream configuration for ${name}. This is likely a bug in the pipeline.`);
764
+ }
765
+ }
766
+ // Write all streams in parallel - Muxer's SyncQueue handles interleaving
767
+ const promises = [];
768
+ for (const [name, stream] of Object.entries(processedStreams)) {
769
+ const streamIndex = streamIndices[name];
770
+ promises.push(consumeStreamInParallel(stream, output, streamIndex, shouldStop));
771
+ }
772
+ await Promise.all(promises);
773
+ await output.close();
534
774
  }
535
775
  else {
536
776
  // Multiple outputs - write each stream to its output
@@ -564,27 +804,27 @@ async function* buildFlexibleNamedStreamPipeline(source, stages, metadata) {
564
804
  for (const stage of stages) {
565
805
  if (isDecoder(stage)) {
566
806
  metadata.decoder = stage;
567
- stream = decodeStream(stream, stage);
807
+ stream = stage.frames(stream);
568
808
  }
569
809
  else if (isEncoder(stage)) {
570
810
  metadata.encoder = stage;
571
- stream = encodeStream(stream, stage);
811
+ stream = stage.packets(stream);
572
812
  }
573
813
  else if (isFilterAPI(stage)) {
574
- stream = filterStream(stream, stage);
814
+ stream = stage.frames(stream);
575
815
  }
576
816
  else if (isBitStreamFilterAPI(stage)) {
577
817
  metadata.bitStreamFilter = stage;
578
- stream = bitStreamFilterStream(stream, stage);
818
+ stream = stage.packets(stream);
579
819
  }
580
820
  else if (Array.isArray(stage)) {
581
821
  // Chain multiple filters or BSFs
582
822
  for (const filter of stage) {
583
823
  if (isFilterAPI(filter)) {
584
- stream = filterStream(stream, filter);
824
+ stream = filter.frames(stream);
585
825
  }
586
826
  else if (isBitStreamFilterAPI(filter)) {
587
- stream = bitStreamFilterStream(stream, filter);
827
+ stream = filter.packets(stream);
588
828
  }
589
829
  }
590
830
  }
@@ -610,34 +850,34 @@ async function* buildNamedStreamPipeline(source, stages, metadata) {
610
850
  for (const stage of stages) {
611
851
  if (isDecoder(stage)) {
612
852
  metadata.decoder = stage;
613
- stream = decodeStream(stream, stage);
853
+ stream = stage.frames(stream);
614
854
  }
615
855
  else if (isEncoder(stage)) {
616
856
  metadata.encoder = stage;
617
- stream = encodeStream(stream, stage);
857
+ stream = stage.packets(stream);
618
858
  }
619
859
  else if (isFilterAPI(stage)) {
620
- stream = filterStream(stream, stage);
860
+ stream = stage.frames(stream);
621
861
  }
622
862
  else if (isBitStreamFilterAPI(stage)) {
623
863
  metadata.bitStreamFilter = stage;
624
- stream = bitStreamFilterStream(stream, stage);
864
+ stream = stage.packets(stream);
625
865
  }
626
866
  else if (Array.isArray(stage)) {
627
867
  // Chain multiple filters or BSFs
628
868
  for (const filter of stage) {
629
869
  if (isFilterAPI(filter)) {
630
- stream = filterStream(stream, filter);
870
+ stream = filter.frames(stream);
631
871
  }
632
872
  else if (isBitStreamFilterAPI(filter)) {
633
- stream = bitStreamFilterStream(stream, filter);
873
+ stream = filter.packets(stream);
634
874
  }
635
875
  }
636
876
  }
637
877
  }
638
878
  // Ensure we're yielding packets
639
879
  for await (const item of stream) {
640
- if (isPacket(item)) {
880
+ if (isPacket(item) || item === null) {
641
881
  yield item;
642
882
  }
643
883
  else {
@@ -645,6 +885,42 @@ async function* buildNamedStreamPipeline(source, stages, metadata) {
645
885
  }
646
886
  }
647
887
  }
888
+ /**
889
+ * Consume a stream in parallel (for passthrough pipelines).
890
+ * Stream index is already added to output.
891
+ *
892
+ * @param stream - Stream of packets
893
+ *
894
+ * @param output - Media output destination
895
+ *
896
+ * @param streamIndex - Pre-allocated stream index in output
897
+ *
898
+ * @param shouldStop - Function to check if pipeline should stop
899
+ *
900
+ * @internal
901
+ */
902
+ async function consumeStreamInParallel(stream, output, streamIndex, shouldStop) {
903
+ // Write all packets (including EOF null)
904
+ for await (const packet_2 of stream) {
905
+ const env_3 = { stack: [], error: void 0, hasError: false };
906
+ try {
907
+ const packet = __addDisposableResource(env_3, packet_2, false);
908
+ // Check if we should stop
909
+ if (shouldStop()) {
910
+ break;
911
+ }
912
+ await output.writePacket(packet, streamIndex);
913
+ }
914
+ catch (e_3) {
915
+ env_3.error = e_3;
916
+ env_3.hasError = true;
917
+ }
918
+ finally {
919
+ __disposeResources(env_3);
920
+ }
921
+ }
922
+ // Note: Don't close output here - it will be closed by the caller after all streams finish
923
+ }
648
924
  /**
649
925
  * Consume a named stream and write to output.
650
926
  *
@@ -662,7 +938,16 @@ async function consumeNamedStream(stream, output, metadata, shouldStop) {
662
938
  // Add stream to output
663
939
  let streamIndex = 0;
664
940
  if (metadata.encoder) {
665
- streamIndex = output.addStream(metadata.encoder);
941
+ // Encoding path
942
+ if (metadata.decoder) {
943
+ // Have decoder - use its stream for metadata/properties
944
+ const originalStream = metadata.decoder.getStream();
945
+ streamIndex = output.addStream(originalStream, { encoder: metadata.encoder });
946
+ }
947
+ else {
948
+ // Encoder-only mode (e.g., frame generator) - no input stream
949
+ streamIndex = output.addStream(metadata.encoder);
950
+ }
666
951
  }
667
952
  else if (metadata.decoder) {
668
953
  // Stream copy - use decoder's original stream
@@ -674,11 +959,11 @@ async function consumeNamedStream(stream, output, metadata, shouldStop) {
674
959
  const originalStream = metadata.bitStreamFilter.getStream();
675
960
  streamIndex = output.addStream(originalStream);
676
961
  }
677
- else if (metadata.mediaInput) {
678
- // Passthrough from MediaInput - use type hint from metadata
679
- const inputStream = metadata.type === 'video' ? metadata.mediaInput.video() : metadata.mediaInput.audio();
962
+ else if (metadata.demuxer) {
963
+ // Passthrough from Demuxer - use type hint from metadata
964
+ const inputStream = metadata.type === 'video' ? metadata.demuxer.video() : metadata.demuxer.audio();
680
965
  if (!inputStream) {
681
- throw new Error(`No ${metadata.type} stream found in MediaInput`);
966
+ throw new Error(`No ${metadata.type} stream found in Demuxer`);
682
967
  }
683
968
  streamIndex = output.addStream(inputStream);
684
969
  }
@@ -688,333 +973,58 @@ async function consumeNamedStream(stream, output, metadata, shouldStop) {
688
973
  }
689
974
  // Store for later use
690
975
  metadata.streamIndex = streamIndex;
691
- // Write all packets
692
- for await (const packet of stream) {
693
- // Check if we should stop
694
- if (shouldStop()) {
695
- packet.free();
696
- break;
697
- }
976
+ // Write all packets (including EOF null)
977
+ for await (const packet_3 of stream) {
978
+ const env_4 = { stack: [], error: void 0, hasError: false };
698
979
  try {
699
- await output.writePacket(packet, streamIndex);
700
- }
701
- finally {
702
- packet.free(); // Free packet after writing
703
- }
704
- }
705
- // Note: Trailer will be written by interleaveToOutput
706
- }
707
- /**
708
- * Interleave multiple streams to a single output.
709
- *
710
- * @param streams - Record of packet streams
711
- *
712
- * @param output - Media output destination
713
- *
714
- * @param metadata - Stream metadata for each stream
715
- *
716
- * @param shouldStop - Function to check if pipeline should stop
717
- *
718
- * @internal
719
- */
720
- async function interleaveToOutput(streams, output, metadata, shouldStop) {
721
- // Add all streams to output first
722
- const streamIndices = {};
723
- for (const [name, meta] of Object.entries(metadata)) {
724
- if (meta.encoder) {
725
- streamIndices[name] = output.addStream(meta.encoder);
726
- }
727
- else if (meta.decoder) {
728
- // Stream copy - use decoder's original stream
729
- const originalStream = meta.decoder.getStream();
730
- streamIndices[name] = output.addStream(originalStream);
731
- }
732
- else if (meta.bitStreamFilter) {
733
- // BSF - use BSF's original stream
734
- const originalStream = meta.bitStreamFilter.getStream();
735
- streamIndices[name] = output.addStream(originalStream);
736
- }
737
- else if (meta.mediaInput) {
738
- // Passthrough from MediaInput - use stream name to determine which stream
739
- const stream = name.includes('video') ? meta.mediaInput.video() : meta.mediaInput.audio();
740
- if (!stream) {
741
- throw new Error(`No matching stream found in MediaInput for ${name}`);
742
- }
743
- streamIndices[name] = output.addStream(stream);
744
- }
745
- else {
746
- // This should not happen
747
- throw new Error(`Cannot determine stream configuration for ${name}. This is likely a bug in the pipeline.`);
748
- }
749
- }
750
- const queues = new Map();
751
- const iterators = new Map();
752
- const done = new Set();
753
- // Initialize iterators
754
- for (const [name, stream] of Object.entries(streams)) {
755
- queues.set(name, []);
756
- iterators.set(name, stream[Symbol.asyncIterator]());
757
- }
758
- // Read initial packet from each stream
759
- for (const [name, iterator] of iterators) {
760
- const result = await iterator.next();
761
- if (!result.done && result.value) {
762
- const packet = result.value;
763
- packet._streamName = name;
764
- queues.get(name).push(packet);
765
- }
766
- else {
767
- done.add(name);
768
- }
769
- }
770
- // Interleave packets based on DTS/PTS
771
- while (done.size < Object.keys(streams).length && !shouldStop()) {
772
- // Find packet with smallest DTS/PTS
773
- let minPacket = null;
774
- let minStreamName = null;
775
- let minTime = BigInt(Number.MAX_SAFE_INTEGER);
776
- for (const [name, queue] of queues) {
777
- if (queue.length > 0) {
778
- const packet = queue[0];
779
- const time = packet.dts ?? packet.pts ?? BigInt(0);
780
- if (time < minTime) {
781
- minTime = time;
782
- minPacket = packet;
783
- minStreamName = name;
784
- }
785
- }
786
- }
787
- if (!minPacket || !minStreamName) {
788
- // All queues empty, read more
789
- for (const [name, iterator] of iterators) {
790
- if (!done.has(name)) {
791
- const result = await iterator.next();
792
- if (!result.done && result.value) {
793
- const packet = result.value;
794
- packet._streamName = name;
795
- queues.get(name).push(packet);
796
- }
797
- else {
798
- done.add(name);
799
- }
800
- }
980
+ const packet = __addDisposableResource(env_4, packet_3, false);
981
+ // Check if we should stop
982
+ if (shouldStop()) {
983
+ break;
801
984
  }
802
- continue;
803
- }
804
- // Write the packet with smallest timestamp
805
- const streamIndex = streamIndices[minStreamName];
806
- await output.writePacket(minPacket, streamIndex);
807
- // Free the packet after writing
808
- minPacket.free();
809
- // Remove from queue
810
- queues.get(minStreamName).shift();
811
- // Read next packet from that stream
812
- const iterator = iterators.get(minStreamName);
813
- const result = await iterator.next();
814
- if (!result.done && result.value) {
815
- const packet = result.value;
816
- packet._streamName = minStreamName;
817
- queues.get(minStreamName).push(packet);
818
- }
819
- else {
820
- done.add(minStreamName);
985
+ await output.writePacket(packet, streamIndex);
821
986
  }
822
- }
823
- // If stopped, free all remaining packets in queues
824
- if (shouldStop()) {
825
- for (const [, queue] of queues) {
826
- for (const packet of queue) {
827
- packet.free();
828
- }
987
+ catch (e_4) {
988
+ env_4.error = e_4;
989
+ env_4.hasError = true;
829
990
  }
830
- }
831
- else {
832
- // Write any remaining packets
833
- for (const [name, queue] of queues) {
834
- const streamIndex = streamIndices[name];
835
- for (const packet of queue) {
836
- await output.writePacket(packet, streamIndex);
837
- packet.free(); // Free packet after writing
838
- }
991
+ finally {
992
+ __disposeResources(env_4);
839
993
  }
840
994
  }
841
- await output.close();
995
+ // Note: Output is closed by the caller after all streams finish
842
996
  }
843
997
  // ============================================================================
844
- // Stream Processing Functions
998
+ // Type Guards
845
999
  // ============================================================================
846
1000
  /**
847
- * Decode a stream of packets to frames.
848
- *
849
- * @param packets - Input packets
850
- *
851
- * @param decoder - Decoder instance
852
- *
853
- * @yields {Frame} Decoded frames
854
- *
855
- * @internal
856
- */
857
- async function* decodeStream(packets, decoder) {
858
- // Process all packets
859
- for await (const packet of packets) {
860
- try {
861
- const frame = await decoder.decode(packet);
862
- if (frame) {
863
- yield frame;
864
- }
865
- }
866
- finally {
867
- // Free packet after decoding
868
- packet.free();
869
- }
870
- }
871
- // Flush decoder
872
- if ('flushFrames' in decoder && typeof decoder.flushFrames === 'function') {
873
- // Use generator method if available
874
- for await (const frame of decoder.flushFrames()) {
875
- yield frame;
876
- }
877
- }
878
- else {
879
- // Fallback to manual flush + receive
880
- await decoder.flush();
881
- let packet;
882
- while ((packet = await decoder.receive()) !== null) {
883
- yield packet;
884
- }
885
- }
886
- }
887
- /**
888
- * Encode a stream of frames to packets.
889
- *
890
- * @param frames - Input frames
891
- *
892
- * @param encoder - Encoder instance
893
- *
894
- * @yields {Packet} Encoded packets
895
- *
896
- * @internal
897
- */
898
- async function* encodeStream(frames, encoder) {
899
- // Process all frames
900
- for await (const frame of frames) {
901
- try {
902
- const packet = await encoder.encode(frame);
903
- if (packet) {
904
- yield packet;
905
- }
906
- }
907
- finally {
908
- // Free the input frame after encoding
909
- frame.free();
910
- }
911
- }
912
- // Flush encoder
913
- if ('flushPackets' in encoder && typeof encoder.flushPackets === 'function') {
914
- // Use generator method if available
915
- for await (const packet of encoder.flushPackets()) {
916
- yield packet;
917
- }
918
- }
919
- else {
920
- // Fallback to manual flush + receive
921
- await encoder.flush();
922
- let packet;
923
- while ((packet = await encoder.receive()) !== null) {
924
- yield packet;
925
- }
926
- }
927
- }
928
- /**
929
- * Filter a stream of frames.
930
- *
931
- * @param frames - Input frames
932
- *
933
- * @param filter - Filter instance
934
- *
935
- * @yields {Frame} Filtered frames
936
- *
937
- * @internal
938
- */
939
- async function* filterStream(frames, filter) {
940
- // Process all frames
941
- for await (const frame of frames) {
942
- try {
943
- const filtered = await filter.process(frame);
944
- if (filtered) {
945
- yield filtered;
946
- }
947
- // Check for buffered frames
948
- let buffered;
949
- while ((buffered = await filter.receive()) !== null) {
950
- yield buffered;
951
- }
952
- }
953
- finally {
954
- // Free the input frame after filtering
955
- frame.free();
956
- }
957
- }
958
- // Flush filter
959
- if ('flushFrames' in filter && typeof filter.flushFrames === 'function') {
960
- // Use generator method if available
961
- for await (const frame of filter.flushFrames()) {
962
- yield frame;
963
- }
964
- }
965
- else {
966
- // Fallback to manual flush + receive
967
- await filter.flush();
968
- let frame;
969
- while ((frame = await filter.receive()) !== null) {
970
- yield frame;
971
- }
972
- }
973
- }
974
- /**
975
- * Process packets through a bitstream filter.
976
- *
977
- * @param packets - Input packets
1001
+ * Check if object is named inputs.
978
1002
  *
979
- * @param bsf - Bitstream filter instance
1003
+ * @param obj - Object to check
980
1004
  *
981
- * @yields {Packet} Filtered packets
1005
+ * @returns True if object is NamedInputs
982
1006
  *
983
1007
  * @internal
984
1008
  */
985
- async function* bitStreamFilterStream(packets, bsf) {
986
- // Process all packets through bitstream filter
987
- for await (const packet of packets) {
988
- try {
989
- const filtered = await bsf.process(packet);
990
- for (const outPacket of filtered) {
991
- yield outPacket;
992
- }
993
- }
994
- finally {
995
- // Free the input packet after filtering
996
- packet.free();
997
- }
998
- }
999
- // Flush bitstream filter
1000
- for await (const packet of bsf.flushPackets()) {
1001
- yield packet;
1002
- }
1009
+ function isNamedInputs(obj) {
1010
+ return obj && typeof obj === 'object' && !Array.isArray(obj) && !isAsyncIterable(obj) && !isDemuxer(obj);
1003
1011
  }
1004
- // ============================================================================
1005
- // Type Guards
1006
- // ============================================================================
1007
1012
  /**
1008
- * Check if object is named inputs.
1013
+ * Check if object is named stages.
1009
1014
  *
1010
1015
  * @param obj - Object to check
1011
1016
  *
1012
- * @returns True if object is NamedInputs
1017
+ * @returns True if object is NamedStages
1013
1018
  *
1014
1019
  * @internal
1015
1020
  */
1016
- function isNamedInputs(obj) {
1017
- return obj && typeof obj === 'object' && !Array.isArray(obj) && !isAsyncIterable(obj) && !isMediaInput(obj);
1021
+ function isNamedStages(obj) {
1022
+ if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {
1023
+ return false;
1024
+ }
1025
+ // Check if object has at least one stream name key (video or audio)
1026
+ const keys = Object.keys(obj);
1027
+ return keys.length > 0 && keys.every((key) => key === 'video' || key === 'audio');
1018
1028
  }
1019
1029
  /**
1020
1030
  * Check if object is async iterable.
@@ -1029,15 +1039,15 @@ function isAsyncIterable(obj) {
1029
1039
  return obj && typeof obj[Symbol.asyncIterator] === 'function';
1030
1040
  }
1031
1041
  /**
1032
- * Check if object is MediaInput.
1042
+ * Check if object is Demuxer.
1033
1043
  *
1034
1044
  * @param obj - Object to check
1035
1045
  *
1036
- * @returns True if object is MediaInput
1046
+ * @returns True if object is Demuxer
1037
1047
  *
1038
1048
  * @internal
1039
1049
  */
1040
- function isMediaInput(obj) {
1050
+ function isDemuxer(obj) {
1041
1051
  return obj && typeof obj.packets === 'function' && typeof obj.video === 'function' && typeof obj.audio === 'function';
1042
1052
  }
1043
1053
  /**
@@ -1086,18 +1096,18 @@ function isFilterAPI(obj) {
1086
1096
  * @internal
1087
1097
  */
1088
1098
  function isBitStreamFilterAPI(obj) {
1089
- return obj && typeof obj.process === 'function' && typeof obj.flushPackets === 'function' && typeof obj.reset === 'function';
1099
+ return obj && typeof obj.filter === 'function' && typeof obj.flushPackets === 'function' && typeof obj.reset === 'function';
1090
1100
  }
1091
1101
  /**
1092
- * Check if object is MediaOutput.
1102
+ * Check if object is Muxer.
1093
1103
  *
1094
1104
  * @param obj - Object to check
1095
1105
  *
1096
- * @returns True if object is MediaOutput
1106
+ * @returns True if object is Muxer
1097
1107
  *
1098
1108
  * @internal
1099
1109
  */
1100
- function isMediaOutput(obj) {
1110
+ function isMuxer(obj) {
1101
1111
  return obj && typeof obj.writePacket === 'function' && typeof obj.addStream === 'function';
1102
1112
  }
1103
1113
  /**