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,11 +50,12 @@ 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_CAP_ENCODER_REORDERED_OPAQUE, AV_CODEC_CAP_PARAM_CHANGE, AV_CODEC_FLAG_COPY_OPAQUE, AV_CODEC_FLAG_FRAME_DURATION, AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX, AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX, AV_PIX_FMT_NONE, AV_PKT_FLAG_TRUSTED, AVCHROMA_LOC_UNSPECIFIED, AVERROR_EAGAIN, AVERROR_EOF, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_VIDEO, } from '../constants/constants.js';
53
+ import { AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE, AV_CODEC_CAP_PARAM_CHANGE, AV_CODEC_FLAG_COPY_OPAQUE, AV_CODEC_FLAG_FRAME_DURATION, AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX, AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX, AV_PICTURE_TYPE_NONE, AV_PIX_FMT_NONE, AV_PKT_FLAG_TRUSTED, AVCHROMA_LOC_UNSPECIFIED, AVERROR_EAGAIN, AVERROR_EOF, AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_VIDEO, EOF, } 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
+ import { Frame } from '../lib/frame.js';
58
59
  import { Packet } from '../lib/packet.js';
59
60
  import { Rational } from '../lib/rational.js';
60
61
  import { avRescaleQ } from '../lib/utilities.js';
@@ -542,28 +543,31 @@ export class Encoder {
542
543
  return this.initialized && !this.isClosed;
543
544
  }
544
545
  /**
545
- * Encode a frame to a packet.
546
+ * Send a frame to the encoder.
546
547
  *
547
- * Sends a frame to the encoder and attempts to receive an encoded packet.
548
+ * Sends a raw frame to the encoder for encoding.
549
+ * Does not return encoded packets - use {@link receive} to retrieve packets.
548
550
  * On first frame, automatically initializes encoder with frame properties.
549
- * Handles internal buffering - may return null if more frames needed.
551
+ * A single frame can produce zero, one, or multiple packets depending on codec buffering.
550
552
  *
551
- * **Note**: This method receives only ONE packet per call.
552
- * A single frame can produce multiple packets (e.g., B-frames, codec buffering).
553
- * To receive all packets from a frame, use {@link encodeAll} or {@link packets} instead.
553
+ * **Important**: This method only SENDS the frame to the encoder.
554
+ * You must call {@link receive} separately (potentially multiple times) to get encoded packets.
554
555
  *
555
- * Direct mapping to avcodec_send_frame() and avcodec_receive_packet().
556
- *
557
- * @param frame - Raw frame to encode (or null to flush)
556
+ * Direct mapping to avcodec_send_frame().
558
557
  *
559
- * @returns Encoded packet, null if more data needed, or null if encoder is closed
558
+ * @param frame - Raw frame to send to encoder
560
559
  *
561
- * @throws {FFmpegError} If encoding fails
560
+ * @throws {FFmpegError} If sending frame fails
562
561
  *
563
562
  * @example
564
563
  * ```typescript
565
- * const packet = await encoder.encode(frame);
566
- * if (packet) {
564
+ * // Send frame and receive packets
565
+ * await encoder.encode(frame);
566
+ *
567
+ * // Receive all available packets
568
+ * while (true) {
569
+ * const packet = await encoder.receive();
570
+ * if (!packet) break;
567
571
  * console.log(`Encoded packet with PTS: ${packet.pts}`);
568
572
  * await output.writePacket(packet);
569
573
  * packet.free();
@@ -572,10 +576,13 @@ export class Encoder {
572
576
  *
573
577
  * @example
574
578
  * ```typescript
575
- * // Encode loop
576
579
  * for await (const frame of decoder.frames(input.packets())) {
577
- * const packet = await encoder.encode(frame);
578
- * if (packet) {
580
+ * // Send frame
581
+ * await encoder.encode(frame);
582
+ *
583
+ * // Receive available packets
584
+ * let packet;
585
+ * while ((packet = await encoder.receive())) {
579
586
  * await output.writePacket(packet);
580
587
  * packet.free();
581
588
  * }
@@ -583,131 +590,98 @@ export class Encoder {
583
590
  * }
584
591
  * ```
585
592
  *
586
- * @see {@link encodeAll} For multiple packet encoding
593
+ * @see {@link receive} For receiving encoded packets
594
+ * @see {@link encodeAll} For combined send+receive operation
587
595
  * @see {@link packets} For automatic frame iteration
588
596
  * @see {@link flush} For end-of-stream handling
589
597
  * @see {@link encodeSync} For synchronous version
590
598
  */
591
599
  async encode(frame) {
592
600
  if (this.isClosed) {
593
- return null;
601
+ return;
594
602
  }
595
603
  // Open encoder if not already done
596
- if (!this.initialized) {
597
- if (!frame) {
598
- return null;
599
- }
600
- this.initializePromise ??= this.initialize(frame);
601
- }
604
+ this.initializePromise ??= this.initialize(frame);
602
605
  await this.initializePromise;
603
606
  // Prepare frame for encoding (set quality, validate channel count)
604
- if (frame) {
605
- this.prepareFrameForEncoding(frame);
606
- }
607
- // Send frame to encoder
608
- const sendRet = await this.codecContext.sendFrame(frame);
609
- // Handle EAGAIN: encoder buffer is full, need to read packets first
610
- // Unlike FFmpeg CLI which reads ALL packets in a loop, our encode() returns
611
- // only one packet at a time. This means the encoder can still have packets
612
- // from previous frames when we try to send a new frame.
613
- if (sendRet === AVERROR_EAGAIN) {
614
- // Encoder is full, receive a packet first
615
- const packet = await this.receive();
616
- if (packet) {
617
- return packet;
607
+ this.prepareFrameForEncoding(frame);
608
+ const encode = async (newFrame) => {
609
+ const sendRet = await this.codecContext.sendFrame(newFrame);
610
+ if (sendRet < 0 && sendRet !== AVERROR_EOF) {
611
+ FFmpegError.throwIfError(sendRet, 'Failed to send frame to encoder');
612
+ return;
618
613
  }
619
- // If receive() returned null, this is unexpected - treat as error
620
- throw new Error('Encoder returned EAGAIN but no packet available');
614
+ };
615
+ if (this.audioFrameBuffer) {
616
+ // Push frame into buffer - actual sending happens in receive()
617
+ await this.audioFrameBuffer.push(frame);
621
618
  }
622
- if (sendRet < 0 && sendRet !== AVERROR_EOF) {
623
- FFmpegError.throwIfError(sendRet, 'Failed to send frame');
619
+ else {
620
+ await encode(frame);
624
621
  }
625
- // Try to receive packet
626
- return await this.receive();
627
622
  }
628
623
  /**
629
- * Encode a frame to a packet synchronously.
624
+ * Send a frame to the encoder synchronously.
630
625
  * Synchronous version of encode.
631
626
  *
632
- * Sends a frame to the encoder and attempts to receive an encoded packet.
627
+ * Sends a raw frame to the encoder for encoding.
628
+ * Does not return encoded packets - use {@link receiveSync} to retrieve packets.
633
629
  * On first frame, automatically initializes encoder with frame properties.
634
- * Handles internal buffering - may return null if more frames needed.
635
- *
636
- * **Note**: This method receives only ONE packet per call.
637
- * A single frame can produce multiple packets (e.g., B-frames, codec buffering).
638
- * To receive all packets from a frame, use {@link encodeAllSync} or {@link packetsSync} instead.
630
+ * A single frame can produce zero, one, or multiple packets depending on codec buffering.
639
631
  *
640
- * Direct mapping to avcodec_send_frame() and avcodec_receive_packet().
632
+ * **Important**: This method only SENDS the frame to the encoder.
633
+ * You must call {@link receiveSync} separately (potentially multiple times) to get encoded packets.
641
634
  *
642
- * @param frame - Raw frame to encode (or null to flush)
635
+ * Direct mapping to avcodec_send_frame().
643
636
  *
644
- * @returns Encoded packet, null if more data needed, or null if encoder is closed
637
+ * @param frame - Raw frame to send to encoder
645
638
  *
646
- * @throws {FFmpegError} If encoding fails
639
+ * @throws {FFmpegError} If sending frame fails
647
640
  *
648
641
  * @example
649
642
  * ```typescript
650
- * const packet = encoder.encodeSync(frame);
651
- * if (packet) {
643
+ * // Send frame and receive packets
644
+ * encoder.encodeSync(frame);
645
+ *
646
+ * // Receive all available packets
647
+ * let packet;
648
+ * while ((packet = encoder.receiveSync())) {
652
649
  * console.log(`Encoded packet with PTS: ${packet.pts}`);
653
650
  * output.writePacketSync(packet);
654
651
  * packet.free();
655
652
  * }
656
653
  * ```
657
654
  *
658
- * @example
659
- * ```typescript
660
- * // Encode loop
661
- * for (const frame of decoder.framesSync(packets)) {
662
- * const packet = encoder.encodeSync(frame);
663
- * if (packet) {
664
- * output.writePacketSync(packet);
665
- * packet.free();
666
- * }
667
- * frame.free();
668
- * }
669
- * ```
670
- *
671
- * @see {@link encodeAllSync} For multiple packet encoding
655
+ * @see {@link receiveSync} For receiving encoded packets
656
+ * @see {@link encodeAllSync} For combined send+receive operation
672
657
  * @see {@link packetsSync} For automatic frame iteration
673
658
  * @see {@link flushSync} For end-of-stream handling
674
659
  * @see {@link encode} For async version
675
660
  */
676
661
  encodeSync(frame) {
677
662
  if (this.isClosed) {
678
- return null;
663
+ return;
679
664
  }
680
665
  // Open encoder if not already done
681
666
  if (!this.initialized) {
682
- if (!frame) {
683
- return null;
684
- }
685
667
  this.initializeSync(frame);
686
668
  }
687
669
  // Prepare frame for encoding (set quality, validate channel count)
688
- if (frame) {
689
- this.prepareFrameForEncoding(frame);
690
- }
691
- // Send frame to encoder
692
- const sendRet = this.codecContext.sendFrameSync(frame);
693
- // Handle EAGAIN: encoder buffer is full, need to read packets first
694
- // Unlike FFmpeg CLI which reads ALL packets in a loop, our encode() returns
695
- // only one packet at a time. This means the encoder can still have packets
696
- // from previous frames when we try to send a new frame.
697
- if (sendRet === AVERROR_EAGAIN) {
698
- // Encoder is full, receive a packet first
699
- const packet = this.receiveSync();
700
- if (packet) {
701
- return packet;
670
+ this.prepareFrameForEncoding(frame);
671
+ const encode = (newFrame) => {
672
+ const sendRet = this.codecContext.sendFrameSync(newFrame);
673
+ if (sendRet < 0 && sendRet !== AVERROR_EOF) {
674
+ FFmpegError.throwIfError(sendRet, 'Failed to send frame to encoder');
675
+ return;
702
676
  }
703
- // If receive() returned null, this is unexpected - treat as error
704
- throw new Error('Encoder returned EAGAIN but no packet available');
677
+ };
678
+ if (this.audioFrameBuffer) {
679
+ // Push frame into buffer - actual sending happens in receiveSync()
680
+ this.audioFrameBuffer.pushSync(frame);
705
681
  }
706
- if (sendRet < 0 && sendRet !== AVERROR_EOF) {
707
- FFmpegError.throwIfError(sendRet, 'Failed to send frame');
682
+ else {
683
+ encode(frame);
708
684
  }
709
- // Try to receive packet
710
- return this.receiveSync();
711
685
  }
712
686
  /**
713
687
  * Encode a frame to packets.
@@ -754,68 +728,19 @@ export class Encoder {
754
728
  * @see {@link encodeAllSync} For synchronous version
755
729
  */
756
730
  async encodeAll(frame) {
757
- if (this.isClosed) {
758
- return [];
759
- }
760
- // Open encoder if not already done
761
- if (!this.initialized) {
762
- if (!frame) {
763
- return [];
764
- }
765
- this.initializePromise ??= this.initialize(frame);
766
- }
767
- await this.initializePromise;
768
- // Prepare frame for encoding (set quality, validate channel count)
769
731
  if (frame) {
770
- this.prepareFrameForEncoding(frame);
771
- }
772
- // If audio encoder with fixed frame size, use AudioFrameBuffer
773
- if (this.audioFrameBuffer && frame) {
774
- // Push frame into buffer
775
- await this.audioFrameBuffer.push(frame);
776
- // Pull and encode all available fixed-size frames
777
- const packets = [];
778
- let _bufferedFrame;
779
- while (!this.isClosed && (_bufferedFrame = await this.audioFrameBuffer.pull()) !== null) {
780
- const env_1 = { stack: [], error: void 0, hasError: false };
781
- try {
782
- const bufferedFrame = __addDisposableResource(env_1, _bufferedFrame, false);
783
- // Send buffered frame to encoder
784
- const sendRet = await this.codecContext.sendFrame(bufferedFrame);
785
- if (sendRet < 0 && sendRet !== AVERROR_EOF && sendRet !== AVERROR_EAGAIN) {
786
- FFmpegError.throwIfError(sendRet, 'Failed to send frame');
787
- }
788
- // Receive packets
789
- while (true) {
790
- const packet = await this.receive();
791
- if (!packet)
792
- break;
793
- packets.push(packet);
794
- }
795
- }
796
- catch (e_1) {
797
- env_1.error = e_1;
798
- env_1.hasError = true;
799
- }
800
- finally {
801
- __disposeResources(env_1);
802
- }
803
- }
804
- return packets;
732
+ await this.encode(frame);
805
733
  }
806
- // Send frame first, error immediately if send fails
807
- const sendRet = await this.codecContext.sendFrame(frame);
808
- if (sendRet < 0 && sendRet !== AVERROR_EOF) {
809
- FFmpegError.throwIfError(sendRet, 'Failed to send frame to encoder');
810
- return [];
734
+ else {
735
+ await this.flush();
811
736
  }
812
737
  // Receive all available packets
813
738
  const packets = [];
814
739
  while (true) {
815
740
  const packet = await this.receive();
816
741
  if (!packet)
817
- break;
818
- packets.push(packet);
742
+ break; // Stop on EAGAIN or EOF
743
+ packets.push(packet); // Only push actual packets
819
744
  }
820
745
  return packets;
821
746
  }
@@ -865,67 +790,19 @@ export class Encoder {
865
790
  * @see {@link encodeAll} For async version
866
791
  */
867
792
  encodeAllSync(frame) {
868
- if (this.isClosed) {
869
- return [];
870
- }
871
- // Open encoder if not already done
872
- if (!this.initialized) {
873
- if (!frame) {
874
- return [];
875
- }
876
- this.initializeSync(frame);
877
- }
878
- // Prepare frame for encoding (set quality, validate channel count)
879
793
  if (frame) {
880
- this.prepareFrameForEncoding(frame);
794
+ this.encodeSync(frame);
881
795
  }
882
- // If audio encoder with fixed frame size, use AudioFrameBuffer
883
- if (this.audioFrameBuffer && frame) {
884
- // Push frame into buffer
885
- this.audioFrameBuffer.pushSync(frame);
886
- // Pull and encode all available fixed-size frames
887
- const packets = [];
888
- let _bufferedFrame;
889
- while (!this.isClosed && (_bufferedFrame = this.audioFrameBuffer.pullSync()) !== null) {
890
- const env_2 = { stack: [], error: void 0, hasError: false };
891
- try {
892
- const bufferedFrame = __addDisposableResource(env_2, _bufferedFrame, false);
893
- // Send buffered frame to encoder
894
- const sendRet = this.codecContext.sendFrameSync(bufferedFrame);
895
- if (sendRet < 0 && sendRet !== AVERROR_EOF && sendRet !== AVERROR_EAGAIN) {
896
- FFmpegError.throwIfError(sendRet, 'Failed to send frame');
897
- }
898
- // Receive packets
899
- while (true) {
900
- const packet = this.receiveSync();
901
- if (!packet)
902
- break;
903
- packets.push(packet);
904
- }
905
- }
906
- catch (e_2) {
907
- env_2.error = e_2;
908
- env_2.hasError = true;
909
- }
910
- finally {
911
- __disposeResources(env_2);
912
- }
913
- }
914
- return packets;
915
- }
916
- // Send frame first, error immediately if send fails
917
- const sendRet = this.codecContext.sendFrameSync(frame);
918
- if (sendRet < 0 && sendRet !== AVERROR_EOF) {
919
- FFmpegError.throwIfError(sendRet, 'Failed to send frame to encoder');
920
- return [];
796
+ else {
797
+ this.flushSync();
921
798
  }
922
799
  // Receive all available packets
923
800
  const packets = [];
924
801
  while (true) {
925
802
  const packet = this.receiveSync();
926
803
  if (!packet)
927
- break;
928
- packets.push(packet);
804
+ break; // Stop on EAGAIN or EOF
805
+ packets.push(packet); // Only push actual packets
929
806
  }
930
807
  return packets;
931
808
  }
@@ -933,20 +810,28 @@ export class Encoder {
933
810
  * Encode frame stream to packet stream.
934
811
  *
935
812
  * High-level async generator for complete encoding pipeline.
936
- * Automatically manages frame memory, encoder state,
937
- * and flushes buffered packets at end.
813
+ * Encoder is only flushed when EOF (null) signal is explicitly received.
938
814
  * Primary interface for stream-based encoding.
939
815
  *
940
- * @param frames - Async iterable of frames (freed automatically)
816
+ * **EOF Handling:**
817
+ * - Send null to flush encoder and get remaining buffered packets
818
+ * - Generator yields null after flushing when null is received
819
+ * - No automatic flushing - encoder stays open until EOF or close()
820
+ *
821
+ * @param frames - Async iterable of frames, single frame, or null to flush
941
822
  *
942
- * @yields {Packet} Encoded packets (caller must free)
823
+ * @yields {Packet | null} Encoded packets, followed by null when explicitly flushed
943
824
  *
944
825
  * @throws {FFmpegError} If encoding fails
945
826
  *
946
827
  * @example
947
828
  * ```typescript
948
- * // Basic encoding pipeline
829
+ * // Stream of frames with automatic EOF propagation
949
830
  * for await (const packet of encoder.packets(decoder.frames(input.packets()))) {
831
+ * if (packet === null) {
832
+ * console.log('Encoder flushed');
833
+ * break;
834
+ * }
950
835
  * await output.writePacket(packet);
951
836
  * packet.free(); // Must free output packets
952
837
  * }
@@ -954,35 +839,26 @@ export class Encoder {
954
839
  *
955
840
  * @example
956
841
  * ```typescript
957
- * // With frame filtering
958
- * async function* filteredFrames() {
959
- * for await (const frame of decoder.frames(input.packets())) {
960
- * const filtered = await filter.process(frame);
961
- * if (filtered) {
962
- * yield filtered;
963
- * }
964
- * frame.free();
965
- * }
966
- * }
967
- *
968
- * for await (const packet of encoder.packets(filteredFrames())) {
842
+ * // Single frame - no automatic flush
843
+ * for await (const packet of encoder.packets(singleFrame)) {
969
844
  * await output.writePacket(packet);
970
845
  * packet.free();
971
846
  * }
847
+ * // Encoder remains open, buffered packets not flushed
972
848
  * ```
973
849
  *
974
850
  * @example
975
851
  * ```typescript
976
- * // Pipeline integration
977
- * import { pipeline } from 'node-av/api';
978
- *
979
- * const control = pipeline(
980
- * input,
981
- * decoder,
982
- * encoder,
983
- * output
984
- * );
985
- * await control.completion;
852
+ * // Explicit flush with EOF
853
+ * for await (const packet of encoder.packets(null)) {
854
+ * if (packet === null) {
855
+ * console.log('All buffered packets flushed');
856
+ * break;
857
+ * }
858
+ * console.log('Buffered packet:', packet.pts);
859
+ * await output.writePacket(packet);
860
+ * packet.free();
861
+ * }
986
862
  * ```
987
863
  *
988
864
  * @see {@link encode} For single frame encoding
@@ -990,123 +866,76 @@ export class Encoder {
990
866
  * @see {@link packetsSync} For sync version
991
867
  */
992
868
  async *packets(frames) {
993
- // Process frames
869
+ const self = this;
870
+ const processFrame = async function* (frame) {
871
+ await self.encode(frame);
872
+ while (true) {
873
+ const packet = await self.receive();
874
+ if (!packet)
875
+ break;
876
+ yield packet;
877
+ }
878
+ }.bind(this);
879
+ const finalize = async function* () {
880
+ for await (const remaining of self.flushPackets()) {
881
+ yield remaining;
882
+ }
883
+ yield null;
884
+ }.bind(this);
885
+ if (frames === null) {
886
+ yield* finalize();
887
+ return;
888
+ }
889
+ if (frames instanceof Frame) {
890
+ yield* processFrame(frames);
891
+ return;
892
+ }
994
893
  for await (const frame_1 of frames) {
995
- const env_3 = { stack: [], error: void 0, hasError: false };
894
+ const env_1 = { stack: [], error: void 0, hasError: false };
996
895
  try {
997
- const frame = __addDisposableResource(env_3, frame_1, false);
998
- // Handle EOF signal
896
+ const frame = __addDisposableResource(env_1, frame_1, false);
999
897
  if (frame === null) {
1000
- // Flush encoder (audio frame buffer doesn't need explicit flush)
1001
- await this.flush();
1002
- while (true) {
1003
- const remaining = await this.receive();
1004
- if (!remaining)
1005
- break;
1006
- yield remaining;
1007
- }
1008
- // Signal EOF and stop processing
1009
- yield null;
898
+ yield* finalize();
1010
899
  return;
1011
900
  }
1012
- if (this.isClosed) {
1013
- break;
1014
- }
1015
- // Open encoder if not already done
1016
- if (!this.initialized) {
1017
- this.initializePromise ??= this.initialize(frame);
1018
- }
1019
- await this.initializePromise;
1020
- // Prepare frame for encoding (set quality, validate channel count)
1021
- if (frame) {
1022
- this.prepareFrameForEncoding(frame);
1023
- }
1024
- // If audio encoder with fixed frame size, use AudioFrameBuffer
1025
- if (this.audioFrameBuffer) {
1026
- // Push frame into buffer
1027
- await this.audioFrameBuffer.push(frame);
1028
- // Pull and encode all available fixed-size frames
1029
- let _bufferedFrame;
1030
- while (!this.isClosed && (_bufferedFrame = await this.audioFrameBuffer.pull()) !== null) {
1031
- const env_4 = { stack: [], error: void 0, hasError: false };
1032
- try {
1033
- const bufferedFrame = __addDisposableResource(env_4, _bufferedFrame, false);
1034
- // Send buffered frame to encoder
1035
- const sendRet = await this.codecContext.sendFrame(bufferedFrame);
1036
- if (sendRet < 0 && sendRet !== AVERROR_EOF && sendRet !== AVERROR_EAGAIN) {
1037
- FFmpegError.throwIfError(sendRet, 'Failed to send frame');
1038
- }
1039
- // Receive packets
1040
- while (true) {
1041
- const packet = await this.receive();
1042
- if (!packet)
1043
- break;
1044
- yield packet;
1045
- }
1046
- }
1047
- catch (e_3) {
1048
- env_4.error = e_3;
1049
- env_4.hasError = true;
1050
- }
1051
- finally {
1052
- __disposeResources(env_4);
1053
- }
1054
- }
1055
- }
1056
- else {
1057
- // Send frame to encoder
1058
- const sendRet = await this.codecContext.sendFrame(frame);
1059
- if (sendRet < 0 && sendRet !== AVERROR_EOF && sendRet !== AVERROR_EAGAIN) {
1060
- FFmpegError.throwIfError(sendRet, 'Failed to send frame');
1061
- }
1062
- // Receive ALL packets
1063
- // A single frame can produce multiple packets (e.g., B-frames, lookahead)
1064
- while (true) {
1065
- const packet = await this.receive();
1066
- if (!packet)
1067
- break;
1068
- yield packet;
1069
- }
1070
- }
901
+ yield* processFrame(frame);
1071
902
  }
1072
- catch (e_4) {
1073
- env_3.error = e_4;
1074
- env_3.hasError = true;
903
+ catch (e_1) {
904
+ env_1.error = e_1;
905
+ env_1.hasError = true;
1075
906
  }
1076
907
  finally {
1077
- __disposeResources(env_3);
908
+ __disposeResources(env_1);
1078
909
  }
1079
910
  }
1080
- // Flush encoder after all frames (fallback if no null was sent)
1081
- await this.flush();
1082
- while (true) {
1083
- const remaining = await this.receive();
1084
- if (!remaining)
1085
- break;
1086
- yield remaining;
1087
- }
1088
- // Signal EOF
1089
- yield null;
1090
911
  }
1091
912
  /**
1092
913
  * Encode frame stream to packet stream synchronously.
1093
914
  * Synchronous version of packets.
1094
915
  *
1095
916
  * High-level sync generator for complete encoding pipeline.
1096
- * Automatically manages frame memory, encoder state,
1097
- * and flushes buffered packets at end.
917
+ * Encoder is only flushed when EOF (null) signal is explicitly received.
1098
918
  * Primary interface for stream-based encoding.
1099
919
  *
1100
- * @param frames - Iterable of frames (freed automatically)
920
+ * **EOF Handling:**
921
+ * - Send null to flush encoder and get remaining buffered packets
922
+ * - Generator yields null after flushing when null is received
923
+ * - No automatic flushing - encoder stays open until EOF or close()
1101
924
  *
1102
- * @yields {Packet} Encoded packets (caller must free)
925
+ * @param frames - Iterable of frames, single frame, or null to flush
926
+ *
927
+ * @yields {Packet | null} Encoded packets, followed by null when explicitly flushed
1103
928
  *
1104
929
  * @throws {FFmpegError} If encoding fails
1105
930
  *
1106
931
  * @example
1107
932
  * ```typescript
1108
- * // Basic encoding pipeline
933
+ * // Stream of frames with automatic EOF propagation
1109
934
  * for (const packet of encoder.packetsSync(decoder.framesSync(packets))) {
935
+ * if (packet === null) {
936
+ * console.log('Encoder flushed');
937
+ * break;
938
+ * }
1110
939
  * output.writePacketSync(packet);
1111
940
  * packet.free(); // Must free output packets
1112
941
  * }
@@ -1114,18 +943,23 @@ export class Encoder {
1114
943
  *
1115
944
  * @example
1116
945
  * ```typescript
1117
- * // With frame filtering
1118
- * function* filteredFrames() {
1119
- * for (const frame of decoder.framesSync(packets)) {
1120
- * const filtered = filter.processSync(frame);
1121
- * if (filtered) {
1122
- * yield filtered;
1123
- * }
1124
- * frame.free();
1125
- * }
946
+ * // Single frame - no automatic flush
947
+ * for (const packet of encoder.packetsSync(singleFrame)) {
948
+ * output.writePacketSync(packet);
949
+ * packet.free();
1126
950
  * }
951
+ * // Encoder remains open, buffered packets not flushed
952
+ * ```
1127
953
  *
1128
- * for (const packet of encoder.packetsSync(filteredFrames())) {
954
+ * @example
955
+ * ```typescript
956
+ * // Explicit flush with EOF
957
+ * for (const packet of encoder.packetsSync(null)) {
958
+ * if (packet === null) {
959
+ * console.log('All buffered packets flushed');
960
+ * break;
961
+ * }
962
+ * console.log('Buffered packet:', packet.pts);
1129
963
  * output.writePacketSync(packet);
1130
964
  * packet.free();
1131
965
  * }
@@ -1136,102 +970,56 @@ export class Encoder {
1136
970
  * @see {@link packets} For async version
1137
971
  */
1138
972
  *packetsSync(frames) {
1139
- // Process frames
973
+ const self = this;
974
+ // Helper: Encode frame and yield all available packets (filters out EAGAIN nulls and EOF)
975
+ const processFrame = function* (frame) {
976
+ self.encodeSync(frame);
977
+ // Receive ALL packets (filter out null/EAGAIN and EOF)
978
+ while (true) {
979
+ const packet = self.receiveSync();
980
+ if (!packet)
981
+ break; // EAGAIN or EOF - no more packets available
982
+ yield packet; // Only yield actual packets
983
+ }
984
+ }.bind(this);
985
+ // Helper: Flush encoder and signal EOF
986
+ const finalize = function* () {
987
+ for (const remaining of self.flushPacketsSync()) {
988
+ yield remaining; // Only yield actual packets
989
+ }
990
+ yield null; // Signal end-of-stream
991
+ }.bind(this);
992
+ // Case 1: EOF input -> flush only
993
+ if (frames === null) {
994
+ yield* finalize();
995
+ return;
996
+ }
997
+ // Case 2: Single frame - NO AUTOMATIC FLUSH
998
+ if (frames instanceof Frame) {
999
+ yield* processFrame(frames);
1000
+ return; // No finalize() call!
1001
+ }
1002
+ // Case 3: Iterable of frames
1140
1003
  for (const frame_2 of frames) {
1141
- const env_5 = { stack: [], error: void 0, hasError: false };
1004
+ const env_2 = { stack: [], error: void 0, hasError: false };
1142
1005
  try {
1143
- const frame = __addDisposableResource(env_5, frame_2, false);
1144
- // Handle EOF signal
1006
+ const frame = __addDisposableResource(env_2, frame_2, false);
1007
+ // Check for EOF signal from upstream
1145
1008
  if (frame === null) {
1146
- // Flush encoder (audio frame buffer doesn't need explicit flush)
1147
- this.flushSync();
1148
- while (true) {
1149
- const remaining = this.receiveSync();
1150
- if (!remaining)
1151
- break;
1152
- yield remaining;
1153
- }
1154
- // Signal EOF and stop processing
1155
- yield null;
1009
+ yield* finalize();
1156
1010
  return;
1157
1011
  }
1158
- if (this.isClosed) {
1159
- break;
1160
- }
1161
- // Open encoder if not already done
1162
- if (!this.initialized) {
1163
- this.initializeSync(frame);
1164
- }
1165
- // Prepare frame for encoding (set quality, validate channel count)
1166
- if (frame) {
1167
- this.prepareFrameForEncoding(frame);
1168
- }
1169
- // If audio encoder with fixed frame size, use AudioFrameBuffer
1170
- if (this.audioFrameBuffer) {
1171
- // Push frame into buffer
1172
- this.audioFrameBuffer.pushSync(frame);
1173
- // Pull and encode all available fixed-size frames
1174
- let _bufferedFrame;
1175
- while (!this.isClosed && (_bufferedFrame = this.audioFrameBuffer.pullSync()) !== null) {
1176
- const env_6 = { stack: [], error: void 0, hasError: false };
1177
- try {
1178
- const bufferedFrame = __addDisposableResource(env_6, _bufferedFrame, false);
1179
- // Send buffered frame to encoder
1180
- const sendRet = this.codecContext.sendFrameSync(bufferedFrame);
1181
- if (sendRet < 0 && sendRet !== AVERROR_EOF && sendRet !== AVERROR_EAGAIN) {
1182
- FFmpegError.throwIfError(sendRet, 'Failed to send frame');
1183
- }
1184
- // Receive packets
1185
- while (true) {
1186
- const packet = this.receiveSync();
1187
- if (!packet)
1188
- break;
1189
- yield packet;
1190
- }
1191
- }
1192
- catch (e_5) {
1193
- env_6.error = e_5;
1194
- env_6.hasError = true;
1195
- }
1196
- finally {
1197
- __disposeResources(env_6);
1198
- }
1199
- }
1200
- }
1201
- else {
1202
- // Send frame to encoder
1203
- const sendRet = this.codecContext.sendFrameSync(frame);
1204
- if (sendRet < 0 && sendRet !== AVERROR_EOF && sendRet !== AVERROR_EAGAIN) {
1205
- FFmpegError.throwIfError(sendRet, 'Failed to send frame');
1206
- }
1207
- // Receive ALL packets
1208
- // A single frame can produce multiple packets (e.g., B-frames, lookahead)
1209
- while (true) {
1210
- const packet = this.receiveSync();
1211
- if (!packet)
1212
- break;
1213
- yield packet;
1214
- }
1215
- }
1012
+ yield* processFrame(frame);
1216
1013
  }
1217
- catch (e_6) {
1218
- env_5.error = e_6;
1219
- env_5.hasError = true;
1014
+ catch (e_2) {
1015
+ env_2.error = e_2;
1016
+ env_2.hasError = true;
1220
1017
  }
1221
1018
  finally {
1222
- __disposeResources(env_5);
1019
+ __disposeResources(env_2);
1223
1020
  }
1224
1021
  }
1225
- // Flush encoder after all frames (fallback if no null was sent)
1226
- this.flushSync();
1227
- while (true) {
1228
- const remaining = this.receiveSync();
1229
- if (!remaining)
1230
- break;
1231
- yield remaining;
1232
- }
1233
- // Signal EOF
1234
- yield null;
1022
+ // No fallback flush - only flush on explicit EOF
1235
1023
  }
1236
1024
  /**
1237
1025
  * Flush encoder and signal end-of-stream.
@@ -1270,17 +1058,17 @@ export class Encoder {
1270
1058
  // For the final frame, we pad or truncate as needed
1271
1059
  let _bufferedFrame;
1272
1060
  while (!this.isClosed && (_bufferedFrame = await this.audioFrameBuffer.pull()) !== null) {
1273
- const env_7 = { stack: [], error: void 0, hasError: false };
1061
+ const env_3 = { stack: [], error: void 0, hasError: false };
1274
1062
  try {
1275
- const bufferedFrame = __addDisposableResource(env_7, _bufferedFrame, false);
1063
+ const bufferedFrame = __addDisposableResource(env_3, _bufferedFrame, false);
1276
1064
  await this.codecContext.sendFrame(bufferedFrame);
1277
1065
  }
1278
- catch (e_7) {
1279
- env_7.error = e_7;
1280
- env_7.hasError = true;
1066
+ catch (e_3) {
1067
+ env_3.error = e_3;
1068
+ env_3.hasError = true;
1281
1069
  }
1282
1070
  finally {
1283
- __disposeResources(env_7);
1071
+ __disposeResources(env_3);
1284
1072
  }
1285
1073
  }
1286
1074
  }
@@ -1330,17 +1118,17 @@ export class Encoder {
1330
1118
  // For the final frame, we pad or truncate as needed
1331
1119
  let _bufferedFrame;
1332
1120
  while (!this.isClosed && (_bufferedFrame = this.audioFrameBuffer.pullSync()) !== null) {
1333
- const env_8 = { stack: [], error: void 0, hasError: false };
1121
+ const env_4 = { stack: [], error: void 0, hasError: false };
1334
1122
  try {
1335
- const bufferedFrame = __addDisposableResource(env_8, _bufferedFrame, false);
1123
+ const bufferedFrame = __addDisposableResource(env_4, _bufferedFrame, false);
1336
1124
  this.codecContext.sendFrameSync(bufferedFrame);
1337
1125
  }
1338
- catch (e_8) {
1339
- env_8.error = e_8;
1340
- env_8.hasError = true;
1126
+ catch (e_4) {
1127
+ env_4.error = e_4;
1128
+ env_4.hasError = true;
1341
1129
  }
1342
1130
  finally {
1343
- __disposeResources(env_8);
1131
+ __disposeResources(env_4);
1344
1132
  }
1345
1133
  }
1346
1134
  }
@@ -1378,11 +1166,12 @@ export class Encoder {
1378
1166
  async *flushPackets() {
1379
1167
  // Send flush signal
1380
1168
  await this.flush();
1169
+ // Yield all remaining packets (filter out null/EAGAIN and EOF)
1381
1170
  while (true) {
1382
1171
  const packet = await this.receive();
1383
1172
  if (!packet)
1384
- break;
1385
- yield packet;
1173
+ break; // Stop on EAGAIN or EOF
1174
+ yield packet; // Only yield actual packets
1386
1175
  }
1387
1176
  }
1388
1177
  /**
@@ -1412,11 +1201,12 @@ export class Encoder {
1412
1201
  *flushPacketsSync() {
1413
1202
  // Send flush signal
1414
1203
  this.flushSync();
1204
+ // Yield all remaining packets (filter out null/EAGAIN and EOF)
1415
1205
  while (true) {
1416
1206
  const packet = this.receiveSync();
1417
1207
  if (!packet)
1418
- break;
1419
- yield packet;
1208
+ break; // Stop on EAGAIN or EOF
1209
+ yield packet; // Only yield actual packets
1420
1210
  }
1421
1211
  }
1422
1212
  /**
@@ -1424,19 +1214,25 @@ export class Encoder {
1424
1214
  *
1425
1215
  * Gets encoded packets from the codec's internal buffer.
1426
1216
  * Handles packet cloning and error checking.
1427
- * Returns null if encoder is closed, not initialized, or no packets available.
1428
- * Call repeatedly until null to drain all buffered packets.
1217
+ * Implements FFmpeg's send/receive pattern.
1218
+ *
1219
+ * **Return Values:**
1220
+ * - `Packet` - Successfully encoded packet (AVERROR >= 0)
1221
+ * - `null` - Need more input frames (AVERROR_EAGAIN), or encoder not initialized
1222
+ * - `undefined` - End of stream reached (AVERROR_EOF), or encoder is closed
1429
1223
  *
1430
1224
  * Direct mapping to avcodec_receive_packet().
1431
1225
  *
1432
- * @returns Cloned packet or null if no packets available
1226
+ * @returns Cloned packet, null if need more data, or undefined if stream ended
1433
1227
  *
1434
1228
  * @throws {FFmpegError} If receive fails with error other than AVERROR_EAGAIN or AVERROR_EOF
1435
1229
  *
1436
1230
  * @example
1437
1231
  * ```typescript
1438
- * const packet = await encoder.receive();
1439
- * if (packet) {
1232
+ * // Process all buffered packets
1233
+ * while (true) {
1234
+ * const packet = await encoder.receive();
1235
+ * if (!packet) break; // Stop on EAGAIN or EOF
1440
1236
  * console.log(`Got packet with PTS: ${packet.pts}`);
1441
1237
  * await output.writePacket(packet);
1442
1238
  * packet.free();
@@ -1445,10 +1241,14 @@ export class Encoder {
1445
1241
  *
1446
1242
  * @example
1447
1243
  * ```typescript
1448
- * // Drain all buffered packets
1449
- * let packet;
1450
- * while ((packet = await encoder.receive()) !== null) {
1451
- * console.log(`Packet size: ${packet.size}`);
1244
+ * // Handle each return value explicitly
1245
+ * const packet = await encoder.receive();
1246
+ * if (packet === EOF) {
1247
+ * console.log('Encoder stream ended');
1248
+ * } else if (packet === null) {
1249
+ * console.log('Need more input frames');
1250
+ * } else {
1251
+ * console.log(`Got packet: pts=${packet.pts}`);
1452
1252
  * await output.writePacket(packet);
1453
1253
  * packet.free();
1454
1254
  * }
@@ -1457,13 +1257,33 @@ export class Encoder {
1457
1257
  * @see {@link encode} For sending frames and receiving packets
1458
1258
  * @see {@link flush} For signaling end-of-stream
1459
1259
  * @see {@link receiveSync} For synchronous version
1260
+ * @see {@link EOF} For end-of-stream signal
1460
1261
  */
1461
1262
  async receive() {
1462
- if (this.isClosed || !this.initialized) {
1263
+ if (this.isClosed) {
1264
+ return EOF;
1265
+ }
1266
+ if (!this.initialized) {
1463
1267
  return null;
1464
1268
  }
1465
1269
  // Clear previous packet data
1466
1270
  this.packet.unref();
1271
+ if (this.audioFrameBuffer?.hasFrame()) {
1272
+ const env_5 = { stack: [], error: void 0, hasError: false };
1273
+ try {
1274
+ const bufferedFrame = __addDisposableResource(env_5, await this.audioFrameBuffer.pull(), false);
1275
+ if (bufferedFrame) {
1276
+ await this.codecContext.sendFrame(bufferedFrame);
1277
+ }
1278
+ }
1279
+ catch (e_5) {
1280
+ env_5.error = e_5;
1281
+ env_5.hasError = true;
1282
+ }
1283
+ finally {
1284
+ __disposeResources(env_5);
1285
+ }
1286
+ }
1467
1287
  const ret = await this.codecContext.receivePacket(this.packet);
1468
1288
  if (ret === 0) {
1469
1289
  // Set packet timebase to codec timebase
@@ -1473,10 +1293,14 @@ export class Encoder {
1473
1293
  // Got a packet, clone it for the user
1474
1294
  return this.packet.clone();
1475
1295
  }
1476
- else if (ret === AVERROR_EAGAIN || ret === AVERROR_EOF) {
1477
- // Need more data or end of stream
1296
+ else if (ret === AVERROR_EAGAIN) {
1297
+ // Need more data
1478
1298
  return null;
1479
1299
  }
1300
+ else if (ret === AVERROR_EOF) {
1301
+ // End of stream
1302
+ return EOF;
1303
+ }
1480
1304
  else {
1481
1305
  // Error
1482
1306
  FFmpegError.throwIfError(ret, 'Failed to receive packet');
@@ -1489,19 +1313,25 @@ export class Encoder {
1489
1313
  *
1490
1314
  * Gets encoded packets from the codec's internal buffer.
1491
1315
  * Handles packet cloning and error checking.
1492
- * Returns null if encoder is closed, not initialized, or no packets available.
1493
- * Call repeatedly until null to drain all buffered packets.
1316
+ * Implements FFmpeg's send/receive pattern.
1317
+ *
1318
+ * **Return Values:**
1319
+ * - `Packet` - Successfully encoded packet (AVERROR >= 0)
1320
+ * - `null` - Need more input frames (AVERROR_EAGAIN), or encoder not initialized
1321
+ * - `undefined` - End of stream reached (AVERROR_EOF), or encoder is closed
1494
1322
  *
1495
1323
  * Direct mapping to avcodec_receive_packet().
1496
1324
  *
1497
- * @returns Cloned packet or null if no packets available
1325
+ * @returns Cloned packet, null if need more data, or undefined if stream ended
1498
1326
  *
1499
1327
  * @throws {FFmpegError} If receive fails with error other than AVERROR_EAGAIN or AVERROR_EOF
1500
1328
  *
1501
1329
  * @example
1502
1330
  * ```typescript
1503
- * const packet = encoder.receiveSync();
1504
- * if (packet) {
1331
+ * // Process all buffered packets
1332
+ * while (true) {
1333
+ * const packet = encoder.receiveSync();
1334
+ * if (!packet) break; // Stop on EAGAIN or EOF
1505
1335
  * console.log(`Got packet with PTS: ${packet.pts}`);
1506
1336
  * output.writePacketSync(packet);
1507
1337
  * packet.free();
@@ -1510,10 +1340,14 @@ export class Encoder {
1510
1340
  *
1511
1341
  * @example
1512
1342
  * ```typescript
1513
- * // Drain all buffered packets
1514
- * let packet;
1515
- * while ((packet = encoder.receiveSync()) !== null) {
1516
- * console.log(`Packet size: ${packet.size}`);
1343
+ * // Handle each return value explicitly
1344
+ * const packet = encoder.receiveSync();
1345
+ * if (packet === EOF) {
1346
+ * console.log('Encoder stream ended');
1347
+ * } else if (packet === null) {
1348
+ * console.log('Need more input frames');
1349
+ * } else {
1350
+ * console.log(`Got packet: pts=${packet.pts}`);
1517
1351
  * output.writePacketSync(packet);
1518
1352
  * packet.free();
1519
1353
  * }
@@ -1522,13 +1356,33 @@ export class Encoder {
1522
1356
  * @see {@link encodeSync} For sending frames and receiving packets
1523
1357
  * @see {@link flushSync} For signaling end-of-stream
1524
1358
  * @see {@link receive} For async version
1359
+ * @see {@link EOF} For end-of-stream signal
1525
1360
  */
1526
1361
  receiveSync() {
1527
- if (this.isClosed || !this.initialized) {
1362
+ if (this.isClosed) {
1363
+ return EOF;
1364
+ }
1365
+ if (!this.initialized) {
1528
1366
  return null;
1529
1367
  }
1530
1368
  // Clear previous packet data
1531
1369
  this.packet.unref();
1370
+ if (this.audioFrameBuffer?.hasFrame()) {
1371
+ const env_6 = { stack: [], error: void 0, hasError: false };
1372
+ try {
1373
+ const bufferedFrame = __addDisposableResource(env_6, this.audioFrameBuffer.pullSync(), false);
1374
+ if (bufferedFrame) {
1375
+ this.codecContext.sendFrameSync(bufferedFrame);
1376
+ }
1377
+ }
1378
+ catch (e_6) {
1379
+ env_6.error = e_6;
1380
+ env_6.hasError = true;
1381
+ }
1382
+ finally {
1383
+ __disposeResources(env_6);
1384
+ }
1385
+ }
1532
1386
  const ret = this.codecContext.receivePacketSync(this.packet);
1533
1387
  if (ret === 0) {
1534
1388
  // Set packet timebase to codec timebase
@@ -1538,10 +1392,14 @@ export class Encoder {
1538
1392
  // Got a packet, clone it for the user
1539
1393
  return this.packet.clone();
1540
1394
  }
1541
- else if (ret === AVERROR_EAGAIN || ret === AVERROR_EOF) {
1542
- // Need more data or end of stream
1395
+ else if (ret === AVERROR_EAGAIN) {
1396
+ // Need more data
1543
1397
  return null;
1544
1398
  }
1399
+ else if (ret === AVERROR_EOF) {
1400
+ // End of stream
1401
+ return EOF;
1402
+ }
1545
1403
  else {
1546
1404
  // Error
1547
1405
  FFmpegError.throwIfError(ret, 'Failed to receive packet');
@@ -1648,9 +1506,9 @@ export class Encoder {
1648
1506
  try {
1649
1507
  // Outer loop - receive frames
1650
1508
  while (!this.inputQueue.isClosed) {
1651
- const env_9 = { stack: [], error: void 0, hasError: false };
1509
+ const env_7 = { stack: [], error: void 0, hasError: false };
1652
1510
  try {
1653
- const frame = __addDisposableResource(env_9, await this.inputQueue.receive(), false);
1511
+ const frame = __addDisposableResource(env_7, await this.inputQueue.receive(), false);
1654
1512
  if (!frame)
1655
1513
  break;
1656
1514
  // Open encoder if not already done
@@ -1660,60 +1518,21 @@ export class Encoder {
1660
1518
  await this.initializePromise;
1661
1519
  // Prepare frame for encoding (set quality, validate channel count)
1662
1520
  this.prepareFrameForEncoding(frame);
1663
- // If audio encoder with fixed frame size, use AudioFrameBuffer
1664
- if (this.audioFrameBuffer) {
1665
- // Push frame into buffer
1666
- await this.audioFrameBuffer.push(frame);
1667
- // Pull and encode all available fixed-size frames
1668
- let _bufferedFrame;
1669
- while ((_bufferedFrame = await this.audioFrameBuffer.pull()) !== null) {
1670
- const env_10 = { stack: [], error: void 0, hasError: false };
1671
- try {
1672
- const bufferedFrame = __addDisposableResource(env_10, _bufferedFrame, false);
1673
- // Send buffered frame to encoder
1674
- const sendRet = await this.codecContext.sendFrame(bufferedFrame);
1675
- if (sendRet < 0 && sendRet !== AVERROR_EOF && sendRet !== AVERROR_EAGAIN) {
1676
- FFmpegError.throwIfError(sendRet, 'Failed to send frame');
1677
- }
1678
- // Receive packets
1679
- while (true) {
1680
- const packet = await this.receive();
1681
- if (!packet)
1682
- break;
1683
- await this.outputQueue.send(packet);
1684
- }
1685
- }
1686
- catch (e_9) {
1687
- env_10.error = e_9;
1688
- env_10.hasError = true;
1689
- }
1690
- finally {
1691
- __disposeResources(env_10);
1692
- }
1693
- }
1694
- }
1695
- else {
1696
- // Send frame to encoder
1697
- const sendRet = await this.codecContext.sendFrame(frame);
1698
- if (sendRet < 0 && sendRet !== AVERROR_EOF && sendRet !== AVERROR_EAGAIN) {
1699
- FFmpegError.throwIfError(sendRet, 'Failed to send frame');
1700
- }
1701
- // Receive ALL packets
1702
- // A single frame can produce multiple packets (e.g., B-frames, lookahead)
1703
- while (!this.outputQueue.isClosed) {
1704
- const packet = await this.receive();
1705
- if (!packet)
1706
- break;
1707
- await this.outputQueue.send(packet);
1708
- }
1521
+ await this.encode(frame);
1522
+ // Receive packets
1523
+ while (!this.outputQueue.isClosed) {
1524
+ const packet = await this.receive();
1525
+ if (!packet)
1526
+ break; // Stop on EAGAIN or EOF
1527
+ await this.outputQueue.send(packet); // Only send actual packets
1709
1528
  }
1710
1529
  }
1711
- catch (e_10) {
1712
- env_9.error = e_10;
1713
- env_9.hasError = true;
1530
+ catch (e_7) {
1531
+ env_7.error = e_7;
1532
+ env_7.hasError = true;
1714
1533
  }
1715
1534
  finally {
1716
- __disposeResources(env_9);
1535
+ __disposeResources(env_7);
1717
1536
  }
1718
1537
  }
1719
1538
  // Flush encoder at end
@@ -1721,8 +1540,8 @@ export class Encoder {
1721
1540
  while (!this.outputQueue.isClosed) {
1722
1541
  const packet = await this.receive();
1723
1542
  if (!packet)
1724
- break;
1725
- await this.outputQueue.send(packet);
1543
+ break; // Stop on EAGAIN or EOF
1544
+ await this.outputQueue.send(packet); // Only send actual packets
1726
1545
  }
1727
1546
  }
1728
1547
  catch {
@@ -1734,14 +1553,45 @@ export class Encoder {
1734
1553
  }
1735
1554
  }
1736
1555
  /**
1737
- * Send frame to input queue.
1556
+ * Send frame to input queue or flush the pipeline.
1557
+ *
1558
+ * When frame is provided, queues it for encoding.
1559
+ * When null is provided, triggers flush sequence:
1560
+ * - Closes input queue
1561
+ * - Waits for worker completion
1562
+ * - Flushes encoder and sends remaining packets to output queue
1563
+ * - Closes output queue
1564
+ * - Waits for pipeTo task completion (writes to muxer)
1565
+ *
1566
+ * Used by scheduler system for pipeline control.
1738
1567
  *
1739
- * @param frame - Frame to send
1568
+ * @param frame - Frame to send, or null to flush
1740
1569
  *
1741
1570
  * @internal
1742
1571
  */
1743
1572
  async sendToQueue(frame) {
1744
- await this.inputQueue.send(frame);
1573
+ if (frame) {
1574
+ await this.inputQueue.send(frame);
1575
+ }
1576
+ else {
1577
+ // Close input queue to signal end of stream to worker
1578
+ this.inputQueue.close();
1579
+ // Wait for worker to finish processing all frames (if exists)
1580
+ if (this.workerPromise) {
1581
+ await this.workerPromise;
1582
+ }
1583
+ // Flush encoder at end
1584
+ await this.flush();
1585
+ while (true) {
1586
+ const packet = await this.receive();
1587
+ if (!packet)
1588
+ break; // Stop on EAGAIN or EOF
1589
+ await this.outputQueue.send(packet); // Only send actual packets
1590
+ }
1591
+ if (this.pipeToPromise) {
1592
+ await this.pipeToPromise;
1593
+ }
1594
+ }
1745
1595
  }
1746
1596
  /**
1747
1597
  * Receive packet from output queue.
@@ -1753,32 +1603,6 @@ export class Encoder {
1753
1603
  async receiveFromQueue() {
1754
1604
  return await this.outputQueue.receive();
1755
1605
  }
1756
- /**
1757
- * Flush the entire filter pipeline.
1758
- *
1759
- * Propagates flush through worker, output queue, and next component.
1760
- *
1761
- * @internal
1762
- */
1763
- async flushPipeline() {
1764
- // Close input queue to signal end of stream to worker
1765
- this.inputQueue.close();
1766
- // Wait for worker to finish processing all frames (if exists)
1767
- if (this.workerPromise) {
1768
- await this.workerPromise;
1769
- }
1770
- // Flush encoder at end
1771
- await this.flush();
1772
- while (true) {
1773
- const packet = await this.receive();
1774
- if (!packet)
1775
- break;
1776
- await this.outputQueue.send(packet);
1777
- }
1778
- if (this.pipeToPromise) {
1779
- await this.pipeToPromise;
1780
- }
1781
- }
1782
1606
  /**
1783
1607
  * Initialize encoder from first frame.
1784
1608
  *
@@ -2048,6 +1872,9 @@ export class Encoder {
2048
1872
  * @internal
2049
1873
  */
2050
1874
  prepareFrameForEncoding(frame) {
1875
+ // Clear pict_type - encoder will determine frame types based on its own settings
1876
+ // Input stream's frame type hints are irrelevant when re-encoding
1877
+ frame.pictType = AV_PICTURE_TYPE_NONE;
2051
1878
  // Adjust frame PTS and timebase to encoder timebase
2052
1879
  // This matches FFmpeg's adjust_frame_pts_to_encoder_tb() behavior which:
2053
1880
  // 1. Converts PTS from frame's timebase to encoder's timebase (av_rescale_q)