node-av 6.2.0-beta.3 → 6.2.0-beta.30

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 (101) hide show
  1. package/README.md +13 -8
  2. package/dist/api/audio-frame-buffer.d.ts +438 -7
  3. package/dist/api/audio-frame-buffer.js +792 -52
  4. package/dist/api/audio-frame-buffer.js.map +1 -1
  5. package/dist/api/bitstream-filter.d.ts +82 -4
  6. package/dist/api/bitstream-filter.js +197 -100
  7. package/dist/api/bitstream-filter.js.map +1 -1
  8. package/dist/api/decoder.d.ts +5 -0
  9. package/dist/api/decoder.js +13 -6
  10. package/dist/api/decoder.js.map +1 -1
  11. package/dist/api/demuxer.d.ts +29 -4
  12. package/dist/api/demuxer.js +209 -60
  13. package/dist/api/demuxer.js.map +1 -1
  14. package/dist/api/encoder-pool.d.ts +43 -1
  15. package/dist/api/encoder-pool.js +121 -9
  16. package/dist/api/encoder-pool.js.map +1 -1
  17. package/dist/api/encoder.d.ts +58 -2
  18. package/dist/api/encoder.js +173 -57
  19. package/dist/api/encoder.js.map +1 -1
  20. package/dist/api/filter.js +15 -6
  21. package/dist/api/filter.js.map +1 -1
  22. package/dist/api/fmp4-stream.d.ts +327 -8
  23. package/dist/api/fmp4-stream.js +663 -123
  24. package/dist/api/fmp4-stream.js.map +1 -1
  25. package/dist/api/hardware.d.ts +2 -0
  26. package/dist/api/hardware.js +25 -4
  27. package/dist/api/hardware.js.map +1 -1
  28. package/dist/api/index.d.ts +2 -2
  29. package/dist/api/index.js.map +1 -1
  30. package/dist/api/io-stream.d.ts +3 -2
  31. package/dist/api/io-stream.js +76 -5
  32. package/dist/api/io-stream.js.map +1 -1
  33. package/dist/api/muxer.d.ts +289 -7
  34. package/dist/api/muxer.js +696 -109
  35. package/dist/api/muxer.js.map +1 -1
  36. package/dist/api/pipeline.d.ts +24 -0
  37. package/dist/api/pipeline.js +76 -5
  38. package/dist/api/pipeline.js.map +1 -1
  39. package/dist/api/rtp-stream.d.ts +77 -8
  40. package/dist/api/rtp-stream.js +316 -88
  41. package/dist/api/rtp-stream.js.map +1 -1
  42. package/dist/api/scaler.d.ts +124 -21
  43. package/dist/api/scaler.js +347 -114
  44. package/dist/api/scaler.js.map +1 -1
  45. package/dist/api/utilities/async-queue.d.ts +3 -0
  46. package/dist/api/utilities/async-queue.js +8 -0
  47. package/dist/api/utilities/async-queue.js.map +1 -1
  48. package/dist/api/utilities/packet-age-watchdog.d.ts +198 -0
  49. package/dist/api/utilities/packet-age-watchdog.js +594 -0
  50. package/dist/api/utilities/packet-age-watchdog.js.map +1 -0
  51. package/dist/api/webrtc-stream.d.ts +2 -0
  52. package/dist/api/webrtc-stream.js +46 -12
  53. package/dist/api/webrtc-stream.js.map +1 -1
  54. package/dist/api/whisper.d.ts +2 -0
  55. package/dist/api/whisper.js +63 -23
  56. package/dist/api/whisper.js.map +1 -1
  57. package/dist/constants/constants.d.ts +1 -0
  58. package/dist/constants/constants.js +1 -0
  59. package/dist/constants/constants.js.map +1 -1
  60. package/dist/constants/filter-options.d.ts +2 -0
  61. package/dist/ffmpeg/install.js +104 -26
  62. package/dist/ffmpeg/install.js.map +1 -1
  63. package/dist/ffmpeg/utils.js +7 -4
  64. package/dist/ffmpeg/utils.js.map +1 -1
  65. package/dist/lib/binding.d.ts +10 -1
  66. package/dist/lib/binding.js +25 -16
  67. package/dist/lib/binding.js.map +1 -1
  68. package/dist/lib/bitstream-filter-context.d.ts +2 -0
  69. package/dist/lib/bitstream-filter-context.js +2 -0
  70. package/dist/lib/bitstream-filter-context.js.map +1 -1
  71. package/dist/lib/codec-context.d.ts +0 -2
  72. package/dist/lib/codec-context.js +4 -22
  73. package/dist/lib/codec-context.js.map +1 -1
  74. package/dist/lib/filter-context.d.ts +0 -1
  75. package/dist/lib/filter-context.js +2 -11
  76. package/dist/lib/filter-context.js.map +1 -1
  77. package/dist/lib/format-context.d.ts +70 -7
  78. package/dist/lib/format-context.js +77 -21
  79. package/dist/lib/format-context.js.map +1 -1
  80. package/dist/lib/frame.d.ts +17 -13
  81. package/dist/lib/frame.js +24 -23
  82. package/dist/lib/frame.js.map +1 -1
  83. package/dist/lib/hardware-frames-context.d.ts +0 -1
  84. package/dist/lib/hardware-frames-context.js +2 -8
  85. package/dist/lib/hardware-frames-context.js.map +1 -1
  86. package/dist/lib/io-context.d.ts +4 -2
  87. package/dist/lib/io-context.js +4 -2
  88. package/dist/lib/io-context.js.map +1 -1
  89. package/dist/lib/native-types.d.ts +7 -5
  90. package/dist/lib/option.d.ts +26 -0
  91. package/dist/lib/option.js +42 -18
  92. package/dist/lib/option.js.map +1 -1
  93. package/dist/lib/packet.d.ts +5 -0
  94. package/dist/lib/packet.js +5 -0
  95. package/dist/lib/packet.js.map +1 -1
  96. package/dist/lib/stream.d.ts +8 -4
  97. package/dist/lib/stream.js +22 -24
  98. package/dist/lib/stream.js.map +1 -1
  99. package/install/check.js +40 -79
  100. package/package.json +45 -34
  101. package/build_mac_local.sh +0 -69
@@ -10,9 +10,9 @@ export type MP4BoxType = 'ftyp' | 'styp' | 'moov' | 'moof' | 'mdat' | 'free' | '
10
10
  export interface MP4Box {
11
11
  /** Four-character code identifying the box type (e.g., 'ftyp', 'moov', 'moof') */
12
12
  type: MP4BoxType;
13
- /** Total size of the box in bytes (including 8-byte header) */
13
+ /** Total size of the box in bytes (including the 8-byte header, or 16-byte for 64-bit boxes) */
14
14
  size: number;
15
- /** Box payload data (excluding the 8-byte header) */
15
+ /** Box payload data (excluding the 8-byte header, or 16-byte for 64-bit boxes) */
16
16
  data: Buffer;
17
17
  /** Offset of this box in the original buffer */
18
18
  offset: number;
@@ -54,6 +54,12 @@ export interface FMP4StreamOptions {
54
54
  /**
55
55
  * Callback invoked when the stream is closed or encounters an error.
56
56
  *
57
+ * Called once per started session. On an error the stream has already been
58
+ * stopped and its resources (including the input connection) released.
59
+ * Output the muxer still held at the error is discarded, so no `onData` call
60
+ * follows the failure. Exceptions thrown by the callback are logged and do
61
+ * not propagate.
62
+ *
57
63
  * @param error - Optional error if stream closed due to an error
58
64
  */
59
65
  onClose?: (error?: Error) => void;
@@ -66,12 +72,56 @@ export interface FMP4StreamOptions {
66
72
  supportedCodecs?: string;
67
73
  /**
68
74
  * Fragment duration in microseconds.
75
+ *
76
+ * Passed to the mp4 muxer as `frag_duration`, which creates a new fragment
77
+ * whenever the current one exceeds this duration (in addition to any cuts
78
+ * requested via movFlags such as `frag_keyframe`).
69
79
  * Smaller values reduce latency but increase overhead.
70
80
  * Set to 1 to send data as soon as possible.
81
+ * Transcoded video gets a GOP of the fragment duration from 0.5 s on, so
82
+ * every fragment starts with a keyframe; below that, and with 0, it gets a
83
+ * 2 s GOP.
71
84
  *
72
85
  * @default 1
86
+ *
87
+ * @example
88
+ * ```typescript
89
+ * const stream = FMP4Stream.create('rtsp://camera.local/stream', {
90
+ * supportedCodecs: 'avc1.640029,mp4a.40.2',
91
+ * fragDuration: 2_000_000, // 2 seconds in microseconds
92
+ * });
93
+ * ```
73
94
  */
74
95
  fragDuration?: number;
96
+ /**
97
+ * Maximum number of media fragments buffered for a {@link FMP4Stream.fragments}
98
+ * consumer before the oldest are dropped.
99
+ *
100
+ * Live sources (RTSP cameras) produce fragments at a fixed rate regardless of
101
+ * consumer speed. Without a bound, a slow or stalled consumer would grow the
102
+ * queue until the process runs out of memory. When the backlog exceeds this
103
+ * limit, the oldest media fragments are dropped (never the init segment,
104
+ * which is kept separately via {@link FMP4Stream.initSegment}) and
105
+ * {@link FMP4Stream.droppedFragments} is incremented.
106
+ *
107
+ * @default 16
108
+ *
109
+ * @example
110
+ * ```typescript
111
+ * const stream = FMP4Stream.create('rtsp://camera.local/stream', {
112
+ * supportedCodecs: 'avc1.640029,mp4a.40.2',
113
+ * boxMode: true,
114
+ * maxQueuedFragments: 8,
115
+ * });
116
+ *
117
+ * await stream.start();
118
+ * for await (const fragment of stream.fragments()) {
119
+ * await sendToClient(fragment.data); // Slow consumer - oldest fragments dropped
120
+ * }
121
+ * console.log(`Dropped ${stream.droppedFragments} fragments`);
122
+ * ```
123
+ */
124
+ maxQueuedFragments?: number;
75
125
  /**
76
126
  * Hardware acceleration configuration for video transcoding.
77
127
  *
@@ -96,6 +146,11 @@ export interface FMP4StreamOptions {
96
146
  fps?: number;
97
147
  width?: number;
98
148
  height?: number;
149
+ bitrate?: number;
150
+ /**
151
+ * FourCC sample entry tag for the HEVC video stream, e.g. `hvc1`. Only applied to HEVC output.
152
+ */
153
+ tag?: string;
99
154
  encoderOptions?: EncoderOptions['options'];
100
155
  };
101
156
  /**
@@ -112,7 +167,10 @@ export interface FMP4StreamOptions {
112
167
  bufferSize?: number;
113
168
  /**
114
169
  * Enable box mode - buffers data until complete MP4 boxes are available.
115
- * When true, onData receives complete boxes with parsed box information.
170
+ * When true, onData receives complete boxes with parsed box information,
171
+ * and media fragments are emitted fragment-aligned: a callback that contains
172
+ * a `moof` always contains its `mdat` too, regardless of how the muxer's
173
+ * I/O buffer split the write (large fragments arrive in several chunks).
116
174
  * When false, onData receives raw chunks as they arrive from FFmpeg.
117
175
  *
118
176
  * @default false
@@ -124,6 +182,60 @@ export interface FMP4StreamOptions {
124
182
  * @default '+frag_keyframe+separate_moof+default_base_moof+empty_moov'
125
183
  */
126
184
  movFlags?: string;
185
+ /**
186
+ * Backward DTS jump in seconds above which the stream ends with an error.
187
+ *
188
+ * A live source that restarts its clock (a camera reboot behind a restreamer)
189
+ * would otherwise leave every following packet clamped to one tick past the
190
+ * previous one, so fragments carry no media for as long as the jump. The stream
191
+ * instead stops and reports the error through `onClose(error)`, and the owner
192
+ * can restart it on a fresh timeline. Smaller jumps are clamped. `0` disables
193
+ * the check. Same semantics as the `Muxer` option of the same name.
194
+ *
195
+ * @default 10
196
+ */
197
+ dtsBackwardThreshold?: number;
198
+ /**
199
+ * Forward DTS jump in seconds, beyond the real time that passed, above which
200
+ * the stream ends with an error.
201
+ *
202
+ * A live source that restarts its clock (a camera reboot behind a
203
+ * restreamer) can continue with timestamps far ahead of the time that
204
+ * passed. Muxing that would leave a sample spanning the jump, or, when the
205
+ * timeline later returns, a stream clamped behind it. The stream instead
206
+ * stops and reports the error through `onClose(error)`, and the owner can
207
+ * restart it on a fresh timeline. Gaps in the source come with a matching
208
+ * wait and pass. Only meaningful for inputs read in real time, so it is off
209
+ * for files, a pre-opened Demuxer and frame sources unless set; pass it for
210
+ * live inputs read over HTTP (HLS, HTTP-FLV) as well. Same semantics as the
211
+ * `Muxer` option of the same name; `0` disables the check.
212
+ *
213
+ * @default 10 for rtsp, rtsps, rtmp (and rtmps, rtmpt, rtmpe, rtmpte, rtmpts), rtp, srtp, udp, srt, tcp, tls and sctp URLs, 0 otherwise
214
+ */
215
+ dtsForwardThreshold?: number;
216
+ /**
217
+ * Longest time in seconds a packet may wait inside the muxer before it is
218
+ * emitted in a fragment.
219
+ *
220
+ * The mp4 muxer holds packets back to interleave the streams by timestamp.
221
+ * When a stream's timestamps stop advancing or run far ahead of a silent
222
+ * stream (a camera restarting its clock behind a restreamer, missing
223
+ * timestamps), packets can pile up without ever being emitted while memory
224
+ * grows. The age is measured per stream in real time while the input
225
+ * delivers packets and is independent of the timestamps; once the oldest
226
+ * pending packet of any stream is older than this, the stream stops and
227
+ * reports the error through `onClose(error)`. A pause of the whole input
228
+ * counts at most 2 s, so a source that goes quiet and resumes does not end
229
+ * the session. Healthy sessions stay below the default: up to the fragment
230
+ * duration (the GOP length with `fragDuration: 0`) plus about 10 s of
231
+ * interleaving delay while a stream is silent. Inputs read faster than real
232
+ * time can pile up correspondingly more media before it fires, inputs that
233
+ * deliver packets more than 2 s apart take correspondingly longer. `0`
234
+ * disables the check.
235
+ *
236
+ * @default 60
237
+ */
238
+ maxPacketAge?: number;
127
239
  /**
128
240
  * AbortSignal for cancellation.
129
241
  *
@@ -132,6 +244,19 @@ export interface FMP4StreamOptions {
132
244
  */
133
245
  signal?: AbortSignal;
134
246
  }
247
+ /**
248
+ * Video description of the muxed fMP4 output.
249
+ */
250
+ export interface FMP4VideoInfo {
251
+ /** Output codec family. */
252
+ codec: 'h264' | 'hevc' | 'av1' | 'unknown';
253
+ /** RFC 6381 codec string of the output (e.g. `hvc1.1.6.L120.B0`). */
254
+ codecString: string;
255
+ width: number;
256
+ height: number;
257
+ /** Frames per second, 0 when unknown. */
258
+ fps: number;
259
+ }
135
260
  /**
136
261
  * Target codec strings for fMP4 streaming.
137
262
  */
@@ -147,7 +272,8 @@ export declare const FMP4_CODECS: {
147
272
  * High-level fMP4 streaming with automatic codec detection and transcoding.
148
273
  *
149
274
  * Provides fragmented MP4 streaming for clients.
150
- * Automatically transcodes video to H.264 and audio to AAC if not supported by client.
275
+ * Automatically transcodes video to H.264 and audio to AAC (Opus when the client
276
+ * lists Opus but not AAC) if not supported by client.
151
277
  * Client sends supported codecs, server transcodes accordingly.
152
278
  * Essential component for building adaptive streaming servers.
153
279
  *
@@ -204,9 +330,19 @@ export declare class FMP4Stream {
204
330
  private signal?;
205
331
  private supportedCodecs;
206
332
  private incompleteBoxBuffer;
333
+ private pendingFragment;
334
+ private abortHandler?;
335
+ private stopRequested;
336
+ private discardOutput;
337
+ private stopPromise?;
338
+ private startAbort?;
339
+ private stopSignal;
207
340
  private fragmentQueue;
341
+ private packetAgeWatchdog?;
342
+ private _droppedFragments;
208
343
  private _initSegment;
209
344
  private _initSegmentResolve;
345
+ private _initSegmentReject;
210
346
  private _initSegmentPromise;
211
347
  private _ftypData;
212
348
  private _moovData;
@@ -217,6 +353,8 @@ export declare class FMP4Stream {
217
353
  *
218
354
  * Use {@link create} factory method
219
355
  *
356
+ * @throws {RangeError} If dtsBackwardThreshold, dtsForwardThreshold or maxPacketAge is not a finite number >= 0
357
+ *
220
358
  * @internal
221
359
  */
222
360
  private constructor();
@@ -232,6 +370,8 @@ export declare class FMP4Stream {
232
370
  *
233
371
  * @returns Configured fMP4 stream instance
234
372
  *
373
+ * @throws {RangeError} If dtsBackwardThreshold, dtsForwardThreshold or maxPacketAge is not a finite number >= 0
374
+ *
235
375
  * @example
236
376
  * ```typescript
237
377
  * // Stream from file with codec negotiation
@@ -257,10 +397,32 @@ export declare class FMP4Stream {
257
397
  *
258
398
  * Only available when boxMode is enabled.
259
399
  * Resolves once the first ftyp and moov boxes have been received.
400
+ * Rejects if the stream fails or is stopped before the init segment
401
+ * was produced, so awaiting it never hangs.
260
402
  *
261
403
  * @throws {Error} If boxMode is not enabled
262
404
  */
263
405
  get initSegment(): Promise<Buffer>;
406
+ /**
407
+ * Number of media fragments dropped due to consumer backpressure.
408
+ *
409
+ * Incremented whenever the {@link fragments} backlog exceeds
410
+ * `maxQueuedFragments` and the oldest fragment is discarded.
411
+ * Reset to 0 when the stream is (re)started.
412
+ *
413
+ * @returns Total dropped fragments for the current run
414
+ *
415
+ * @example
416
+ * ```typescript
417
+ * for await (const fragment of stream.fragments()) {
418
+ * await sendToClient(fragment.data);
419
+ * if (stream.droppedFragments > 0) {
420
+ * console.warn(`Consumer too slow: ${stream.droppedFragments} fragments dropped`);
421
+ * }
422
+ * }
423
+ * ```
424
+ */
425
+ get droppedFragments(): number;
264
426
  /**
265
427
  * Get the demuxer instance.
266
428
  *
@@ -300,13 +462,29 @@ export declare class FMP4Stream {
300
462
  * ```
301
463
  */
302
464
  getCodecString(): string;
465
+ /**
466
+ * Video description of the muxed fMP4 output (output codec, RFC 6381 codec string, resolution, fps).
467
+ *
468
+ * Available after {@link start} has opened the input.
469
+ *
470
+ * @returns The output video info, or `undefined` when there is no video track.
471
+ *
472
+ * @example
473
+ * ```typescript
474
+ * const videoInfo = stream.getVideoInfo();
475
+ * if (videoInfo) {
476
+ * console.log(`Codec: ${videoInfo.codec}, Resolution: ${videoInfo.width}x${videoInfo.height}, FPS: ${videoInfo.fps}`);
477
+ * }
478
+ * ```
479
+ */
480
+ getVideoInfo(): FMP4VideoInfo | undefined;
303
481
  /**
304
482
  * Start streaming media to fMP4 chunks.
305
483
  *
306
484
  * Begins the media processing pipeline, reading packets from input,
307
485
  * transcoding based on supported codecs, and generating fMP4 chunks.
308
486
  * Video transcodes to H.264 if H.264/H.265 not supported.
309
- * Audio transcodes to AAC if AAC/FLAC/Opus not supported.
487
+ * Audio transcodes to AAC (Opus when only Opus is listed) if the input codec is not supported.
310
488
  * This method returns immediately after starting the pipeline.
311
489
  *
312
490
  * @returns Promise that resolves when pipeline is started
@@ -334,11 +512,57 @@ export declare class FMP4Stream {
334
512
  * @internal
335
513
  */
336
514
  private doStart;
515
+ /**
516
+ * Abort an in-flight startup once stop() was requested.
517
+ *
518
+ * Checked after every await in the startup path: the resources created so
519
+ * far are all assigned to instance fields, so the stop() that is waiting on
520
+ * this startup tears them down the moment it unwinds.
521
+ *
522
+ * @throws {Error} If stop() was called while starting
523
+ *
524
+ * @internal
525
+ */
526
+ private throwIfStopRequested;
527
+ /**
528
+ * Cap the video bitrate on the encoder context.
529
+ *
530
+ * Applies a VBV rate cap (maxrate + a 1s buffer) WITHOUT setting a target
531
+ * bitrate, so the encoder keeps its default constant-quality mode but can
532
+ * never exceed the negotiated ceiling. Setting the bitrate as an ABR target
533
+ * instead would push a normally-lighter stream UP to fill it, overshooting on
534
+ * bursts - the opposite of what strict rate-capped consumers want.
535
+ * No-op when no bitrate was requested.
536
+ *
537
+ * @param ctx - Encoder codec context
538
+ *
539
+ * @param bitrate - Maximum bitrate in bits per second, or undefined
540
+ *
541
+ * @internal
542
+ */
543
+ private applyBitrate;
544
+ /**
545
+ * GOP size for the video encoder that starts every fragment with a keyframe.
546
+ *
547
+ * frag_duration flushes a fragment once it is that long, mid-GOP on a
548
+ * P-frame unless the GOP has the same length, and strict consumers reject
549
+ * such fragments. Shorter fragments (the 1 µs default flushes after every
550
+ * frame) are not matched: a GOP of a few frames multiplies the bitrate, so
551
+ * below MIN_FRAGMENT_GOP the GOP is 2 s, as with a fragDuration of 0.
552
+ *
553
+ * @param fps - Output frame rate
554
+ *
555
+ * @returns GOP size in frames
556
+ *
557
+ * @internal
558
+ */
559
+ private gopSize;
337
560
  /**
338
561
  * Create the fMP4 output muxer with the fragment-emitting write callback.
339
562
  *
340
563
  * Uses `this.input` when present (demuxer path) and no input for the
341
- * frame-source path.
564
+ * frame-source path. Attaches a fresh packet age watchdog unless
565
+ * `maxPacketAge` is 0.
342
566
  *
343
567
  * @returns The configured muxer
344
568
  *
@@ -349,13 +573,36 @@ export declare class FMP4Stream {
349
573
  * Wire the pipeline completion to the fragment/close callbacks.
350
574
  *
351
575
  * On success ends the fragment iterator and invokes `onClose()`; on error
352
- * stops the stream and invokes `onClose(error)`. Runs in the background.
576
+ * stops the stream, discarding the output the muxer still holds, and invokes
577
+ * `onClose(error)`. Either way `onClose` runs exactly once and the detached
578
+ * chain never rejects. Runs in the background.
353
579
  *
354
580
  * @param completion - The pipeline completion promise
355
581
  *
356
582
  * @internal
357
583
  */
358
584
  private attachCompletion;
585
+ /**
586
+ * Invoke the owner's onClose callback.
587
+ *
588
+ * The completion chain runs detached, so an exception from the callback
589
+ * could only surface as an unhandled rejection - it is logged instead.
590
+ *
591
+ * @param error - Pipeline error, or undefined for a clean end
592
+ *
593
+ * @internal
594
+ */
595
+ private emitClose;
596
+ /**
597
+ * Whether a pipeline rejection was caused by an intentional shutdown.
598
+ *
599
+ * @param error - The pipeline rejection reason
600
+ *
601
+ * @returns True when stop() is in flight or the owner's signal aborted
602
+ *
603
+ * @internal
604
+ */
605
+ private isDeliberateStop;
359
606
  /**
360
607
  * Set up encoders and the fMP4 muxer for a pre-composited frame source and
361
608
  * start encoding. No demuxer or decoder is involved - the provided frames are
@@ -374,7 +621,8 @@ export declare class FMP4Stream {
374
621
  *
375
622
  * Stops the pipeline, closes output, and releases all FFmpeg resources.
376
623
  * Safe to call multiple times. After stopping, you can call start() again
377
- * to restart the stream.
624
+ * to restart the stream. Resolves even if the pipeline had failed; that
625
+ * error is reported through `onClose(error)`.
378
626
  *
379
627
  * @example
380
628
  * ```typescript
@@ -388,6 +636,12 @@ export declare class FMP4Stream {
388
636
  * ```
389
637
  */
390
638
  stop(): Promise<void>;
639
+ /**
640
+ * Perform the actual teardown (see {@link stop}).
641
+ *
642
+ * @internal
643
+ */
644
+ private doStop;
391
645
  /**
392
646
  * Async generator that yields media fragments (moof+mdat chunks).
393
647
  *
@@ -399,8 +653,16 @@ export declare class FMP4Stream {
399
653
  *
400
654
  * The generator completes when the stream stops or the pipeline ends.
401
655
  *
656
+ * Only a single consumer is supported: a second concurrent fragments() call
657
+ * would silently strand the first consumer, so it throws instead. The
658
+ * backlog is bounded by `maxQueuedFragments` - if the consumer falls behind
659
+ * a live source, the oldest fragments are dropped (see
660
+ * {@link droppedFragments}).
661
+ *
402
662
  * @yields {FMP4Fragment} Media fragment with data buffer and box info
403
663
  *
664
+ * @throws {Error} If another fragments() iterator is already active
665
+ *
404
666
  * @example
405
667
  * ```typescript
406
668
  * const stream = FMP4Stream.create('rtsp://camera/stream', {
@@ -433,6 +695,27 @@ export declare class FMP4Stream {
433
695
  * @internal
434
696
  */
435
697
  private isVideoCodecSupported;
698
+ /**
699
+ * Codec that unsupported audio is transcoded to.
700
+ *
701
+ * @returns Opus when the client lists Opus but not AAC, otherwise AAC
702
+ *
703
+ * @internal
704
+ */
705
+ private audioTranscodeTarget;
706
+ /**
707
+ * Create the audio filter and encoder for the transcode target.
708
+ *
709
+ * AAC is resampled to 44.1 kHz stereo. Opus is resampled to 48 kHz, the only
710
+ * rate it transmits, keeps mono input mono and uses 20 ms frames.
711
+ *
712
+ * @param decoder - Input audio decoder, absent for a frame source
713
+ *
714
+ * @param channels - Input channel count, 0 when unknown
715
+ *
716
+ * @internal
717
+ */
718
+ private createAudioEncoder;
436
719
  /**
437
720
  * Check if audio codec is supported.
438
721
  *
@@ -446,11 +729,35 @@ export declare class FMP4Stream {
446
729
  /**
447
730
  * Process buffer in box mode - buffers until complete boxes are available.
448
731
  *
732
+ * The muxer splits writes at arbitrary positions, so a chunk may end
733
+ * anywhere - including inside a box header. Any trailing partial box
734
+ * (header or body) is buffered for the next chunk; the parser never
735
+ * loses bytes regardless of where the split falls.
736
+ *
737
+ * Emission is fragment-aligned: a `moof` is held back until its `mdat` has
738
+ * arrived, then both are emitted as ONE onData call. The avio buffer flushes
739
+ * at arbitrary points (a fragment larger than the buffer always splits), so
740
+ * without this a consumer would see a bare `moof` followed by a detached
741
+ * `mdat` - unplayable framing for fMP4 consumers.
742
+ * Non-fragment boxes (ftyp, moov, mfra, ...) are emitted as they complete.
743
+ *
449
744
  * @param chunk - Incoming data chunk from FFmpeg
450
745
  *
451
746
  * @internal
452
747
  */
453
748
  private processBoxMode;
749
+ /**
750
+ * Emit a group of complete boxes as one onData call.
751
+ *
752
+ * Concatenates the raw box bytes, rebuilds the box list with offsets
753
+ * relative to the emitted buffer, tracks the init segment, and feeds the
754
+ * fragments() queue when the group is a media fragment.
755
+ *
756
+ * @param entries - Complete boxes in stream order
757
+ *
758
+ * @internal
759
+ */
760
+ private emitBoxGroup;
454
761
  /**
455
762
  * Push a fragment to the fragment queue for the async generator.
456
763
  *
@@ -461,6 +768,18 @@ export declare class FMP4Stream {
461
768
  * @internal
462
769
  */
463
770
  private pushFragment;
771
+ /**
772
+ * Reject a pending initSegment promise so consumers never hang.
773
+ *
774
+ * No-op when the promise was never requested or has already settled. The
775
+ * promise is marked as handled to avoid an unhandled rejection when no
776
+ * consumer is currently awaiting it.
777
+ *
778
+ * @param error - Rejection reason (pipeline error or stop-before-init)
779
+ *
780
+ * @internal
781
+ */
782
+ private rejectInitSegment;
464
783
  /**
465
784
  * Signal the fragment queue that no more fragments will arrive.
466
785
  *