node-av 4.0.0 → 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 (104) hide show
  1. package/README.md +23 -0
  2. package/binding.gyp +19 -11
  3. package/dist/api/bitstream-filter.d.ts +13 -12
  4. package/dist/api/bitstream-filter.js +33 -29
  5. package/dist/api/bitstream-filter.js.map +1 -1
  6. package/dist/api/decoder.d.ts +211 -96
  7. package/dist/api/decoder.js +396 -375
  8. package/dist/api/decoder.js.map +1 -1
  9. package/dist/api/demuxer.d.ts +10 -10
  10. package/dist/api/demuxer.js +7 -10
  11. package/dist/api/demuxer.js.map +1 -1
  12. package/dist/api/encoder.d.ts +155 -122
  13. package/dist/api/encoder.js +368 -541
  14. package/dist/api/encoder.js.map +1 -1
  15. package/dist/api/filter-complex.d.ts +769 -0
  16. package/dist/api/filter-complex.js +1596 -0
  17. package/dist/api/filter-complex.js.map +1 -0
  18. package/dist/api/filter-presets.d.ts +68 -0
  19. package/dist/api/filter-presets.js +96 -0
  20. package/dist/api/filter-presets.js.map +1 -1
  21. package/dist/api/filter.d.ts +183 -113
  22. package/dist/api/filter.js +347 -365
  23. package/dist/api/filter.js.map +1 -1
  24. package/dist/api/fmp4-stream.d.ts +2 -2
  25. package/dist/api/fmp4-stream.js.map +1 -1
  26. package/dist/api/index.d.ts +2 -0
  27. package/dist/api/index.js +3 -0
  28. package/dist/api/index.js.map +1 -1
  29. package/dist/api/io-stream.d.ts +3 -3
  30. package/dist/api/io-stream.js.map +1 -1
  31. package/dist/api/muxer.d.ts +10 -10
  32. package/dist/api/muxer.js +6 -6
  33. package/dist/api/muxer.js.map +1 -1
  34. package/dist/api/pipeline.d.ts +2 -2
  35. package/dist/api/pipeline.js +22 -22
  36. package/dist/api/pipeline.js.map +1 -1
  37. package/dist/api/rtp-stream.d.ts +2 -2
  38. package/dist/api/rtp-stream.js.map +1 -1
  39. package/dist/api/types.d.ts +63 -7
  40. package/dist/api/utilities/audio-sample.d.ts +10 -0
  41. package/dist/api/utilities/audio-sample.js +10 -0
  42. package/dist/api/utilities/audio-sample.js.map +1 -1
  43. package/dist/api/utilities/channel-layout.d.ts +1 -0
  44. package/dist/api/utilities/channel-layout.js +1 -0
  45. package/dist/api/utilities/channel-layout.js.map +1 -1
  46. package/dist/api/utilities/image.d.ts +38 -0
  47. package/dist/api/utilities/image.js +38 -0
  48. package/dist/api/utilities/image.js.map +1 -1
  49. package/dist/api/utilities/index.d.ts +1 -0
  50. package/dist/api/utilities/index.js +2 -0
  51. package/dist/api/utilities/index.js.map +1 -1
  52. package/dist/api/utilities/media-type.d.ts +1 -0
  53. package/dist/api/utilities/media-type.js +1 -0
  54. package/dist/api/utilities/media-type.js.map +1 -1
  55. package/dist/api/utilities/pixel-format.d.ts +3 -0
  56. package/dist/api/utilities/pixel-format.js +3 -0
  57. package/dist/api/utilities/pixel-format.js.map +1 -1
  58. package/dist/api/utilities/sample-format.d.ts +5 -0
  59. package/dist/api/utilities/sample-format.js +5 -0
  60. package/dist/api/utilities/sample-format.js.map +1 -1
  61. package/dist/api/utilities/scheduler.d.ts +21 -52
  62. package/dist/api/utilities/scheduler.js +20 -58
  63. package/dist/api/utilities/scheduler.js.map +1 -1
  64. package/dist/api/utilities/streaming.d.ts +32 -1
  65. package/dist/api/utilities/streaming.js +32 -1
  66. package/dist/api/utilities/streaming.js.map +1 -1
  67. package/dist/api/utilities/timestamp.d.ts +14 -0
  68. package/dist/api/utilities/timestamp.js +14 -0
  69. package/dist/api/utilities/timestamp.js.map +1 -1
  70. package/dist/api/utilities/whisper-model.d.ts +310 -0
  71. package/dist/api/utilities/whisper-model.js +528 -0
  72. package/dist/api/utilities/whisper-model.js.map +1 -0
  73. package/dist/api/whisper.d.ts +324 -0
  74. package/dist/api/whisper.js +362 -0
  75. package/dist/api/whisper.js.map +1 -0
  76. package/dist/constants/constants.d.ts +3 -1
  77. package/dist/constants/constants.js +1 -0
  78. package/dist/constants/constants.js.map +1 -1
  79. package/dist/ffmpeg/index.d.ts +3 -3
  80. package/dist/ffmpeg/index.js +3 -3
  81. package/dist/ffmpeg/utils.d.ts +27 -0
  82. package/dist/ffmpeg/utils.js +28 -16
  83. package/dist/ffmpeg/utils.js.map +1 -1
  84. package/dist/lib/binding.d.ts +4 -4
  85. package/dist/lib/binding.js.map +1 -1
  86. package/dist/lib/codec-parameters.d.ts +47 -1
  87. package/dist/lib/codec-parameters.js +55 -0
  88. package/dist/lib/codec-parameters.js.map +1 -1
  89. package/dist/lib/fifo.d.ts +416 -0
  90. package/dist/lib/fifo.js +453 -0
  91. package/dist/lib/fifo.js.map +1 -0
  92. package/dist/lib/frame.d.ts +96 -1
  93. package/dist/lib/frame.js +139 -1
  94. package/dist/lib/frame.js.map +1 -1
  95. package/dist/lib/index.d.ts +1 -0
  96. package/dist/lib/index.js +2 -0
  97. package/dist/lib/index.js.map +1 -1
  98. package/dist/lib/native-types.d.ts +29 -2
  99. package/dist/lib/rational.d.ts +18 -0
  100. package/dist/lib/rational.js +19 -0
  101. package/dist/lib/rational.js.map +1 -1
  102. package/dist/lib/types.d.ts +23 -1
  103. package/install/check.js +2 -2
  104. package/package.json +30 -20
@@ -50,12 +50,13 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
50
50
  var e = new Error(message);
51
51
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
52
  });
53
- import { AV_CODEC_FLAG_COPY_OPAQUE, AV_FRAME_FLAG_CORRUPT, AV_NOPTS_VALUE, AV_ROUND_UP, AVERROR_EAGAIN, AVERROR_EOF, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_VIDEO, INT_MAX, } from '../constants/constants.js';
53
+ import { AV_CODEC_FLAG_COPY_OPAQUE, AV_FRAME_FLAG_CORRUPT, AV_NOPTS_VALUE, AV_ROUND_UP, AVERROR_EAGAIN, AVERROR_EOF, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_VIDEO, EOF, INT_MAX, } from '../constants/constants.js';
54
54
  import { CodecContext } from '../lib/codec-context.js';
55
55
  import { Codec } from '../lib/codec.js';
56
56
  import { Dictionary } from '../lib/dictionary.js';
57
57
  import { FFmpegError } from '../lib/error.js';
58
58
  import { Frame } from '../lib/frame.js';
59
+ import { Packet } from '../lib/packet.js';
59
60
  import { Rational } from '../lib/rational.js';
60
61
  import { avGcd, avInvQ, avMulQ, avRescaleDelta, avRescaleQ, avRescaleQRnd } from '../lib/utilities.js';
61
62
  import { FRAME_THREAD_QUEUE_SIZE, PACKET_THREAD_QUEUE_SIZE } from './constants.js';
@@ -432,28 +433,31 @@ export class Decoder {
432
433
  return this.initialized && !this.isClosed;
433
434
  }
434
435
  /**
435
- * Decode a packet to a frame.
436
+ * Send a packet to the decoder.
436
437
  *
437
- * Sends a packet to the decoder and attempts to receive a decoded frame.
438
- * Handles internal buffering - may return null if more packets needed.
438
+ * Sends a compressed packet to the decoder for decoding.
439
+ * Does not return decoded frames - use {@link receive} to retrieve frames.
440
+ * A single packet can produce zero, one, or multiple frames depending on codec buffering.
439
441
  * Automatically manages decoder state and error recovery.
440
442
  *
441
- * **Note**: This method receives only ONE frame per call.
442
- * A single packet can produce multiple frames (e.g., packed B-frames, codec buffering).
443
- * To receive all frames from a packet, use {@link decodeAll} or {@link frames} instead.
443
+ * **Important**: This method only SENDS the packet to the decoder.
444
+ * You must call {@link receive} separately (potentially multiple times) to get decoded frames.
444
445
  *
445
- * Direct mapping to avcodec_send_packet() and avcodec_receive_frame().
446
+ * Direct mapping to avcodec_send_packet().
446
447
  *
447
- * @param packet - Compressed packet to decode
448
+ * @param packet - Compressed packet to send to decoder
448
449
  *
449
- * @returns Decoded frame or null if more data needed or decoder is closed
450
- *
451
- * @throws {FFmpegError} If decoding fails
450
+ * @throws {FFmpegError} If sending packet fails
452
451
  *
453
452
  * @example
454
453
  * ```typescript
455
- * const frame = await decoder.decode(packet);
456
- * if (frame) {
454
+ * // Send packet and receive frames
455
+ * await decoder.decode(packet);
456
+ *
457
+ * // Receive all available frames
458
+ * while (true) {
459
+ * const frame = await decoder.receive();
460
+ * if (!frame) break;
457
461
  * console.log(`Decoded frame with PTS: ${frame.pts}`);
458
462
  * frame.free();
459
463
  * }
@@ -463,8 +467,12 @@ export class Decoder {
463
467
  * ```typescript
464
468
  * for await (const packet of input.packets()) {
465
469
  * if (packet.streamIndex === decoder.getStream().index) {
466
- * const frame = await decoder.decode(packet);
467
- * if (frame) {
470
+ * // Send packet
471
+ * await decoder.decode(packet);
472
+ *
473
+ * // Receive available frames
474
+ * let frame;
475
+ * while ((frame = await decoder.receive())) {
468
476
  * await processFrame(frame);
469
477
  * frame.free();
470
478
  * }
@@ -473,106 +481,119 @@ export class Decoder {
473
481
  * }
474
482
  * ```
475
483
  *
476
- * @see {@link decodeAll} For multiple frame decoding
484
+ * @see {@link receive} For receiving decoded frames
485
+ * @see {@link decodeAll} For combined send+receive operation
477
486
  * @see {@link frames} For automatic packet iteration
478
487
  * @see {@link flush} For end-of-stream handling
479
488
  * @see {@link decodeSync} For synchronous version
480
489
  */
481
490
  async decode(packet) {
482
491
  if (this.isClosed) {
483
- return null;
492
+ return;
493
+ }
494
+ if (packet.streamIndex !== this.stream.index) {
495
+ return;
484
496
  }
485
497
  // Skip 0-sized packets
486
498
  if (packet.size === 0) {
487
- return null;
499
+ return;
488
500
  }
489
501
  // Send packet to decoder
490
502
  const sendRet = await this.codecContext.sendPacket(packet);
491
- // Handle EAGAIN: decoder buffer is full, need to read frames first
492
- // Unlike FFmpeg CLI which reads ALL frames in a loop, our decode() returns
493
- // only one frame at a time. This means the decoder can still have frames
494
- // from previous packets when we try to send a new packet.
503
+ // EAGAIN during send_packet is a decoder bug (FFmpeg treats this as AVERROR_BUG)
504
+ // We read all decoded frames with receive() until done, so decoder should never be full
495
505
  if (sendRet === AVERROR_EAGAIN) {
496
- // Decoder buffer full, receive a frame first
497
- const frame = await this.receive();
498
- if (frame) {
499
- return frame;
500
- }
501
- // If receive() returned null, this is unexpected - treat as decoder bug
502
- throw new Error('Decoder returned EAGAIN on send but no frame available - decoder bug');
506
+ throw new Error('Decoder returned EAGAIN on send - this is a decoder bug');
503
507
  }
504
- // Handle other send errors (matches FFmpeg's error handling)
508
+ // Handle send errors
505
509
  if (sendRet < 0 && sendRet !== AVERROR_EOF) {
506
510
  if (this.options.exitOnError) {
507
511
  FFmpegError.throwIfError(sendRet, 'Failed to send packet to decoder');
508
512
  }
513
+ // exitOnError=false: Continue to receive loop to drain any buffered frames
509
514
  }
510
- // Try to receive frame
511
- return await this.receive();
512
515
  }
513
516
  /**
514
- * Decode a packet to frame synchronously.
517
+ * Send a packet to the decoder synchronously.
515
518
  * Synchronous version of decode.
516
519
  *
517
- * Send packet to decoder and attempt to receive frame.
518
- * Handles decoder buffering and error conditions.
519
- * May return null if decoder needs more data.
520
+ * Sends a compressed packet to the decoder for decoding.
521
+ * Does not return decoded frames - use {@link receiveSync} to retrieve frames.
522
+ * A single packet can produce zero, one, or multiple frames depending on codec buffering.
523
+ * Automatically manages decoder state and error recovery.
520
524
  *
521
- * **Note**: This method receives only ONE frame per call.
522
- * A single packet can produce multiple frames (e.g., packed B-frames, codec buffering).
523
- * To receive all frames from a packet, use {@link decodeAllSync} or {@link framesSync} instead.
525
+ * **Important**: This method only SENDS the packet to the decoder.
526
+ * You must call {@link receiveSync} separately (potentially multiple times) to get decoded frames.
524
527
  *
525
- * @param packet - Compressed packet to decode
528
+ * Direct mapping to avcodec_send_packet().
526
529
  *
527
- * @returns Decoded frame or null if more data needed or decoder is closed
530
+ * @param packet - Compressed packet to send to decoder
528
531
  *
529
- * @throws {FFmpegError} If decoding fails
532
+ * @throws {FFmpegError} If sending packet fails
530
533
  *
531
534
  * @example
532
535
  * ```typescript
533
- * const frame = decoder.decodeSync(packet);
534
- * if (frame) {
535
- * console.log(`Decoded: ${frame.width}x${frame.height}`);
536
+ * // Send packet and receive frames
537
+ * await decoder.decode(packet);
538
+ *
539
+ * // Receive all available frames
540
+ * while (true) {
541
+ * const frame = await decoder.receive();
542
+ * if (!frame) break;
543
+ * console.log(`Decoded frame with PTS: ${frame.pts}`);
544
+ * frame.free();
545
+ * }
546
+ * ```
547
+ *
548
+ * @example
549
+ * ```typescript
550
+ * for await (const packet of input.packets()) {
551
+ * if (packet.streamIndex === decoder.getStream().index) {
552
+ * // Send packet
553
+ * await decoder.decode(packet);
554
+ *
555
+ * // Receive available frames
556
+ * let frame;
557
+ * while ((frame = await decoder.receive())) {
558
+ * await processFrame(frame);
559
+ * frame.free();
560
+ * }
561
+ * }
562
+ * packet.free();
536
563
  * }
537
564
  * ```
538
565
  *
539
- * @see {@link decodeAllSync} For multiple frame decoding
566
+ * @see {@link receiveSync} For receiving decoded frames
567
+ * @see {@link decodeAllSync} For combined send+receive operation
540
568
  * @see {@link framesSync} For automatic packet iteration
541
569
  * @see {@link flushSync} For end-of-stream handling
542
570
  * @see {@link decode} For async version
543
571
  */
544
572
  decodeSync(packet) {
545
573
  if (this.isClosed) {
546
- return null;
574
+ return;
575
+ }
576
+ if (packet.streamIndex !== this.stream.index) {
577
+ return;
547
578
  }
548
579
  // Skip 0-sized packets
549
580
  if (packet.size === 0) {
550
- return null;
581
+ return;
551
582
  }
552
583
  // Send packet to decoder
553
584
  const sendRet = this.codecContext.sendPacketSync(packet);
554
- // Handle EAGAIN: decoder buffer is full, need to read frames first
555
- // Unlike FFmpeg CLI which reads ALL frames in a loop, our decode() returns
556
- // only one frame at a time. This means the decoder can still have frames
557
- // from previous packets when we try to send a new packet.
585
+ // EAGAIN during send_packet is a decoder bug (FFmpeg treats this as AVERROR_BUG)
586
+ // We read all decoded frames with receive() until done, so decoder should never be full
558
587
  if (sendRet === AVERROR_EAGAIN) {
559
- // Decoder buffer full, receive a frame first
560
- const frame = this.receiveSync();
561
- if (frame) {
562
- return frame;
563
- }
564
- // If receive() returned null, this is unexpected - treat as decoder bug
565
- throw new Error('Decoder returned EAGAIN on send but no frame available - decoder bug');
588
+ throw new Error('Decoder returned EAGAIN on send - this is a decoder bug');
566
589
  }
567
- // Handle other send errors
590
+ // Handle send errors
568
591
  if (sendRet < 0 && sendRet !== AVERROR_EOF) {
569
592
  if (this.options.exitOnError) {
570
593
  FFmpegError.throwIfError(sendRet, 'Failed to send packet to decoder');
571
594
  }
572
- // exitOnError=false: Continue to receive
595
+ // exitOnError=false: Continue to receive loop to drain any buffered frames
573
596
  }
574
- // Try to receive frame
575
- return this.receiveSync();
576
597
  }
577
598
  /**
578
599
  * Decode a packet to frames.
@@ -602,12 +623,10 @@ export class Decoder {
602
623
  * @example
603
624
  * ```typescript
604
625
  * for await (const packet of input.packets()) {
605
- * if (packet.streamIndex === decoder.getStream().index) {
606
- * const frames = await decoder.decodeAll(packet);
607
- * for (const frame of frames) {
608
- * await processFrame(frame);
609
- * frame.free();
610
- * }
626
+ * const frames = await decoder.decodeAll(packet);
627
+ * for (const frame of frames) {
628
+ * await processFrame(frame);
629
+ * frame.free();
611
630
  * }
612
631
  * packet.free();
613
632
  * }
@@ -619,29 +638,14 @@ export class Decoder {
619
638
  * @see {@link decodeAllSync} For synchronous version
620
639
  */
621
640
  async decodeAll(packet) {
622
- if (this.isClosed) {
623
- return [];
624
- }
625
- // Skip 0-sized packets
626
- if (packet.size === 0) {
627
- return [];
628
- }
629
- // Send packet to decoder
630
- const sendRet = await this.codecContext.sendPacket(packet);
631
- // EAGAIN during send_packet is a decoder bug (FFmpeg treats this as AVERROR_BUG)
632
- // We read all decoded frames with receive() until done, so decoder should never be full
633
- if (sendRet === AVERROR_EAGAIN) {
634
- throw new Error('Decoder returned EAGAIN on send - this is a decoder bug');
641
+ const frames = [];
642
+ if (packet) {
643
+ await this.decode(packet);
635
644
  }
636
- // Handle send errors
637
- if (sendRet < 0 && sendRet !== AVERROR_EOF) {
638
- if (this.options.exitOnError) {
639
- FFmpegError.throwIfError(sendRet, 'Failed to send packet to decoder');
640
- }
641
- // exitOnError=false: Continue to receive loop to drain any buffered frames
645
+ else {
646
+ await this.flush();
642
647
  }
643
648
  // Receive all available frames
644
- const frames = [];
645
649
  while (true) {
646
650
  const remaining = await this.receive();
647
651
  if (!remaining)
@@ -671,6 +675,17 @@ export class Decoder {
671
675
  * console.log(`Decoded: ${frame.width}x${frame.height}`);
672
676
  * frame.free();
673
677
  * }
678
+ *
679
+ * @example
680
+ * ```typescript
681
+ * for (const packet of input.packetsSync()) {
682
+ * const frames = await decoder.decodeAllSync(packet);
683
+ * for (const frame of frames) {
684
+ * processFrame(frame);
685
+ * frame.free();
686
+ * }
687
+ * packet.free();
688
+ * }
674
689
  * ```
675
690
  *
676
691
  * @see {@link decodeSync} For single packet decoding
@@ -679,29 +694,14 @@ export class Decoder {
679
694
  * @see {@link decodeAll} For async version
680
695
  */
681
696
  decodeAllSync(packet) {
682
- if (this.isClosed) {
683
- return [];
684
- }
685
- // Skip 0-sized packets
686
- if (packet.size === 0) {
687
- return [];
688
- }
689
- // Send packet to decoder
690
- const sendRet = this.codecContext.sendPacketSync(packet);
691
- // EAGAIN during send_packet is a decoder bug (FFmpeg treats this as AVERROR_BUG)
692
- // We read all decoded frames with receive() until done, so decoder should never be full
693
- if (sendRet === AVERROR_EAGAIN) {
694
- throw new Error('Decoder returned EAGAIN on send - this is a decoder bug');
697
+ const frames = [];
698
+ if (packet) {
699
+ this.decodeSync(packet);
695
700
  }
696
- // Handle send errors
697
- if (sendRet < 0 && sendRet !== AVERROR_EOF) {
698
- if (this.options.exitOnError) {
699
- FFmpegError.throwIfError(sendRet, 'Failed to send packet to decoder');
700
- }
701
- // exitOnError=false: Continue to receive loop to drain any buffered frames
701
+ else {
702
+ this.flushSync();
702
703
  }
703
704
  // Receive all available frames
704
- const frames = [];
705
705
  while (true) {
706
706
  const remaining = this.receiveSync();
707
707
  if (!remaining)
@@ -714,13 +714,17 @@ export class Decoder {
714
714
  * Decode packet stream to frame stream.
715
715
  *
716
716
  * High-level async generator for complete decoding pipeline.
717
- * Automatically filters packets for this stream, manages memory,
718
- * and flushes buffered frames at end.
717
+ * Decoder is only flushed when EOF (null) signal is explicitly received.
719
718
  * Primary interface for stream-based decoding.
720
719
  *
721
- * @param packets - Async iterable of packets
720
+ * **EOF Handling:**
721
+ * - Send null to flush decoder and get remaining buffered frames
722
+ * - Generator yields null after flushing when null is received
723
+ * - No automatic flushing - decoder stays open until EOF or close()
724
+ *
725
+ * @param packets - Async iterable of packets, single packet, or null to flush
722
726
  *
723
- * @yields {Frame} Decoded frames
727
+ * @yields {Frame | null} Decoded frames, followed by null when explicitly flushed
724
728
  *
725
729
  * @throws {Error} If decoder is closed
726
730
  *
@@ -728,10 +732,15 @@ export class Decoder {
728
732
  *
729
733
  * @example
730
734
  * ```typescript
735
+ * // Stream of packets with automatic EOF propagation
731
736
  * await using input = await Demuxer.open('video.mp4');
732
737
  * using decoder = await Decoder.create(input.video());
733
738
  *
734
739
  * for await (const frame of decoder.frames(input.packets())) {
740
+ * if (frame === null) {
741
+ * console.log('Decoding complete');
742
+ * break;
743
+ * }
735
744
  * console.log(`Frame: ${frame.width}x${frame.height}`);
736
745
  * frame.free();
737
746
  * }
@@ -739,26 +748,30 @@ export class Decoder {
739
748
  *
740
749
  * @example
741
750
  * ```typescript
742
- * for await (const frame of decoder.frames(input.packets())) {
743
- * // Process frame
744
- * await filter.process(frame);
745
- *
746
- * // Frame automatically freed
751
+ * // Single packet (no automatic flush)
752
+ * for await (const frame of decoder.frames(singlePacket)) {
753
+ * await encoder.encode(frame);
754
+ * frame.free();
755
+ * }
756
+ * // Decoder still has buffered frames - send null to flush
757
+ * for await (const frame of decoder.frames(null)) {
758
+ * if (frame === null) break;
759
+ * await encoder.encode(frame);
747
760
  * frame.free();
748
761
  * }
749
762
  * ```
750
763
  *
751
764
  * @example
752
765
  * ```typescript
753
- * import { pipeline } from 'node-av/api';
754
- *
755
- * const control = pipeline(
756
- * input,
757
- * decoder,
758
- * encoder,
759
- * output
760
- * );
761
- * await control.completion;
766
+ * // Explicit flush with EOF
767
+ * for await (const frame of decoder.frames(null)) {
768
+ * if (frame === null) {
769
+ * console.log('All buffered frames flushed');
770
+ * break;
771
+ * }
772
+ * console.log('Buffered frame:', frame.pts);
773
+ * frame.free();
774
+ * }
762
775
  * ```
763
776
  *
764
777
  * @see {@link decode} For single packet decoding
@@ -766,54 +779,39 @@ export class Decoder {
766
779
  * @see {@link framesSync} For sync version
767
780
  */
768
781
  async *frames(packets) {
782
+ const self = this;
783
+ const processPacket = async function* (packet) {
784
+ await self.decode(packet);
785
+ while (true) {
786
+ const frame = await self.receive();
787
+ if (!frame)
788
+ break;
789
+ yield frame;
790
+ }
791
+ }.bind(this);
792
+ const finalize = async function* () {
793
+ for await (const remaining of self.flushFrames()) {
794
+ yield remaining;
795
+ }
796
+ yield null;
797
+ }.bind(this);
798
+ if (packets === null) {
799
+ yield* finalize();
800
+ return;
801
+ }
802
+ if (packets instanceof Packet) {
803
+ yield* processPacket(packets);
804
+ return;
805
+ }
769
806
  for await (const packet_1 of packets) {
770
807
  const env_1 = { stack: [], error: void 0, hasError: false };
771
808
  try {
772
809
  const packet = __addDisposableResource(env_1, packet_1, false);
773
- // Handle EOF signal
774
810
  if (packet === null) {
775
- // Flush decoder
776
- await this.flush();
777
- while (true) {
778
- const remaining = await this.receive();
779
- if (!remaining)
780
- break;
781
- yield remaining;
782
- }
783
- // Signal EOF and stop processing
784
- yield null;
811
+ yield* finalize();
785
812
  return;
786
813
  }
787
- // Only process packets for our stream
788
- if (packet.streamIndex === this.stream.index) {
789
- if (this.isClosed) {
790
- break;
791
- }
792
- // Skip 0-sized packets
793
- if (packet.size === 0) {
794
- continue;
795
- }
796
- // Send packet to decoder
797
- const sendRet = await this.codecContext.sendPacket(packet);
798
- // EAGAIN during send_packet is a decoder bug
799
- // We read all decoded frames with receive() until done, so decoder should never be full
800
- if (sendRet === AVERROR_EAGAIN) {
801
- throw new Error('Decoder returned EAGAIN but no frame available');
802
- }
803
- if (sendRet < 0 && sendRet !== AVERROR_EOF) {
804
- if (this.options.exitOnError) {
805
- FFmpegError.throwIfError(sendRet, 'Failed to send packet');
806
- }
807
- }
808
- // Receive ALL available frames immediately
809
- // This ensures frames are yielded ASAP without latency
810
- while (true) {
811
- const frame = await this.receive();
812
- if (!frame)
813
- break; // EAGAIN or EOF
814
- yield frame;
815
- }
816
- }
814
+ yield* processPacket(packet);
817
815
  }
818
816
  catch (e_1) {
819
817
  env_1.error = e_1;
@@ -823,28 +821,23 @@ export class Decoder {
823
821
  __disposeResources(env_1);
824
822
  }
825
823
  }
826
- // Flush decoder after all packets (fallback if no null was sent)
827
- await this.flush();
828
- while (true) {
829
- const remaining = await this.receive();
830
- if (!remaining)
831
- break;
832
- yield remaining;
833
- }
834
- // Signal EOF
835
- yield null;
836
824
  }
837
825
  /**
838
826
  * Decode packet stream to frame stream synchronously.
839
827
  * Synchronous version of frames.
840
828
  *
841
- * High-level sync generator for complete decoding pipeline.
842
- * Automatically filters packets for this stream, manages memory,
843
- * and flushes buffered frames at end.
829
+ * High-level async generator for complete decoding pipeline.
830
+ * Decoder is only flushed when EOF (null) signal is explicitly received.
831
+ * Primary interface for stream-based decoding.
832
+ *
833
+ * **EOF Handling:**
834
+ * - Send null to flush decoder and get remaining buffered frames
835
+ * - Generator yields null after flushing when null is received
836
+ * - No automatic flushing - decoder stays open until EOF or close()
844
837
  *
845
- * @param packets - Iterable of packets
838
+ * @param packets - Iterable of packets, single packet, or null to flush
846
839
  *
847
- * @yields {Frame} Decoded frames
840
+ * @yields {Frame | null} Decoded frames, followed by null when explicitly flushed
848
841
  *
849
842
  * @throws {Error} If decoder is closed
850
843
  *
@@ -852,65 +845,82 @@ export class Decoder {
852
845
  *
853
846
  * @example
854
847
  * ```typescript
855
- * for (const frame of decoder.framesSync(packets)) {
848
+ * // Stream of packets with automatic EOF propagation
849
+ * await using input = await Demuxer.open('video.mp4');
850
+ * using decoder = await Decoder.create(input.video());
851
+ *
852
+ * for (const frame of decoder.framesSync(input.packetsSync())) {
853
+ * if (frame === null) {
854
+ * console.log('Decoding complete');
855
+ * break;
856
+ * }
856
857
  * console.log(`Frame: ${frame.width}x${frame.height}`);
857
- * // Process frame...
858
+ * frame.free();
858
859
  * }
859
860
  * ```
860
861
  *
861
- * @see {@link decodeSync} For single packet decoding
862
- * @see {@link Demuxer.packetsSync} For packet source
863
- * @see {@link frames} For async version
862
+ * @example
863
+ * ```typescript
864
+ * // Single packet (no automatic flush)
865
+ * for (const frame of decoder.framesSync(singlePacket)) {
866
+ * encoder.encodeSync(frame);
867
+ * frame.free();
868
+ * }
869
+ * // Decoder still has buffered frames - send null to flush
870
+ * for (const frame of decoder.framesSync(null)) {
871
+ * if (frame === null) break;
872
+ * encoder.encodeSync(frame);
873
+ * frame.free();
874
+ * }
875
+ * ```
876
+ *
877
+ * @example
878
+ * ```typescript
879
+ * // Explicit flush with EOF
880
+ * for (const frame of decoder.framesSync(null)) {
881
+ * if (frame === null) {
882
+ * console.log('All buffered frames flushed');
883
+ * break;
884
+ * }
885
+ * console.log('Buffered frame:', frame.pts);
886
+ * frame.free();
887
+ * }
888
+ * ```
864
889
  */
865
890
  *framesSync(packets) {
891
+ const self = this;
892
+ const processPacket = function* (packet) {
893
+ self.decodeSync(packet);
894
+ while (true) {
895
+ const frame = self.receiveSync();
896
+ if (!frame)
897
+ break;
898
+ yield frame;
899
+ }
900
+ }.bind(this);
901
+ const finalize = function* () {
902
+ for (const remaining of self.flushFramesSync()) {
903
+ yield remaining;
904
+ }
905
+ yield null;
906
+ }.bind(this);
907
+ if (packets === null) {
908
+ yield* finalize();
909
+ return;
910
+ }
911
+ if (packets instanceof Packet) {
912
+ yield* processPacket(packets);
913
+ return;
914
+ }
866
915
  for (const packet_2 of packets) {
867
916
  const env_2 = { stack: [], error: void 0, hasError: false };
868
917
  try {
869
918
  const packet = __addDisposableResource(env_2, packet_2, false);
870
- // Handle EOF signal
871
919
  if (packet === null) {
872
- // Flush decoder
873
- this.flushSync();
874
- while (true) {
875
- const remaining = this.receiveSync();
876
- if (!remaining)
877
- break;
878
- yield remaining;
879
- }
880
- // Signal EOF and stop processing
881
- yield null;
920
+ yield* finalize();
882
921
  return;
883
922
  }
884
- // Only process packets for our stream
885
- if (packet.streamIndex === this.stream.index) {
886
- if (this.isClosed) {
887
- break;
888
- }
889
- // Skip 0-sized packets
890
- if (packet.size === 0) {
891
- continue;
892
- }
893
- // Send packet to decoder
894
- const sendRet = this.codecContext.sendPacketSync(packet);
895
- // EAGAIN during send_packet is a decoder bug
896
- // We read all decoded frames with receive() until done, so decoder should never be full
897
- if (sendRet === AVERROR_EAGAIN) {
898
- throw new Error('Decoder returned EAGAIN but no frame available');
899
- }
900
- if (sendRet < 0 && sendRet !== AVERROR_EOF) {
901
- if (this.options.exitOnError) {
902
- FFmpegError.throwIfError(sendRet, 'Failed to send packet');
903
- }
904
- }
905
- // Receive ALL available frames immediately
906
- // This ensures frames are yielded ASAP without latency
907
- while (true) {
908
- const frame = this.receiveSync();
909
- if (!frame)
910
- break; // EAGAIN or EOF
911
- yield frame;
912
- }
913
- }
923
+ yield* processPacket(packet);
914
924
  }
915
925
  catch (e_2) {
916
926
  env_2.error = e_2;
@@ -920,16 +930,6 @@ export class Decoder {
920
930
  __disposeResources(env_2);
921
931
  }
922
932
  }
923
- // Flush decoder after all packets (fallback if no null was sent)
924
- this.flushSync();
925
- while (true) {
926
- const remaining = this.receiveSync();
927
- if (!remaining)
928
- break;
929
- yield remaining;
930
- }
931
- // Signal EOF
932
- yield null;
933
933
  }
934
934
  /**
935
935
  * Flush decoder and signal end-of-stream.
@@ -1080,79 +1080,92 @@ export class Decoder {
1080
1080
  * Gets decoded frames from the codec's internal buffer.
1081
1081
  * Handles frame cloning and error checking.
1082
1082
  * Hardware frames include hw_frames_ctx reference.
1083
- * Call repeatedly until null to drain all buffered frames.
1083
+ * Call repeatedly to drain all buffered frames.
1084
+ *
1085
+ * **Return Values:**
1086
+ * - `Frame` - Successfully decoded frame
1087
+ * - `null` - No frame available (AVERROR_EAGAIN), send more packets
1088
+ * - `undefined` - End of stream reached (AVERROR_EOF), decoder flushed
1084
1089
  *
1085
1090
  * Direct mapping to avcodec_receive_frame().
1086
1091
  *
1087
- * @returns Cloned frame or null if no frames available
1092
+ * @returns Decoded frame, null (need more data), or undefined (end of stream)
1088
1093
  *
1089
1094
  * @throws {FFmpegError} If receive fails with error other than AVERROR_EAGAIN or AVERROR_EOF
1090
1095
  *
1091
1096
  * @example
1092
1097
  * ```typescript
1093
1098
  * const frame = await decoder.receive();
1094
- * if (frame) {
1099
+ * if (frame === EOF) {
1100
+ * console.log('Decoder flushed, no more frames');
1101
+ * } else if (frame) {
1095
1102
  * console.log('Got decoded frame');
1096
1103
  * frame.free();
1104
+ * } else {
1105
+ * console.log('Need more packets');
1097
1106
  * }
1098
1107
  * ```
1099
1108
  *
1100
1109
  * @example
1101
1110
  * ```typescript
1102
- * // Drain all buffered frames
1111
+ * // Drain all buffered frames (stop on null or EOF)
1103
1112
  * let frame;
1104
- * while ((frame = await decoder.receive()) !== null) {
1113
+ * while ((frame = await decoder.receive()) && frame !== EOF) {
1105
1114
  * console.log(`Frame PTS: ${frame.pts}`);
1106
1115
  * frame.free();
1107
1116
  * }
1108
1117
  * ```
1109
1118
  *
1110
- * @see {@link decode} For sending packets and receiving frames
1119
+ * @see {@link decode} For sending packets
1111
1120
  * @see {@link flush} For signaling end-of-stream
1112
1121
  * @see {@link receiveSync} For synchronous version
1122
+ * @see {@link EOF} For end-of-stream signal
1113
1123
  */
1114
1124
  async receive() {
1115
- // When exitOnError=false, continue on errors until we get a frame or EAGAIN/EOF
1116
- while (!this.isClosed) {
1117
- // Clear previous frame data
1118
- this.frame.unref();
1119
- const ret = await this.codecContext.receiveFrame(this.frame);
1120
- if (ret === 0) {
1121
- // Set frame time_base to decoder's packet timebase
1122
- this.frame.timeBase = this.codecContext.pktTimebase;
1123
- // Check for corrupt frame
1124
- if (this.frame.decodeErrorFlags || this.frame.hasFlags(AV_FRAME_FLAG_CORRUPT)) {
1125
- if (this.options.exitOnError) {
1126
- throw new Error('Corrupt decoded frame detected');
1127
- }
1128
- // exitOnError=false: skip corrupt frame, continue to next
1129
- continue;
1130
- }
1131
- // Handles PTS assignment, duration estimation, and frame tracking
1132
- if (this.codecContext.codecType === AVMEDIA_TYPE_VIDEO) {
1133
- this.processVideoFrame(this.frame);
1134
- }
1135
- // Handles timestamp extrapolation, sample rate changes, and duration calculation
1136
- if (this.codecContext.codecType === AVMEDIA_TYPE_AUDIO) {
1137
- this.processAudioFrame(this.frame);
1125
+ if (this.isClosed) {
1126
+ return EOF;
1127
+ }
1128
+ // Clear previous frame data
1129
+ this.frame.unref();
1130
+ const ret = await this.codecContext.receiveFrame(this.frame);
1131
+ if (ret === 0) {
1132
+ // Set frame time_base to decoder's packet timebase
1133
+ this.frame.timeBase = this.codecContext.pktTimebase;
1134
+ // Check for corrupt frame
1135
+ if (this.frame.decodeErrorFlags || this.frame.hasFlags(AV_FRAME_FLAG_CORRUPT)) {
1136
+ if (this.options.exitOnError) {
1137
+ throw new Error('Corrupt decoded frame detected');
1138
1138
  }
1139
- // Got a frame, clone it for the user
1140
- return this.frame.clone();
1141
- }
1142
- else if (ret === AVERROR_EAGAIN || ret === AVERROR_EOF) {
1143
- // Need more data or end of stream
1139
+ // exitOnError=false: skip corrupt frame
1144
1140
  return null;
1145
1141
  }
1146
- else {
1147
- // Error during receive
1148
- if (this.options.exitOnError) {
1149
- FFmpegError.throwIfError(ret, 'Failed to receive frame');
1150
- }
1151
- // exitOnError=false: continue to next frame
1152
- continue;
1142
+ // Handles PTS assignment, duration estimation, and frame tracking
1143
+ if (this.codecContext.codecType === AVMEDIA_TYPE_VIDEO) {
1144
+ this.processVideoFrame(this.frame);
1153
1145
  }
1146
+ // Handles timestamp extrapolation, sample rate changes, and duration calculation
1147
+ if (this.codecContext.codecType === AVMEDIA_TYPE_AUDIO) {
1148
+ this.processAudioFrame(this.frame);
1149
+ }
1150
+ // Got a frame, clone it for the user
1151
+ return this.frame.clone();
1152
+ }
1153
+ else if (ret === AVERROR_EAGAIN) {
1154
+ // Need more data
1155
+ return null;
1156
+ }
1157
+ else if (ret === AVERROR_EOF) {
1158
+ // End of stream
1159
+ return EOF;
1160
+ }
1161
+ else {
1162
+ // Error during receive
1163
+ if (this.options.exitOnError) {
1164
+ FFmpegError.throwIfError(ret, 'Failed to receive frame');
1165
+ }
1166
+ // exitOnError=false: return null, caller can retry if desired
1167
+ return null;
1154
1168
  }
1155
- return null;
1156
1169
  }
1157
1170
  /**
1158
1171
  * Receive frame from decoder synchronously.
@@ -1161,81 +1174,94 @@ export class Decoder {
1161
1174
  * Gets decoded frames from the codec's internal buffer.
1162
1175
  * Handles frame cloning and error checking.
1163
1176
  * Hardware frames include hw_frames_ctx reference.
1164
- * Call repeatedly until null to drain all buffered frames.
1177
+ * Call repeatedly to drain all buffered frames.
1178
+ *
1179
+ * **Return Values:**
1180
+ * - `Frame` - Successfully decoded frame
1181
+ * - `null` - No frame available (AVERROR_EAGAIN), send more packets
1182
+ * - `undefined` - End of stream reached (AVERROR_EOF), decoder flushed
1165
1183
  *
1166
1184
  * Direct mapping to avcodec_receive_frame().
1167
1185
  *
1168
- * @returns Cloned frame or null if no frames available
1186
+ * @returns Decoded frame, null (need more data), or undefined (end of stream)
1169
1187
  *
1170
1188
  * @throws {FFmpegError} If receive fails with error other than AVERROR_EAGAIN or AVERROR_EOF
1171
1189
  *
1172
1190
  * @example
1173
1191
  * ```typescript
1174
1192
  * const frame = decoder.receiveSync();
1175
- * if (frame) {
1193
+ * if (frame === EOF) {
1194
+ * console.log('Decoder flushed, no more frames');
1195
+ * } else if (frame) {
1176
1196
  * console.log('Got decoded frame');
1177
1197
  * frame.free();
1198
+ * } else {
1199
+ * console.log('Need more packets');
1178
1200
  * }
1179
1201
  * ```
1180
1202
  *
1181
1203
  * @example
1182
1204
  * ```typescript
1183
- * // Drain all buffered frames
1205
+ * // Drain all buffered frames (stop on null or EOF)
1184
1206
  * let frame;
1185
- * while ((frame = decoder.receiveSync()) !== null) {
1207
+ * while ((frame = decoder.receiveSync()) && frame !== EOF) {
1186
1208
  * console.log(`Frame PTS: ${frame.pts}`);
1187
1209
  * frame.free();
1188
1210
  * }
1189
1211
  * ```
1190
1212
  *
1191
- * @see {@link decodeSync} For sending packets and receiving frames
1213
+ * @see {@link decodeSync} For sending packets
1192
1214
  * @see {@link flushSync} For signaling end-of-stream
1193
1215
  * @see {@link receive} For async version
1216
+ * @see {@link EOF} For end-of-stream signal
1194
1217
  */
1195
1218
  receiveSync() {
1196
- // When exitOnError=false, continue on errors until we get a frame or EAGAIN/EOF
1197
- while (!this.isClosed) {
1198
- // Clear previous frame data
1199
- this.frame.unref();
1200
- const ret = this.codecContext.receiveFrameSync(this.frame);
1201
- if (ret === 0) {
1202
- // Set frame time_base to decoder's packet timebase
1203
- this.frame.timeBase = this.codecContext.pktTimebase;
1204
- // Check for corrupt frame
1205
- if (this.frame.decodeErrorFlags || this.frame.hasFlags(AV_FRAME_FLAG_CORRUPT)) {
1206
- if (this.options.exitOnError) {
1207
- throw new Error('Corrupt decoded frame detected');
1208
- }
1209
- // exitOnError=false: skip corrupt frame, continue to next
1210
- continue;
1211
- }
1212
- // Process video frame
1213
- // Handles PTS assignment, duration estimation, and frame tracking
1214
- if (this.codecContext.codecType === AVMEDIA_TYPE_VIDEO) {
1215
- this.processVideoFrame(this.frame);
1216
- }
1217
- // Process audio frame
1218
- // Handles timestamp extrapolation, sample rate changes, and duration calculation
1219
- if (this.codecContext.codecType === AVMEDIA_TYPE_AUDIO) {
1220
- this.processAudioFrame(this.frame);
1219
+ if (this.isClosed) {
1220
+ return EOF;
1221
+ }
1222
+ // Clear previous frame data
1223
+ this.frame.unref();
1224
+ const ret = this.codecContext.receiveFrameSync(this.frame);
1225
+ if (ret === 0) {
1226
+ // Set frame time_base to decoder's packet timebase
1227
+ this.frame.timeBase = this.codecContext.pktTimebase;
1228
+ // Check for corrupt frame
1229
+ if (this.frame.decodeErrorFlags || this.frame.hasFlags(AV_FRAME_FLAG_CORRUPT)) {
1230
+ if (this.options.exitOnError) {
1231
+ throw new Error('Corrupt decoded frame detected');
1221
1232
  }
1222
- // Got a frame, clone it for the user
1223
- return this.frame.clone();
1224
- }
1225
- else if (ret === AVERROR_EAGAIN || ret === AVERROR_EOF) {
1226
- // Need more data or end of stream
1233
+ // exitOnError=false: skip corrupt frame
1227
1234
  return null;
1228
1235
  }
1229
- else {
1230
- // Error during receive
1231
- if (this.options.exitOnError) {
1232
- FFmpegError.throwIfError(ret, 'Failed to receive frame');
1233
- }
1234
- // exitOnError=false: continue to next frame
1235
- continue;
1236
+ // Process video frame
1237
+ // Handles PTS assignment, duration estimation, and frame tracking
1238
+ if (this.codecContext.codecType === AVMEDIA_TYPE_VIDEO) {
1239
+ this.processVideoFrame(this.frame);
1236
1240
  }
1241
+ // Process audio frame
1242
+ // Handles timestamp extrapolation, sample rate changes, and duration calculation
1243
+ if (this.codecContext.codecType === AVMEDIA_TYPE_AUDIO) {
1244
+ this.processAudioFrame(this.frame);
1245
+ }
1246
+ // Got a frame, clone it for the user
1247
+ return this.frame.clone();
1248
+ }
1249
+ else if (ret === AVERROR_EAGAIN) {
1250
+ // Need more data
1251
+ return null;
1252
+ }
1253
+ else if (ret === AVERROR_EOF) {
1254
+ // End of stream
1255
+ return EOF;
1256
+ }
1257
+ else {
1258
+ // Error during receive
1259
+ if (this.options.exitOnError) {
1260
+ FFmpegError.throwIfError(ret, 'Failed to receive frame');
1261
+ }
1262
+ // exitOnError=false: return null, caller can retry if desired
1263
+ return null;
1237
1264
  }
1238
- return null;
1239
1265
  }
1240
1266
  pipeTo(target) {
1241
1267
  const t = target;
@@ -1352,18 +1378,7 @@ export class Decoder {
1352
1378
  if (packet.size === 0) {
1353
1379
  continue;
1354
1380
  }
1355
- // Send packet to decoder
1356
- const sendRet = await this.codecContext.sendPacket(packet);
1357
- // EAGAIN during send_packet is a decoder bug
1358
- // We read all decoded frames with receive() until done, so decoder should never be full
1359
- if (sendRet === AVERROR_EAGAIN) {
1360
- throw new Error('Decoder returned EAGAIN but no frame available');
1361
- }
1362
- if (sendRet < 0 && sendRet !== AVERROR_EOF) {
1363
- if (this.options.exitOnError) {
1364
- FFmpegError.throwIfError(sendRet, 'Failed to send packet');
1365
- }
1366
- }
1381
+ await this.decode(packet);
1367
1382
  // Receive ALL available frames immediately
1368
1383
  // This ensures frames are yielded ASAP without latency
1369
1384
  while (!this.outputQueue.isClosed) {
@@ -1399,14 +1414,54 @@ export class Decoder {
1399
1414
  }
1400
1415
  }
1401
1416
  /**
1402
- * Send packet to input queue.
1417
+ * Send packet to input queue or flush the pipeline.
1403
1418
  *
1404
- * @param packet - Packet to send
1419
+ * When packet is provided, queues it for processing.
1420
+ * When null is provided, triggers flush sequence:
1421
+ * - Closes input queue
1422
+ * - Waits for worker completion
1423
+ * - Flushes decoder and sends remaining frames to output queue
1424
+ * - Closes output queue
1425
+ * - Waits for pipeTo task completion
1426
+ * - Propagates flush to next component (if any)
1427
+ *
1428
+ * Used by scheduler system for pipeline control.
1429
+ *
1430
+ * @param packet - Packet to send, or null to flush
1405
1431
  *
1406
1432
  * @internal
1407
1433
  */
1408
1434
  async sendToQueue(packet) {
1409
- await this.inputQueue.send(packet);
1435
+ if (packet) {
1436
+ await this.inputQueue.send(packet);
1437
+ }
1438
+ else {
1439
+ // Close input queue to signal end of stream to worker
1440
+ this.inputQueue.close();
1441
+ // Wait for worker to finish processing all packets (if exists)
1442
+ if (this.workerPromise) {
1443
+ await this.workerPromise;
1444
+ }
1445
+ // Flush decoder at end
1446
+ await this.flush();
1447
+ // Send all flushed frames to output queue
1448
+ while (true) {
1449
+ const frame = await this.receive();
1450
+ if (!frame)
1451
+ break;
1452
+ await this.outputQueue.send(frame);
1453
+ }
1454
+ // Close output queue to signal end of stream to pipeTo() task
1455
+ this.outputQueue.close();
1456
+ // Wait for pipeTo() task to finish processing all frames (if exists)
1457
+ if (this.pipeToPromise) {
1458
+ await this.pipeToPromise;
1459
+ }
1460
+ // Then propagate flush to next component
1461
+ if (this.nextComponent) {
1462
+ await this.nextComponent.sendToQueue(null);
1463
+ }
1464
+ }
1410
1465
  }
1411
1466
  /**
1412
1467
  * Receive frame from output queue.
@@ -1418,40 +1473,6 @@ export class Decoder {
1418
1473
  async receiveFromQueue() {
1419
1474
  return await this.outputQueue.receive();
1420
1475
  }
1421
- /**
1422
- * Flush the entire filter pipeline.
1423
- *
1424
- * Propagates flush through worker, output queue, and next component.
1425
- *
1426
- * @internal
1427
- */
1428
- async flushPipeline() {
1429
- // Close input queue to signal end of stream to worker
1430
- this.inputQueue.close();
1431
- // Wait for worker to finish processing all packets (if exists)
1432
- if (this.workerPromise) {
1433
- await this.workerPromise;
1434
- }
1435
- // Flush decoder at end
1436
- await this.flush();
1437
- // Send all flushed frames to output queue
1438
- while (true) {
1439
- const frame = await this.receive();
1440
- if (!frame)
1441
- break;
1442
- await this.outputQueue.send(frame);
1443
- }
1444
- // Close output queue to signal end of stream to pipeTo() task
1445
- this.outputQueue.close();
1446
- // Wait for pipeTo() task to finish processing all frames (if exists)
1447
- if (this.pipeToPromise) {
1448
- await this.pipeToPromise;
1449
- }
1450
- // Then propagate flush to next component
1451
- if (this.nextComponent) {
1452
- await this.nextComponent.flushPipeline();
1453
- }
1454
- }
1455
1476
  /**
1456
1477
  * Estimate video frame duration.
1457
1478
  *