@remotion/web-renderer 4.0.383 → 4.0.384

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.
@@ -1,4 +1,4 @@
1
- // ../../node_modules/.bun/mediabunny@1.25.3/node_modules/mediabunny/dist/modules/src/misc.js
1
+ // ../../node_modules/.bun/mediabunny@1.25.8/node_modules/mediabunny/dist/modules/src/misc.js
2
2
  /*!
3
3
  * Copyright (c) 2025-present, Vanilagy and contributors
4
4
  *
@@ -186,6 +186,29 @@ var promiseWithResolvers = () => {
186
186
  var assertNever = (x) => {
187
187
  throw new Error(`Unexpected value: ${x}`);
188
188
  };
189
+ var setUint24 = (view, byteOffset, value, littleEndian) => {
190
+ value = value >>> 0;
191
+ value = value & 16777215;
192
+ if (littleEndian) {
193
+ view.setUint8(byteOffset, value & 255);
194
+ view.setUint8(byteOffset + 1, value >>> 8 & 255);
195
+ view.setUint8(byteOffset + 2, value >>> 16 & 255);
196
+ } else {
197
+ view.setUint8(byteOffset, value >>> 16 & 255);
198
+ view.setUint8(byteOffset + 1, value >>> 8 & 255);
199
+ view.setUint8(byteOffset + 2, value & 255);
200
+ }
201
+ };
202
+ var setInt24 = (view, byteOffset, value, littleEndian) => {
203
+ value = clamp(value, -8388608, 8388607);
204
+ if (value < 0) {
205
+ value = value + 16777216 & 16777215;
206
+ }
207
+ setUint24(view, byteOffset, value, littleEndian);
208
+ };
209
+ var clamp = (value, min, max) => {
210
+ return Math.max(min, Math.min(max, value));
211
+ };
189
212
  var UNDETERMINED_LANGUAGE = "und";
190
213
  var roundToMultiple = (value, multiple) => {
191
214
  return Math.round(value / multiple) * multiple;
@@ -291,7 +314,7 @@ var polyfillSymbolDispose = () => {
291
314
  Symbol.dispose ??= Symbol("Symbol.dispose");
292
315
  };
293
316
 
294
- // ../../node_modules/.bun/mediabunny@1.25.3/node_modules/mediabunny/dist/modules/src/metadata.js
317
+ // ../../node_modules/.bun/mediabunny@1.25.8/node_modules/mediabunny/dist/modules/src/metadata.js
295
318
  /*!
296
319
  * Copyright (c) 2025-present, Vanilagy and contributors
297
320
  *
@@ -430,7 +453,7 @@ var validateTrackDisposition = (disposition) => {
430
453
  }
431
454
  };
432
455
 
433
- // ../../node_modules/.bun/mediabunny@1.25.3/node_modules/mediabunny/dist/modules/src/codec.js
456
+ // ../../node_modules/.bun/mediabunny@1.25.8/node_modules/mediabunny/dist/modules/src/codec.js
434
457
  /*!
435
458
  * Copyright (c) 2025-present, Vanilagy and contributors
436
459
  *
@@ -640,6 +663,109 @@ var generateAv1CodecConfigurationFromCodecString = (codecString) => {
640
663
  const fourthByte = initialPresentationDelayPresent;
641
664
  return [firstByte, secondByte, thirdByte, fourthByte];
642
665
  };
666
+ var buildAudioCodecString = (codec, numberOfChannels, sampleRate) => {
667
+ if (codec === "aac") {
668
+ if (numberOfChannels >= 2 && sampleRate <= 24000) {
669
+ return "mp4a.40.29";
670
+ }
671
+ if (sampleRate <= 24000) {
672
+ return "mp4a.40.5";
673
+ }
674
+ return "mp4a.40.2";
675
+ } else if (codec === "mp3") {
676
+ return "mp3";
677
+ } else if (codec === "opus") {
678
+ return "opus";
679
+ } else if (codec === "vorbis") {
680
+ return "vorbis";
681
+ } else if (codec === "flac") {
682
+ return "flac";
683
+ } else if (PCM_AUDIO_CODECS.includes(codec)) {
684
+ return codec;
685
+ }
686
+ throw new TypeError(`Unhandled codec '${codec}'.`);
687
+ };
688
+ var aacFrequencyTable = [
689
+ 96000,
690
+ 88200,
691
+ 64000,
692
+ 48000,
693
+ 44100,
694
+ 32000,
695
+ 24000,
696
+ 22050,
697
+ 16000,
698
+ 12000,
699
+ 11025,
700
+ 8000,
701
+ 7350
702
+ ];
703
+ var aacChannelMap = [-1, 1, 2, 3, 4, 5, 6, 8];
704
+ var parseAacAudioSpecificConfig = (bytes) => {
705
+ if (!bytes || bytes.byteLength < 2) {
706
+ throw new TypeError("AAC description must be at least 2 bytes long.");
707
+ }
708
+ const bitstream = new Bitstream(bytes);
709
+ let objectType = bitstream.readBits(5);
710
+ if (objectType === 31) {
711
+ objectType = 32 + bitstream.readBits(6);
712
+ }
713
+ const frequencyIndex = bitstream.readBits(4);
714
+ let sampleRate = null;
715
+ if (frequencyIndex === 15) {
716
+ sampleRate = bitstream.readBits(24);
717
+ } else {
718
+ if (frequencyIndex < aacFrequencyTable.length) {
719
+ sampleRate = aacFrequencyTable[frequencyIndex];
720
+ }
721
+ }
722
+ const channelConfiguration = bitstream.readBits(4);
723
+ let numberOfChannels = null;
724
+ if (channelConfiguration >= 1 && channelConfiguration <= 7) {
725
+ numberOfChannels = aacChannelMap[channelConfiguration];
726
+ }
727
+ return {
728
+ objectType,
729
+ frequencyIndex,
730
+ sampleRate,
731
+ channelConfiguration,
732
+ numberOfChannels
733
+ };
734
+ };
735
+ var buildAacAudioSpecificConfig = (config) => {
736
+ let frequencyIndex = aacFrequencyTable.indexOf(config.sampleRate);
737
+ let customSampleRate = null;
738
+ if (frequencyIndex === -1) {
739
+ frequencyIndex = 15;
740
+ customSampleRate = config.sampleRate;
741
+ }
742
+ const channelConfiguration = aacChannelMap.indexOf(config.numberOfChannels);
743
+ if (channelConfiguration === -1) {
744
+ throw new TypeError(`Unsupported number of channels: ${config.numberOfChannels}`);
745
+ }
746
+ let bitCount = 5 + 4 + 4;
747
+ if (config.objectType >= 32) {
748
+ bitCount += 6;
749
+ }
750
+ if (frequencyIndex === 15) {
751
+ bitCount += 24;
752
+ }
753
+ const byteCount = Math.ceil(bitCount / 8);
754
+ const bytes = new Uint8Array(byteCount);
755
+ const bitstream = new Bitstream(bytes);
756
+ if (config.objectType < 32) {
757
+ bitstream.writeBits(5, config.objectType);
758
+ } else {
759
+ bitstream.writeBits(5, 31);
760
+ bitstream.writeBits(6, config.objectType - 32);
761
+ }
762
+ bitstream.writeBits(4, frequencyIndex);
763
+ if (frequencyIndex === 15) {
764
+ bitstream.writeBits(24, customSampleRate);
765
+ }
766
+ bitstream.writeBits(4, channelConfiguration);
767
+ return bytes;
768
+ };
643
769
  var OPUS_SAMPLE_RATE = 48000;
644
770
  var PCM_CODEC_REGEX = /^pcm-([usf])(\d+)+(be)?$/;
645
771
  var parsePcmCodec = (codec) => {
@@ -714,6 +840,22 @@ var getVideoEncoderConfigExtension = (codec) => {
714
840
  }
715
841
  return {};
716
842
  };
843
+ var getAudioEncoderConfigExtension = (codec) => {
844
+ if (codec === "aac") {
845
+ return {
846
+ aac: {
847
+ format: "aac"
848
+ }
849
+ };
850
+ } else if (codec === "opus") {
851
+ return {
852
+ opus: {
853
+ format: "opus"
854
+ }
855
+ };
856
+ }
857
+ return {};
858
+ };
717
859
  var VALID_VIDEO_CODEC_STRING_PREFIXES = ["avc1", "avc3", "hev1", "hvc1", "vp8", "vp09", "av01"];
718
860
  var AVC_CODEC_STRING_REGEX = /^(avc1|avc3)\.[0-9a-fA-F]{6}$/;
719
861
  var HEVC_CODEC_STRING_REGEX = /^(hev1|hvc1)\.(?:[ABC]?\d+)\.[0-9a-fA-F]{1,8}\.[LH]\d+(?:\.[0-9a-fA-F]{1,2}){0,6}$/;
@@ -881,7 +1023,7 @@ var validateSubtitleMetadata = (metadata) => {
881
1023
  }
882
1024
  };
883
1025
 
884
- // ../../node_modules/.bun/mediabunny@1.25.3/node_modules/mediabunny/dist/modules/src/muxer.js
1026
+ // ../../node_modules/.bun/mediabunny@1.25.8/node_modules/mediabunny/dist/modules/src/muxer.js
885
1027
  /*!
886
1028
  * Copyright (c) 2025-present, Vanilagy and contributors
887
1029
  *
@@ -925,7 +1067,7 @@ class Muxer {
925
1067
  }
926
1068
  }
927
1069
 
928
- // ../../node_modules/.bun/mediabunny@1.25.3/node_modules/mediabunny/dist/modules/src/codec-data.js
1070
+ // ../../node_modules/.bun/mediabunny@1.25.8/node_modules/mediabunny/dist/modules/src/codec-data.js
929
1071
  /*!
930
1072
  * Copyright (c) 2025-present, Vanilagy and contributors
931
1073
  *
@@ -936,6 +1078,7 @@ class Muxer {
936
1078
  var AvcNalUnitType;
937
1079
  (function(AvcNalUnitType2) {
938
1080
  AvcNalUnitType2[AvcNalUnitType2["IDR"] = 5] = "IDR";
1081
+ AvcNalUnitType2[AvcNalUnitType2["SEI"] = 6] = "SEI";
939
1082
  AvcNalUnitType2[AvcNalUnitType2["SPS"] = 7] = "SPS";
940
1083
  AvcNalUnitType2[AvcNalUnitType2["PPS"] = 8] = "PPS";
941
1084
  AvcNalUnitType2[AvcNalUnitType2["SPS_EXT"] = 13] = "SPS_EXT";
@@ -1630,7 +1773,7 @@ var FlacBlockType;
1630
1773
  FlacBlockType2[FlacBlockType2["PICTURE"] = 6] = "PICTURE";
1631
1774
  })(FlacBlockType || (FlacBlockType = {}));
1632
1775
 
1633
- // ../../node_modules/.bun/mediabunny@1.25.3/node_modules/mediabunny/dist/modules/src/custom-coder.js
1776
+ // ../../node_modules/.bun/mediabunny@1.25.8/node_modules/mediabunny/dist/modules/src/custom-coder.js
1634
1777
  /*!
1635
1778
  * Copyright (c) 2025-present, Vanilagy and contributors
1636
1779
  *
@@ -1639,8 +1782,9 @@ var FlacBlockType;
1639
1782
  * file, You can obtain one at https://mozilla.org/MPL/2.0/.
1640
1783
  */
1641
1784
  var customVideoEncoders = [];
1785
+ var customAudioEncoders = [];
1642
1786
 
1643
- // ../../node_modules/.bun/mediabunny@1.25.3/node_modules/mediabunny/dist/modules/src/packet.js
1787
+ // ../../node_modules/.bun/mediabunny@1.25.8/node_modules/mediabunny/dist/modules/src/packet.js
1644
1788
  /*!
1645
1789
  * Copyright (c) 2025-present, Vanilagy and contributors
1646
1790
  *
@@ -1772,7 +1916,60 @@ class EncodedPacket {
1772
1916
  }
1773
1917
  }
1774
1918
 
1775
- // ../../node_modules/.bun/mediabunny@1.25.3/node_modules/mediabunny/dist/modules/src/sample.js
1919
+ // ../../node_modules/.bun/mediabunny@1.25.8/node_modules/mediabunny/dist/modules/src/pcm.js
1920
+ /*!
1921
+ * Copyright (c) 2025-present, Vanilagy and contributors
1922
+ *
1923
+ * This Source Code Form is subject to the terms of the Mozilla Public
1924
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
1925
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
1926
+ */
1927
+ var toUlaw = (s16) => {
1928
+ const MULAW_MAX = 8191;
1929
+ const MULAW_BIAS = 33;
1930
+ let number = s16;
1931
+ let mask = 4096;
1932
+ let sign = 0;
1933
+ let position = 12;
1934
+ let lsb = 0;
1935
+ if (number < 0) {
1936
+ number = -number;
1937
+ sign = 128;
1938
+ }
1939
+ number += MULAW_BIAS;
1940
+ if (number > MULAW_MAX) {
1941
+ number = MULAW_MAX;
1942
+ }
1943
+ while ((number & mask) !== mask && position >= 5) {
1944
+ mask >>= 1;
1945
+ position--;
1946
+ }
1947
+ lsb = number >> position - 4 & 15;
1948
+ return ~(sign | position - 5 << 4 | lsb) & 255;
1949
+ };
1950
+ var toAlaw = (s16) => {
1951
+ const ALAW_MAX = 4095;
1952
+ let mask = 2048;
1953
+ let sign = 0;
1954
+ let position = 11;
1955
+ let lsb = 0;
1956
+ let number = s16;
1957
+ if (number < 0) {
1958
+ number = -number;
1959
+ sign = 128;
1960
+ }
1961
+ if (number > ALAW_MAX) {
1962
+ number = ALAW_MAX;
1963
+ }
1964
+ while ((number & mask) !== mask && position >= 5) {
1965
+ mask >>= 1;
1966
+ position--;
1967
+ }
1968
+ lsb = number >> (position === 4 ? 1 : position - 4) & 15;
1969
+ return (sign | position - 4 << 4 | lsb) ^ 85;
1970
+ };
1971
+
1972
+ // ../../node_modules/.bun/mediabunny@1.25.8/node_modules/mediabunny/dist/modules/src/sample.js
1776
1973
  /*!
1777
1974
  * Copyright (c) 2025-present, Vanilagy and contributors
1778
1975
  *
@@ -1781,6 +1978,31 @@ class EncodedPacket {
1781
1978
  * file, You can obtain one at https://mozilla.org/MPL/2.0/.
1782
1979
  */
1783
1980
  polyfillSymbolDispose();
1981
+ var lastVideoGcErrorLog = -Infinity;
1982
+ var lastAudioGcErrorLog = -Infinity;
1983
+ var finalizationRegistry = null;
1984
+ if (typeof FinalizationRegistry !== "undefined") {
1985
+ finalizationRegistry = new FinalizationRegistry((value) => {
1986
+ const now = Date.now();
1987
+ if (value.type === "video") {
1988
+ if (now - lastVideoGcErrorLog >= 1000) {
1989
+ console.error(`A VideoSample was garbage collected without first being closed. For proper resource management,` + ` make sure to call close() on all your VideoSamples as soon as you're done using them.`);
1990
+ lastVideoGcErrorLog = now;
1991
+ }
1992
+ if (typeof VideoFrame !== "undefined" && value.data instanceof VideoFrame) {
1993
+ value.data.close();
1994
+ }
1995
+ } else {
1996
+ if (now - lastAudioGcErrorLog >= 1000) {
1997
+ console.error(`An AudioSample was garbage collected without first being closed. For proper resource management,` + ` make sure to call close() on all your AudioSamples as soon as you're done using them.`);
1998
+ lastAudioGcErrorLog = now;
1999
+ }
2000
+ if (typeof AudioData !== "undefined" && value.data instanceof AudioData) {
2001
+ value.data.close();
2002
+ }
2003
+ }
2004
+ });
2005
+ }
1784
2006
 
1785
2007
  class VideoSample {
1786
2008
  get displayWidth() {
@@ -1800,7 +2022,7 @@ class VideoSample {
1800
2022
  }
1801
2023
  constructor(data, init) {
1802
2024
  this._closed = false;
1803
- if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {
2025
+ if (data instanceof ArrayBuffer || typeof SharedArrayBuffer !== "undefined" && data instanceof SharedArrayBuffer || ArrayBuffer.isView(data)) {
1804
2026
  if (!init || typeof init !== "object") {
1805
2027
  throw new TypeError("init must be an object.");
1806
2028
  }
@@ -1905,6 +2127,7 @@ class VideoSample {
1905
2127
  } else {
1906
2128
  throw new TypeError("Invalid data type: Must be a BufferSource or CanvasImageSource.");
1907
2129
  }
2130
+ finalizationRegistry?.register(this, { type: "video", data: this._data }, this);
1908
2131
  }
1909
2132
  clone() {
1910
2133
  if (this._closed) {
@@ -1943,6 +2166,7 @@ class VideoSample {
1943
2166
  if (this._closed) {
1944
2167
  return;
1945
2168
  }
2169
+ finalizationRegistry?.unregister(this);
1946
2170
  if (isVideoFrame(this._data)) {
1947
2171
  this._data.close();
1948
2172
  } else {
@@ -2222,7 +2446,465 @@ var validateCropRectangle = (crop, prefix) => {
2222
2446
  };
2223
2447
  var AUDIO_SAMPLE_FORMATS = new Set(["f32", "f32-planar", "s16", "s16-planar", "s32", "s32-planar", "u8", "u8-planar"]);
2224
2448
 
2225
- // ../../node_modules/.bun/mediabunny@1.25.3/node_modules/mediabunny/dist/modules/src/isobmff/isobmff-misc.js
2449
+ class AudioSample {
2450
+ get microsecondTimestamp() {
2451
+ return Math.trunc(SECOND_TO_MICROSECOND_FACTOR * this.timestamp);
2452
+ }
2453
+ get microsecondDuration() {
2454
+ return Math.trunc(SECOND_TO_MICROSECOND_FACTOR * this.duration);
2455
+ }
2456
+ constructor(init) {
2457
+ this._closed = false;
2458
+ if (isAudioData(init)) {
2459
+ if (init.format === null) {
2460
+ throw new TypeError("AudioData with null format is not supported.");
2461
+ }
2462
+ this._data = init;
2463
+ this.format = init.format;
2464
+ this.sampleRate = init.sampleRate;
2465
+ this.numberOfFrames = init.numberOfFrames;
2466
+ this.numberOfChannels = init.numberOfChannels;
2467
+ this.timestamp = init.timestamp / 1e6;
2468
+ this.duration = init.numberOfFrames / init.sampleRate;
2469
+ } else {
2470
+ if (!init || typeof init !== "object") {
2471
+ throw new TypeError("Invalid AudioDataInit: must be an object.");
2472
+ }
2473
+ if (!AUDIO_SAMPLE_FORMATS.has(init.format)) {
2474
+ throw new TypeError("Invalid AudioDataInit: invalid format.");
2475
+ }
2476
+ if (!Number.isFinite(init.sampleRate) || init.sampleRate <= 0) {
2477
+ throw new TypeError("Invalid AudioDataInit: sampleRate must be > 0.");
2478
+ }
2479
+ if (!Number.isInteger(init.numberOfChannels) || init.numberOfChannels === 0) {
2480
+ throw new TypeError("Invalid AudioDataInit: numberOfChannels must be an integer > 0.");
2481
+ }
2482
+ if (!Number.isFinite(init?.timestamp)) {
2483
+ throw new TypeError("init.timestamp must be a number.");
2484
+ }
2485
+ const numberOfFrames = init.data.byteLength / (getBytesPerSample(init.format) * init.numberOfChannels);
2486
+ if (!Number.isInteger(numberOfFrames)) {
2487
+ throw new TypeError("Invalid AudioDataInit: data size is not a multiple of frame size.");
2488
+ }
2489
+ this.format = init.format;
2490
+ this.sampleRate = init.sampleRate;
2491
+ this.numberOfFrames = numberOfFrames;
2492
+ this.numberOfChannels = init.numberOfChannels;
2493
+ this.timestamp = init.timestamp;
2494
+ this.duration = numberOfFrames / init.sampleRate;
2495
+ let dataBuffer;
2496
+ if (init.data instanceof ArrayBuffer) {
2497
+ dataBuffer = new Uint8Array(init.data);
2498
+ } else if (ArrayBuffer.isView(init.data)) {
2499
+ dataBuffer = new Uint8Array(init.data.buffer, init.data.byteOffset, init.data.byteLength);
2500
+ } else {
2501
+ throw new TypeError("Invalid AudioDataInit: data is not a BufferSource.");
2502
+ }
2503
+ const expectedSize = this.numberOfFrames * this.numberOfChannels * getBytesPerSample(this.format);
2504
+ if (dataBuffer.byteLength < expectedSize) {
2505
+ throw new TypeError("Invalid AudioDataInit: insufficient data size.");
2506
+ }
2507
+ this._data = dataBuffer;
2508
+ }
2509
+ finalizationRegistry?.register(this, { type: "audio", data: this._data }, this);
2510
+ }
2511
+ allocationSize(options) {
2512
+ if (!options || typeof options !== "object") {
2513
+ throw new TypeError("options must be an object.");
2514
+ }
2515
+ if (!Number.isInteger(options.planeIndex) || options.planeIndex < 0) {
2516
+ throw new TypeError("planeIndex must be a non-negative integer.");
2517
+ }
2518
+ if (options.format !== undefined && !AUDIO_SAMPLE_FORMATS.has(options.format)) {
2519
+ throw new TypeError("Invalid format.");
2520
+ }
2521
+ if (options.frameOffset !== undefined && (!Number.isInteger(options.frameOffset) || options.frameOffset < 0)) {
2522
+ throw new TypeError("frameOffset must be a non-negative integer.");
2523
+ }
2524
+ if (options.frameCount !== undefined && (!Number.isInteger(options.frameCount) || options.frameCount < 0)) {
2525
+ throw new TypeError("frameCount must be a non-negative integer.");
2526
+ }
2527
+ if (this._closed) {
2528
+ throw new Error("AudioSample is closed.");
2529
+ }
2530
+ const destFormat = options.format ?? this.format;
2531
+ const frameOffset = options.frameOffset ?? 0;
2532
+ if (frameOffset >= this.numberOfFrames) {
2533
+ throw new RangeError("frameOffset out of range");
2534
+ }
2535
+ const copyFrameCount = options.frameCount !== undefined ? options.frameCount : this.numberOfFrames - frameOffset;
2536
+ if (copyFrameCount > this.numberOfFrames - frameOffset) {
2537
+ throw new RangeError("frameCount out of range");
2538
+ }
2539
+ const bytesPerSample = getBytesPerSample(destFormat);
2540
+ const isPlanar = formatIsPlanar(destFormat);
2541
+ if (isPlanar && options.planeIndex >= this.numberOfChannels) {
2542
+ throw new RangeError("planeIndex out of range");
2543
+ }
2544
+ if (!isPlanar && options.planeIndex !== 0) {
2545
+ throw new RangeError("planeIndex out of range");
2546
+ }
2547
+ const elementCount = isPlanar ? copyFrameCount : copyFrameCount * this.numberOfChannels;
2548
+ return elementCount * bytesPerSample;
2549
+ }
2550
+ copyTo(destination, options) {
2551
+ if (!isAllowSharedBufferSource(destination)) {
2552
+ throw new TypeError("destination must be an ArrayBuffer or an ArrayBuffer view.");
2553
+ }
2554
+ if (!options || typeof options !== "object") {
2555
+ throw new TypeError("options must be an object.");
2556
+ }
2557
+ if (!Number.isInteger(options.planeIndex) || options.planeIndex < 0) {
2558
+ throw new TypeError("planeIndex must be a non-negative integer.");
2559
+ }
2560
+ if (options.format !== undefined && !AUDIO_SAMPLE_FORMATS.has(options.format)) {
2561
+ throw new TypeError("Invalid format.");
2562
+ }
2563
+ if (options.frameOffset !== undefined && (!Number.isInteger(options.frameOffset) || options.frameOffset < 0)) {
2564
+ throw new TypeError("frameOffset must be a non-negative integer.");
2565
+ }
2566
+ if (options.frameCount !== undefined && (!Number.isInteger(options.frameCount) || options.frameCount < 0)) {
2567
+ throw new TypeError("frameCount must be a non-negative integer.");
2568
+ }
2569
+ if (this._closed) {
2570
+ throw new Error("AudioSample is closed.");
2571
+ }
2572
+ const { planeIndex, format, frameCount: optFrameCount, frameOffset: optFrameOffset } = options;
2573
+ const destFormat = format ?? this.format;
2574
+ if (!destFormat)
2575
+ throw new Error("Destination format not determined");
2576
+ const numFrames = this.numberOfFrames;
2577
+ const numChannels = this.numberOfChannels;
2578
+ const frameOffset = optFrameOffset ?? 0;
2579
+ if (frameOffset >= numFrames) {
2580
+ throw new RangeError("frameOffset out of range");
2581
+ }
2582
+ const copyFrameCount = optFrameCount !== undefined ? optFrameCount : numFrames - frameOffset;
2583
+ if (copyFrameCount > numFrames - frameOffset) {
2584
+ throw new RangeError("frameCount out of range");
2585
+ }
2586
+ const destBytesPerSample = getBytesPerSample(destFormat);
2587
+ const destIsPlanar = formatIsPlanar(destFormat);
2588
+ if (destIsPlanar && planeIndex >= numChannels) {
2589
+ throw new RangeError("planeIndex out of range");
2590
+ }
2591
+ if (!destIsPlanar && planeIndex !== 0) {
2592
+ throw new RangeError("planeIndex out of range");
2593
+ }
2594
+ const destElementCount = destIsPlanar ? copyFrameCount : copyFrameCount * numChannels;
2595
+ const requiredSize = destElementCount * destBytesPerSample;
2596
+ if (destination.byteLength < requiredSize) {
2597
+ throw new RangeError("Destination buffer is too small");
2598
+ }
2599
+ const destView = toDataView(destination);
2600
+ const writeFn = getWriteFunction(destFormat);
2601
+ if (isAudioData(this._data)) {
2602
+ if (destIsPlanar) {
2603
+ if (destFormat === "f32-planar") {
2604
+ this._data.copyTo(destination, {
2605
+ planeIndex,
2606
+ frameOffset,
2607
+ frameCount: copyFrameCount,
2608
+ format: "f32-planar"
2609
+ });
2610
+ } else {
2611
+ const tempBuffer = new ArrayBuffer(copyFrameCount * 4);
2612
+ const tempArray = new Float32Array(tempBuffer);
2613
+ this._data.copyTo(tempArray, {
2614
+ planeIndex,
2615
+ frameOffset,
2616
+ frameCount: copyFrameCount,
2617
+ format: "f32-planar"
2618
+ });
2619
+ const tempView = new DataView(tempBuffer);
2620
+ for (let i = 0;i < copyFrameCount; i++) {
2621
+ const destOffset = i * destBytesPerSample;
2622
+ const sample = tempView.getFloat32(i * 4, true);
2623
+ writeFn(destView, destOffset, sample);
2624
+ }
2625
+ }
2626
+ } else {
2627
+ const numCh = numChannels;
2628
+ const temp = new Float32Array(copyFrameCount);
2629
+ for (let ch = 0;ch < numCh; ch++) {
2630
+ this._data.copyTo(temp, {
2631
+ planeIndex: ch,
2632
+ frameOffset,
2633
+ frameCount: copyFrameCount,
2634
+ format: "f32-planar"
2635
+ });
2636
+ for (let i = 0;i < copyFrameCount; i++) {
2637
+ const destIndex = i * numCh + ch;
2638
+ const destOffset = destIndex * destBytesPerSample;
2639
+ writeFn(destView, destOffset, temp[i]);
2640
+ }
2641
+ }
2642
+ }
2643
+ } else {
2644
+ const uint8Data = this._data;
2645
+ const srcView = toDataView(uint8Data);
2646
+ const srcFormat = this.format;
2647
+ const readFn = getReadFunction(srcFormat);
2648
+ const srcBytesPerSample = getBytesPerSample(srcFormat);
2649
+ const srcIsPlanar = formatIsPlanar(srcFormat);
2650
+ for (let i = 0;i < copyFrameCount; i++) {
2651
+ if (destIsPlanar) {
2652
+ const destOffset = i * destBytesPerSample;
2653
+ let srcOffset;
2654
+ if (srcIsPlanar) {
2655
+ srcOffset = (planeIndex * numFrames + (i + frameOffset)) * srcBytesPerSample;
2656
+ } else {
2657
+ srcOffset = ((i + frameOffset) * numChannels + planeIndex) * srcBytesPerSample;
2658
+ }
2659
+ const normalized = readFn(srcView, srcOffset);
2660
+ writeFn(destView, destOffset, normalized);
2661
+ } else {
2662
+ for (let ch = 0;ch < numChannels; ch++) {
2663
+ const destIndex = i * numChannels + ch;
2664
+ const destOffset = destIndex * destBytesPerSample;
2665
+ let srcOffset;
2666
+ if (srcIsPlanar) {
2667
+ srcOffset = (ch * numFrames + (i + frameOffset)) * srcBytesPerSample;
2668
+ } else {
2669
+ srcOffset = ((i + frameOffset) * numChannels + ch) * srcBytesPerSample;
2670
+ }
2671
+ const normalized = readFn(srcView, srcOffset);
2672
+ writeFn(destView, destOffset, normalized);
2673
+ }
2674
+ }
2675
+ }
2676
+ }
2677
+ }
2678
+ clone() {
2679
+ if (this._closed) {
2680
+ throw new Error("AudioSample is closed.");
2681
+ }
2682
+ if (isAudioData(this._data)) {
2683
+ const sample = new AudioSample(this._data.clone());
2684
+ sample.setTimestamp(this.timestamp);
2685
+ return sample;
2686
+ } else {
2687
+ return new AudioSample({
2688
+ format: this.format,
2689
+ sampleRate: this.sampleRate,
2690
+ numberOfFrames: this.numberOfFrames,
2691
+ numberOfChannels: this.numberOfChannels,
2692
+ timestamp: this.timestamp,
2693
+ data: this._data
2694
+ });
2695
+ }
2696
+ }
2697
+ close() {
2698
+ if (this._closed) {
2699
+ return;
2700
+ }
2701
+ finalizationRegistry?.unregister(this);
2702
+ if (isAudioData(this._data)) {
2703
+ this._data.close();
2704
+ } else {
2705
+ this._data = new Uint8Array(0);
2706
+ }
2707
+ this._closed = true;
2708
+ }
2709
+ toAudioData() {
2710
+ if (this._closed) {
2711
+ throw new Error("AudioSample is closed.");
2712
+ }
2713
+ if (isAudioData(this._data)) {
2714
+ if (this._data.timestamp === this.microsecondTimestamp) {
2715
+ return this._data.clone();
2716
+ } else {
2717
+ if (formatIsPlanar(this.format)) {
2718
+ const size = this.allocationSize({ planeIndex: 0, format: this.format });
2719
+ const data = new ArrayBuffer(size * this.numberOfChannels);
2720
+ for (let i = 0;i < this.numberOfChannels; i++) {
2721
+ this.copyTo(new Uint8Array(data, i * size, size), { planeIndex: i, format: this.format });
2722
+ }
2723
+ return new AudioData({
2724
+ format: this.format,
2725
+ sampleRate: this.sampleRate,
2726
+ numberOfFrames: this.numberOfFrames,
2727
+ numberOfChannels: this.numberOfChannels,
2728
+ timestamp: this.microsecondTimestamp,
2729
+ data
2730
+ });
2731
+ } else {
2732
+ const data = new ArrayBuffer(this.allocationSize({ planeIndex: 0, format: this.format }));
2733
+ this.copyTo(data, { planeIndex: 0, format: this.format });
2734
+ return new AudioData({
2735
+ format: this.format,
2736
+ sampleRate: this.sampleRate,
2737
+ numberOfFrames: this.numberOfFrames,
2738
+ numberOfChannels: this.numberOfChannels,
2739
+ timestamp: this.microsecondTimestamp,
2740
+ data
2741
+ });
2742
+ }
2743
+ }
2744
+ } else {
2745
+ return new AudioData({
2746
+ format: this.format,
2747
+ sampleRate: this.sampleRate,
2748
+ numberOfFrames: this.numberOfFrames,
2749
+ numberOfChannels: this.numberOfChannels,
2750
+ timestamp: this.microsecondTimestamp,
2751
+ data: this._data.buffer instanceof ArrayBuffer ? this._data.buffer : this._data.slice()
2752
+ });
2753
+ }
2754
+ }
2755
+ toAudioBuffer() {
2756
+ if (this._closed) {
2757
+ throw new Error("AudioSample is closed.");
2758
+ }
2759
+ const audioBuffer = new AudioBuffer({
2760
+ numberOfChannels: this.numberOfChannels,
2761
+ length: this.numberOfFrames,
2762
+ sampleRate: this.sampleRate
2763
+ });
2764
+ const dataBytes = new Float32Array(this.allocationSize({ planeIndex: 0, format: "f32-planar" }) / 4);
2765
+ for (let i = 0;i < this.numberOfChannels; i++) {
2766
+ this.copyTo(dataBytes, { planeIndex: i, format: "f32-planar" });
2767
+ audioBuffer.copyToChannel(dataBytes, i);
2768
+ }
2769
+ return audioBuffer;
2770
+ }
2771
+ setTimestamp(newTimestamp) {
2772
+ if (!Number.isFinite(newTimestamp)) {
2773
+ throw new TypeError("newTimestamp must be a number.");
2774
+ }
2775
+ this.timestamp = newTimestamp;
2776
+ }
2777
+ [Symbol.dispose]() {
2778
+ this.close();
2779
+ }
2780
+ static *_fromAudioBuffer(audioBuffer, timestamp) {
2781
+ if (!(audioBuffer instanceof AudioBuffer)) {
2782
+ throw new TypeError("audioBuffer must be an AudioBuffer.");
2783
+ }
2784
+ const MAX_FLOAT_COUNT = 48000 * 5;
2785
+ const numberOfChannels = audioBuffer.numberOfChannels;
2786
+ const sampleRate = audioBuffer.sampleRate;
2787
+ const totalFrames = audioBuffer.length;
2788
+ const maxFramesPerChunk = Math.floor(MAX_FLOAT_COUNT / numberOfChannels);
2789
+ let currentRelativeFrame = 0;
2790
+ let remainingFrames = totalFrames;
2791
+ while (remainingFrames > 0) {
2792
+ const framesToCopy = Math.min(maxFramesPerChunk, remainingFrames);
2793
+ const chunkData = new Float32Array(numberOfChannels * framesToCopy);
2794
+ for (let channel = 0;channel < numberOfChannels; channel++) {
2795
+ audioBuffer.copyFromChannel(chunkData.subarray(channel * framesToCopy, (channel + 1) * framesToCopy), channel, currentRelativeFrame);
2796
+ }
2797
+ yield new AudioSample({
2798
+ format: "f32-planar",
2799
+ sampleRate,
2800
+ numberOfFrames: framesToCopy,
2801
+ numberOfChannels,
2802
+ timestamp: timestamp + currentRelativeFrame / sampleRate,
2803
+ data: chunkData
2804
+ });
2805
+ currentRelativeFrame += framesToCopy;
2806
+ remainingFrames -= framesToCopy;
2807
+ }
2808
+ }
2809
+ static fromAudioBuffer(audioBuffer, timestamp) {
2810
+ if (!(audioBuffer instanceof AudioBuffer)) {
2811
+ throw new TypeError("audioBuffer must be an AudioBuffer.");
2812
+ }
2813
+ const MAX_FLOAT_COUNT = 48000 * 5;
2814
+ const numberOfChannels = audioBuffer.numberOfChannels;
2815
+ const sampleRate = audioBuffer.sampleRate;
2816
+ const totalFrames = audioBuffer.length;
2817
+ const maxFramesPerChunk = Math.floor(MAX_FLOAT_COUNT / numberOfChannels);
2818
+ let currentRelativeFrame = 0;
2819
+ let remainingFrames = totalFrames;
2820
+ const result = [];
2821
+ while (remainingFrames > 0) {
2822
+ const framesToCopy = Math.min(maxFramesPerChunk, remainingFrames);
2823
+ const chunkData = new Float32Array(numberOfChannels * framesToCopy);
2824
+ for (let channel = 0;channel < numberOfChannels; channel++) {
2825
+ audioBuffer.copyFromChannel(chunkData.subarray(channel * framesToCopy, (channel + 1) * framesToCopy), channel, currentRelativeFrame);
2826
+ }
2827
+ const audioSample = new AudioSample({
2828
+ format: "f32-planar",
2829
+ sampleRate,
2830
+ numberOfFrames: framesToCopy,
2831
+ numberOfChannels,
2832
+ timestamp: timestamp + currentRelativeFrame / sampleRate,
2833
+ data: chunkData
2834
+ });
2835
+ result.push(audioSample);
2836
+ currentRelativeFrame += framesToCopy;
2837
+ remainingFrames -= framesToCopy;
2838
+ }
2839
+ return result;
2840
+ }
2841
+ }
2842
+ var getBytesPerSample = (format) => {
2843
+ switch (format) {
2844
+ case "u8":
2845
+ case "u8-planar":
2846
+ return 1;
2847
+ case "s16":
2848
+ case "s16-planar":
2849
+ return 2;
2850
+ case "s32":
2851
+ case "s32-planar":
2852
+ return 4;
2853
+ case "f32":
2854
+ case "f32-planar":
2855
+ return 4;
2856
+ default:
2857
+ throw new Error("Unknown AudioSampleFormat");
2858
+ }
2859
+ };
2860
+ var formatIsPlanar = (format) => {
2861
+ switch (format) {
2862
+ case "u8-planar":
2863
+ case "s16-planar":
2864
+ case "s32-planar":
2865
+ case "f32-planar":
2866
+ return true;
2867
+ default:
2868
+ return false;
2869
+ }
2870
+ };
2871
+ var getReadFunction = (format) => {
2872
+ switch (format) {
2873
+ case "u8":
2874
+ case "u8-planar":
2875
+ return (view, offset) => (view.getUint8(offset) - 128) / 128;
2876
+ case "s16":
2877
+ case "s16-planar":
2878
+ return (view, offset) => view.getInt16(offset, true) / 32768;
2879
+ case "s32":
2880
+ case "s32-planar":
2881
+ return (view, offset) => view.getInt32(offset, true) / 2147483648;
2882
+ case "f32":
2883
+ case "f32-planar":
2884
+ return (view, offset) => view.getFloat32(offset, true);
2885
+ }
2886
+ };
2887
+ var getWriteFunction = (format) => {
2888
+ switch (format) {
2889
+ case "u8":
2890
+ case "u8-planar":
2891
+ return (view, offset, value) => view.setUint8(offset, clamp((value + 1) * 127.5, 0, 255));
2892
+ case "s16":
2893
+ case "s16-planar":
2894
+ return (view, offset, value) => view.setInt16(offset, clamp(Math.round(value * 32767), -32768, 32767), true);
2895
+ case "s32":
2896
+ case "s32-planar":
2897
+ return (view, offset, value) => view.setInt32(offset, clamp(Math.round(value * 2147483647), -2147483648, 2147483647), true);
2898
+ case "f32":
2899
+ case "f32-planar":
2900
+ return (view, offset, value) => view.setFloat32(offset, value, true);
2901
+ }
2902
+ };
2903
+ var isAudioData = (x) => {
2904
+ return typeof AudioData !== "undefined" && x instanceof AudioData;
2905
+ };
2906
+
2907
+ // ../../node_modules/.bun/mediabunny@1.25.8/node_modules/mediabunny/dist/modules/src/isobmff/isobmff-misc.js
2226
2908
  /*!
2227
2909
  * Copyright (c) 2025-present, Vanilagy and contributors
2228
2910
  *
@@ -2240,7 +2922,7 @@ var buildIsobmffMimeType = (info) => {
2240
2922
  return string;
2241
2923
  };
2242
2924
 
2243
- // ../../node_modules/.bun/mediabunny@1.25.3/node_modules/mediabunny/dist/modules/src/isobmff/isobmff-reader.js
2925
+ // ../../node_modules/.bun/mediabunny@1.25.8/node_modules/mediabunny/dist/modules/src/isobmff/isobmff-reader.js
2244
2926
  /*!
2245
2927
  * Copyright (c) 2025-present, Vanilagy and contributors
2246
2928
  *
@@ -2251,7 +2933,7 @@ var buildIsobmffMimeType = (info) => {
2251
2933
  var MIN_BOX_HEADER_SIZE = 8;
2252
2934
  var MAX_BOX_HEADER_SIZE = 16;
2253
2935
 
2254
- // ../../node_modules/.bun/mediabunny@1.25.3/node_modules/mediabunny/dist/modules/src/matroska/ebml.js
2936
+ // ../../node_modules/.bun/mediabunny@1.25.8/node_modules/mediabunny/dist/modules/src/matroska/ebml.js
2255
2937
  /*!
2256
2938
  * Copyright (c) 2025-present, Vanilagy and contributors
2257
2939
  *
@@ -2661,7 +3343,7 @@ var CODEC_STRING_MAP = {
2661
3343
  webvtt: "S_TEXT/WEBVTT"
2662
3344
  };
2663
3345
 
2664
- // ../../node_modules/.bun/mediabunny@1.25.3/node_modules/mediabunny/dist/modules/src/matroska/matroska-misc.js
3346
+ // ../../node_modules/.bun/mediabunny@1.25.8/node_modules/mediabunny/dist/modules/src/matroska/matroska-misc.js
2665
3347
  /*!
2666
3348
  * Copyright (c) 2025-present, Vanilagy and contributors
2667
3349
  *
@@ -2679,7 +3361,7 @@ var buildMatroskaMimeType = (info) => {
2679
3361
  return string;
2680
3362
  };
2681
3363
 
2682
- // ../../node_modules/.bun/mediabunny@1.25.3/node_modules/mediabunny/dist/modules/src/subtitles.js
3364
+ // ../../node_modules/.bun/mediabunny@1.25.8/node_modules/mediabunny/dist/modules/src/subtitles.js
2683
3365
  /*!
2684
3366
  * Copyright (c) 2025-present, Vanilagy and contributors
2685
3367
  *
@@ -2703,7 +3385,7 @@ var formatSubtitleTimestamp = (timestamp) => {
2703
3385
  return hours.toString().padStart(2, "0") + ":" + minutes.toString().padStart(2, "0") + ":" + seconds.toString().padStart(2, "0") + "." + milliseconds.toString().padStart(3, "0");
2704
3386
  };
2705
3387
 
2706
- // ../../node_modules/.bun/mediabunny@1.25.3/node_modules/mediabunny/dist/modules/src/isobmff/isobmff-boxes.js
3388
+ // ../../node_modules/.bun/mediabunny@1.25.8/node_modules/mediabunny/dist/modules/src/isobmff/isobmff-boxes.js
2707
3389
  /*!
2708
3390
  * Copyright (c) 2025-present, Vanilagy and contributors
2709
3391
  *
@@ -4037,7 +4719,7 @@ var getLanguageCodeInt = (code) => {
4037
4719
  return language;
4038
4720
  };
4039
4721
 
4040
- // ../../node_modules/.bun/mediabunny@1.25.3/node_modules/mediabunny/dist/modules/src/writer.js
4722
+ // ../../node_modules/.bun/mediabunny@1.25.8/node_modules/mediabunny/dist/modules/src/writer.js
4041
4723
  /*!
4042
4724
  * Copyright (c) 2025-present, Vanilagy and contributors
4043
4725
  *
@@ -4165,7 +4847,7 @@ class BufferTargetWriter extends Writer {
4165
4847
  }
4166
4848
  var DEFAULT_CHUNK_SIZE = 2 ** 24;
4167
4849
 
4168
- // ../../node_modules/.bun/mediabunny@1.25.3/node_modules/mediabunny/dist/modules/src/target.js
4850
+ // ../../node_modules/.bun/mediabunny@1.25.8/node_modules/mediabunny/dist/modules/src/target.js
4169
4851
  var nodeAlias = (() => ({}));
4170
4852
  /*!
4171
4853
  * Copyright (c) 2025-present, Vanilagy and contributors
@@ -4191,7 +4873,7 @@ class BufferTarget extends Target {
4191
4873
  }
4192
4874
  }
4193
4875
 
4194
- // ../../node_modules/.bun/mediabunny@1.25.3/node_modules/mediabunny/dist/modules/src/isobmff/isobmff-muxer.js
4876
+ // ../../node_modules/.bun/mediabunny@1.25.8/node_modules/mediabunny/dist/modules/src/isobmff/isobmff-muxer.js
4195
4877
  /*!
4196
4878
  * Copyright (c) 2025-present, Vanilagy and contributors
4197
4879
  *
@@ -5018,7 +5700,7 @@ class IsobmffMuxer extends Muxer {
5018
5700
  }
5019
5701
  }
5020
5702
 
5021
- // ../../node_modules/.bun/mediabunny@1.25.3/node_modules/mediabunny/dist/modules/src/matroska/matroska-muxer.js
5703
+ // ../../node_modules/.bun/mediabunny@1.25.8/node_modules/mediabunny/dist/modules/src/matroska/matroska-muxer.js
5022
5704
  /*!
5023
5705
  * Copyright (c) 2025-present, Vanilagy and contributors
5024
5706
  *
@@ -5901,7 +6583,7 @@ ${cue.notes ?? ""}`;
5901
6583
  }
5902
6584
  }
5903
6585
 
5904
- // ../../node_modules/.bun/mediabunny@1.25.3/node_modules/mediabunny/dist/modules/src/output-format.js
6586
+ // ../../node_modules/.bun/mediabunny@1.25.8/node_modules/mediabunny/dist/modules/src/output-format.js
5905
6587
  /*!
5906
6588
  * Copyright (c) 2025-present, Vanilagy and contributors
5907
6589
  *
@@ -6119,7 +6801,7 @@ class WebMOutputFormat extends MkvOutputFormat {
6119
6801
  }
6120
6802
  }
6121
6803
 
6122
- // ../../node_modules/.bun/mediabunny@1.25.3/node_modules/mediabunny/dist/modules/src/encode.js
6804
+ // ../../node_modules/.bun/mediabunny@1.25.8/node_modules/mediabunny/dist/modules/src/encode.js
6123
6805
  /*!
6124
6806
  * Copyright (c) 2025-present, Vanilagy and contributors
6125
6807
  *
@@ -6197,6 +6879,53 @@ var buildVideoEncoderConfig = (options) => {
6197
6879
  ...getVideoEncoderConfigExtension(options.codec)
6198
6880
  };
6199
6881
  };
6882
+ var validateAudioEncodingConfig = (config) => {
6883
+ if (!config || typeof config !== "object") {
6884
+ throw new TypeError("Encoding config must be an object.");
6885
+ }
6886
+ if (!AUDIO_CODECS.includes(config.codec)) {
6887
+ throw new TypeError(`Invalid audio codec '${config.codec}'. Must be one of: ${AUDIO_CODECS.join(", ")}.`);
6888
+ }
6889
+ if (config.bitrate === undefined && (!PCM_AUDIO_CODECS.includes(config.codec) || config.codec === "flac")) {
6890
+ throw new TypeError("config.bitrate must be provided for compressed audio codecs.");
6891
+ }
6892
+ if (config.bitrate !== undefined && !(config.bitrate instanceof Quality) && (!Number.isInteger(config.bitrate) || config.bitrate <= 0)) {
6893
+ throw new TypeError("config.bitrate, when provided, must be a positive integer or a quality.");
6894
+ }
6895
+ if (config.onEncodedPacket !== undefined && typeof config.onEncodedPacket !== "function") {
6896
+ throw new TypeError("config.onEncodedChunk, when provided, must be a function.");
6897
+ }
6898
+ if (config.onEncoderConfig !== undefined && typeof config.onEncoderConfig !== "function") {
6899
+ throw new TypeError("config.onEncoderConfig, when provided, must be a function.");
6900
+ }
6901
+ validateAudioEncodingAdditionalOptions(config.codec, config);
6902
+ };
6903
+ var validateAudioEncodingAdditionalOptions = (codec, options) => {
6904
+ if (!options || typeof options !== "object") {
6905
+ throw new TypeError("Encoding options must be an object.");
6906
+ }
6907
+ if (options.bitrateMode !== undefined && !["constant", "variable"].includes(options.bitrateMode)) {
6908
+ throw new TypeError("bitrateMode, when provided, must be 'constant' or 'variable'.");
6909
+ }
6910
+ if (options.fullCodecString !== undefined && typeof options.fullCodecString !== "string") {
6911
+ throw new TypeError("fullCodecString, when provided, must be a string.");
6912
+ }
6913
+ if (options.fullCodecString !== undefined && inferCodecFromCodecString(options.fullCodecString) !== codec) {
6914
+ throw new TypeError(`fullCodecString, when provided, must be a string that matches the specified codec (${codec}).`);
6915
+ }
6916
+ };
6917
+ var buildAudioEncoderConfig = (options) => {
6918
+ const resolvedBitrate = options.bitrate instanceof Quality ? options.bitrate._toAudioBitrate(options.codec) : options.bitrate;
6919
+ return {
6920
+ codec: options.fullCodecString ?? buildAudioCodecString(options.codec, options.numberOfChannels, options.sampleRate),
6921
+ numberOfChannels: options.numberOfChannels,
6922
+ sampleRate: options.sampleRate,
6923
+ bitrate: resolvedBitrate,
6924
+ bitrateMode: options.bitrateMode,
6925
+ ...getAudioEncoderConfigExtension(options.codec)
6926
+ };
6927
+ };
6928
+
6200
6929
  class Quality {
6201
6930
  constructor(factor) {
6202
6931
  this._factor = factor;
@@ -6267,8 +6996,52 @@ var QUALITY_LOW = /* @__PURE__ */ new Quality(0.6);
6267
6996
  var QUALITY_MEDIUM = /* @__PURE__ */ new Quality(1);
6268
6997
  var QUALITY_HIGH = /* @__PURE__ */ new Quality(2);
6269
6998
  var QUALITY_VERY_HIGH = /* @__PURE__ */ new Quality(4);
6999
+ var canEncodeAudio = async (codec, options = {}) => {
7000
+ const { numberOfChannels = 2, sampleRate = 48000, bitrate = 128000, ...restOptions } = options;
7001
+ if (!AUDIO_CODECS.includes(codec)) {
7002
+ return false;
7003
+ }
7004
+ if (!Number.isInteger(numberOfChannels) || numberOfChannels <= 0) {
7005
+ throw new TypeError("numberOfChannels must be a positive integer.");
7006
+ }
7007
+ if (!Number.isInteger(sampleRate) || sampleRate <= 0) {
7008
+ throw new TypeError("sampleRate must be a positive integer.");
7009
+ }
7010
+ if (!(bitrate instanceof Quality) && (!Number.isInteger(bitrate) || bitrate <= 0)) {
7011
+ throw new TypeError("bitrate must be a positive integer.");
7012
+ }
7013
+ validateAudioEncodingAdditionalOptions(codec, restOptions);
7014
+ let encoderConfig = null;
7015
+ if (customAudioEncoders.length > 0) {
7016
+ encoderConfig ??= buildAudioEncoderConfig({
7017
+ codec,
7018
+ numberOfChannels,
7019
+ sampleRate,
7020
+ bitrate,
7021
+ ...restOptions
7022
+ });
7023
+ if (customAudioEncoders.some((x) => x.supports(codec, encoderConfig))) {
7024
+ return true;
7025
+ }
7026
+ }
7027
+ if (PCM_AUDIO_CODECS.includes(codec)) {
7028
+ return true;
7029
+ }
7030
+ if (typeof AudioEncoder === "undefined") {
7031
+ return false;
7032
+ }
7033
+ encoderConfig ??= buildAudioEncoderConfig({
7034
+ codec,
7035
+ numberOfChannels,
7036
+ sampleRate,
7037
+ bitrate,
7038
+ ...restOptions
7039
+ });
7040
+ const support = await AudioEncoder.isConfigSupported(encoderConfig);
7041
+ return support.supported === true;
7042
+ };
6270
7043
 
6271
- // ../../node_modules/.bun/mediabunny@1.25.3/node_modules/mediabunny/dist/modules/src/media-source.js
7044
+ // ../../node_modules/.bun/mediabunny@1.25.8/node_modules/mediabunny/dist/modules/src/media-source.js
6272
7045
  /*!
6273
7046
  * Copyright (c) 2025-present, Vanilagy and contributors
6274
7047
  *
@@ -6898,6 +7671,359 @@ class AudioSource extends MediaSource {
6898
7671
  this._codec = codec;
6899
7672
  }
6900
7673
  }
7674
+ class AudioEncoderWrapper {
7675
+ constructor(source, encodingConfig) {
7676
+ this.source = source;
7677
+ this.encodingConfig = encodingConfig;
7678
+ this.ensureEncoderPromise = null;
7679
+ this.encoderInitialized = false;
7680
+ this.encoder = null;
7681
+ this.muxer = null;
7682
+ this.lastNumberOfChannels = null;
7683
+ this.lastSampleRate = null;
7684
+ this.isPcmEncoder = false;
7685
+ this.outputSampleSize = null;
7686
+ this.writeOutputValue = null;
7687
+ this.customEncoder = null;
7688
+ this.customEncoderCallSerializer = new CallSerializer;
7689
+ this.customEncoderQueueSize = 0;
7690
+ this.lastEndSampleIndex = null;
7691
+ this.error = null;
7692
+ this.errorNeedsNewStack = true;
7693
+ }
7694
+ async add(audioSample, shouldClose) {
7695
+ try {
7696
+ this.checkForEncoderError();
7697
+ this.source._ensureValidAdd();
7698
+ if (this.lastNumberOfChannels !== null && this.lastSampleRate !== null) {
7699
+ if (audioSample.numberOfChannels !== this.lastNumberOfChannels || audioSample.sampleRate !== this.lastSampleRate) {
7700
+ throw new Error(`Audio parameters must remain constant. Expected ${this.lastNumberOfChannels} channels at` + ` ${this.lastSampleRate} Hz, got ${audioSample.numberOfChannels} channels at` + ` ${audioSample.sampleRate} Hz.`);
7701
+ }
7702
+ } else {
7703
+ this.lastNumberOfChannels = audioSample.numberOfChannels;
7704
+ this.lastSampleRate = audioSample.sampleRate;
7705
+ }
7706
+ if (!this.encoderInitialized) {
7707
+ if (!this.ensureEncoderPromise) {
7708
+ this.ensureEncoder(audioSample);
7709
+ }
7710
+ if (!this.encoderInitialized) {
7711
+ await this.ensureEncoderPromise;
7712
+ }
7713
+ }
7714
+ assert(this.encoderInitialized);
7715
+ {
7716
+ const startSampleIndex = Math.round(audioSample.timestamp * audioSample.sampleRate);
7717
+ const endSampleIndex = Math.round((audioSample.timestamp + audioSample.duration) * audioSample.sampleRate);
7718
+ if (this.lastEndSampleIndex === null) {
7719
+ this.lastEndSampleIndex = endSampleIndex;
7720
+ } else {
7721
+ const sampleDiff = startSampleIndex - this.lastEndSampleIndex;
7722
+ if (sampleDiff >= 64) {
7723
+ const fillSample = new AudioSample({
7724
+ data: new Float32Array(sampleDiff * audioSample.numberOfChannels),
7725
+ format: "f32-planar",
7726
+ sampleRate: audioSample.sampleRate,
7727
+ numberOfChannels: audioSample.numberOfChannels,
7728
+ numberOfFrames: sampleDiff,
7729
+ timestamp: this.lastEndSampleIndex / audioSample.sampleRate
7730
+ });
7731
+ await this.add(fillSample, true);
7732
+ }
7733
+ this.lastEndSampleIndex += audioSample.numberOfFrames;
7734
+ }
7735
+ }
7736
+ if (this.customEncoder) {
7737
+ this.customEncoderQueueSize++;
7738
+ const clonedSample = audioSample.clone();
7739
+ const promise = this.customEncoderCallSerializer.call(() => this.customEncoder.encode(clonedSample)).then(() => this.customEncoderQueueSize--).catch((error) => this.error ??= error).finally(() => {
7740
+ clonedSample.close();
7741
+ });
7742
+ if (this.customEncoderQueueSize >= 4) {
7743
+ await promise;
7744
+ }
7745
+ await this.muxer.mutex.currentPromise;
7746
+ } else if (this.isPcmEncoder) {
7747
+ await this.doPcmEncoding(audioSample, shouldClose);
7748
+ } else {
7749
+ assert(this.encoder);
7750
+ const audioData = audioSample.toAudioData();
7751
+ this.encoder.encode(audioData);
7752
+ audioData.close();
7753
+ if (shouldClose) {
7754
+ audioSample.close();
7755
+ }
7756
+ if (this.encoder.encodeQueueSize >= 4) {
7757
+ await new Promise((resolve) => this.encoder.addEventListener("dequeue", resolve, { once: true }));
7758
+ }
7759
+ await this.muxer.mutex.currentPromise;
7760
+ }
7761
+ } finally {
7762
+ if (shouldClose) {
7763
+ audioSample.close();
7764
+ }
7765
+ }
7766
+ }
7767
+ async doPcmEncoding(audioSample, shouldClose) {
7768
+ assert(this.outputSampleSize);
7769
+ assert(this.writeOutputValue);
7770
+ const { numberOfChannels, numberOfFrames, sampleRate, timestamp } = audioSample;
7771
+ const CHUNK_SIZE = 2048;
7772
+ const outputs = [];
7773
+ for (let frame = 0;frame < numberOfFrames; frame += CHUNK_SIZE) {
7774
+ const frameCount = Math.min(CHUNK_SIZE, audioSample.numberOfFrames - frame);
7775
+ const outputSize = frameCount * numberOfChannels * this.outputSampleSize;
7776
+ const outputBuffer = new ArrayBuffer(outputSize);
7777
+ const outputView = new DataView(outputBuffer);
7778
+ outputs.push({ frameCount, view: outputView });
7779
+ }
7780
+ const allocationSize = audioSample.allocationSize({ planeIndex: 0, format: "f32-planar" });
7781
+ const floats = new Float32Array(allocationSize / Float32Array.BYTES_PER_ELEMENT);
7782
+ for (let i = 0;i < numberOfChannels; i++) {
7783
+ audioSample.copyTo(floats, { planeIndex: i, format: "f32-planar" });
7784
+ for (let j = 0;j < outputs.length; j++) {
7785
+ const { frameCount, view: view2 } = outputs[j];
7786
+ for (let k = 0;k < frameCount; k++) {
7787
+ this.writeOutputValue(view2, (k * numberOfChannels + i) * this.outputSampleSize, floats[j * CHUNK_SIZE + k]);
7788
+ }
7789
+ }
7790
+ }
7791
+ if (shouldClose) {
7792
+ audioSample.close();
7793
+ }
7794
+ const meta = {
7795
+ decoderConfig: {
7796
+ codec: this.encodingConfig.codec,
7797
+ numberOfChannels,
7798
+ sampleRate
7799
+ }
7800
+ };
7801
+ for (let i = 0;i < outputs.length; i++) {
7802
+ const { frameCount, view: view2 } = outputs[i];
7803
+ const outputBuffer = view2.buffer;
7804
+ const startFrame = i * CHUNK_SIZE;
7805
+ const packet = new EncodedPacket(new Uint8Array(outputBuffer), "key", timestamp + startFrame / sampleRate, frameCount / sampleRate);
7806
+ this.encodingConfig.onEncodedPacket?.(packet, meta);
7807
+ await this.muxer.addEncodedAudioPacket(this.source._connectedTrack, packet, meta);
7808
+ }
7809
+ }
7810
+ ensureEncoder(audioSample) {
7811
+ const encoderError = new Error;
7812
+ this.ensureEncoderPromise = (async () => {
7813
+ const { numberOfChannels, sampleRate } = audioSample;
7814
+ const encoderConfig = buildAudioEncoderConfig({
7815
+ numberOfChannels,
7816
+ sampleRate,
7817
+ ...this.encodingConfig
7818
+ });
7819
+ this.encodingConfig.onEncoderConfig?.(encoderConfig);
7820
+ const MatchingCustomEncoder = customAudioEncoders.find((x) => x.supports(this.encodingConfig.codec, encoderConfig));
7821
+ if (MatchingCustomEncoder) {
7822
+ this.customEncoder = new MatchingCustomEncoder;
7823
+ this.customEncoder.codec = this.encodingConfig.codec;
7824
+ this.customEncoder.config = encoderConfig;
7825
+ this.customEncoder.onPacket = (packet, meta) => {
7826
+ if (!(packet instanceof EncodedPacket)) {
7827
+ throw new TypeError("The first argument passed to onPacket must be an EncodedPacket.");
7828
+ }
7829
+ if (meta !== undefined && (!meta || typeof meta !== "object")) {
7830
+ throw new TypeError("The second argument passed to onPacket must be an object or undefined.");
7831
+ }
7832
+ this.encodingConfig.onEncodedPacket?.(packet, meta);
7833
+ this.muxer.addEncodedAudioPacket(this.source._connectedTrack, packet, meta).catch((error) => {
7834
+ this.error ??= error;
7835
+ this.errorNeedsNewStack = false;
7836
+ });
7837
+ };
7838
+ await this.customEncoder.init();
7839
+ } else if (PCM_AUDIO_CODECS.includes(this.encodingConfig.codec)) {
7840
+ this.initPcmEncoder();
7841
+ } else {
7842
+ if (typeof AudioEncoder === "undefined") {
7843
+ throw new Error("AudioEncoder is not supported by this browser.");
7844
+ }
7845
+ const support = await AudioEncoder.isConfigSupported(encoderConfig);
7846
+ if (!support.supported) {
7847
+ throw new Error(`This specific encoder configuration (${encoderConfig.codec}, ${encoderConfig.bitrate} bps,` + ` ${encoderConfig.numberOfChannels} channels, ${encoderConfig.sampleRate} Hz) is not` + ` supported by this browser. Consider using another codec or changing your audio parameters.`);
7848
+ }
7849
+ this.encoder = new AudioEncoder({
7850
+ output: (chunk, meta) => {
7851
+ if (this.encodingConfig.codec === "aac" && meta?.decoderConfig) {
7852
+ let needsDescriptionOverwrite = false;
7853
+ if (!meta.decoderConfig.description || meta.decoderConfig.description.byteLength < 2) {
7854
+ needsDescriptionOverwrite = true;
7855
+ } else {
7856
+ const audioSpecificConfig = parseAacAudioSpecificConfig(toUint8Array(meta.decoderConfig.description));
7857
+ needsDescriptionOverwrite = audioSpecificConfig.objectType === 0;
7858
+ }
7859
+ if (needsDescriptionOverwrite) {
7860
+ const objectType = Number(last(encoderConfig.codec.split(".")));
7861
+ meta.decoderConfig.description = buildAacAudioSpecificConfig({
7862
+ objectType,
7863
+ numberOfChannels: meta.decoderConfig.numberOfChannels,
7864
+ sampleRate: meta.decoderConfig.sampleRate
7865
+ });
7866
+ }
7867
+ }
7868
+ const packet = EncodedPacket.fromEncodedChunk(chunk);
7869
+ this.encodingConfig.onEncodedPacket?.(packet, meta);
7870
+ this.muxer.addEncodedAudioPacket(this.source._connectedTrack, packet, meta).catch((error) => {
7871
+ this.error ??= error;
7872
+ this.errorNeedsNewStack = false;
7873
+ });
7874
+ },
7875
+ error: (error) => {
7876
+ error.stack = encoderError.stack;
7877
+ this.error ??= error;
7878
+ }
7879
+ });
7880
+ this.encoder.configure(encoderConfig);
7881
+ }
7882
+ assert(this.source._connectedTrack);
7883
+ this.muxer = this.source._connectedTrack.output._muxer;
7884
+ this.encoderInitialized = true;
7885
+ })();
7886
+ }
7887
+ initPcmEncoder() {
7888
+ this.isPcmEncoder = true;
7889
+ const codec = this.encodingConfig.codec;
7890
+ const { dataType, sampleSize, littleEndian } = parsePcmCodec(codec);
7891
+ this.outputSampleSize = sampleSize;
7892
+ switch (sampleSize) {
7893
+ case 1:
7894
+ {
7895
+ if (dataType === "unsigned") {
7896
+ this.writeOutputValue = (view2, byteOffset, value) => view2.setUint8(byteOffset, clamp((value + 1) * 127.5, 0, 255));
7897
+ } else if (dataType === "signed") {
7898
+ this.writeOutputValue = (view2, byteOffset, value) => {
7899
+ view2.setInt8(byteOffset, clamp(Math.round(value * 128), -128, 127));
7900
+ };
7901
+ } else if (dataType === "ulaw") {
7902
+ this.writeOutputValue = (view2, byteOffset, value) => {
7903
+ const int16 = clamp(Math.floor(value * 32767), -32768, 32767);
7904
+ view2.setUint8(byteOffset, toUlaw(int16));
7905
+ };
7906
+ } else if (dataType === "alaw") {
7907
+ this.writeOutputValue = (view2, byteOffset, value) => {
7908
+ const int16 = clamp(Math.floor(value * 32767), -32768, 32767);
7909
+ view2.setUint8(byteOffset, toAlaw(int16));
7910
+ };
7911
+ } else {
7912
+ assert(false);
7913
+ }
7914
+ }
7915
+ ;
7916
+ break;
7917
+ case 2:
7918
+ {
7919
+ if (dataType === "unsigned") {
7920
+ this.writeOutputValue = (view2, byteOffset, value) => view2.setUint16(byteOffset, clamp((value + 1) * 32767.5, 0, 65535), littleEndian);
7921
+ } else if (dataType === "signed") {
7922
+ this.writeOutputValue = (view2, byteOffset, value) => view2.setInt16(byteOffset, clamp(Math.round(value * 32767), -32768, 32767), littleEndian);
7923
+ } else {
7924
+ assert(false);
7925
+ }
7926
+ }
7927
+ ;
7928
+ break;
7929
+ case 3:
7930
+ {
7931
+ if (dataType === "unsigned") {
7932
+ this.writeOutputValue = (view2, byteOffset, value) => setUint24(view2, byteOffset, clamp((value + 1) * 8388607.5, 0, 16777215), littleEndian);
7933
+ } else if (dataType === "signed") {
7934
+ this.writeOutputValue = (view2, byteOffset, value) => setInt24(view2, byteOffset, clamp(Math.round(value * 8388607), -8388608, 8388607), littleEndian);
7935
+ } else {
7936
+ assert(false);
7937
+ }
7938
+ }
7939
+ ;
7940
+ break;
7941
+ case 4:
7942
+ {
7943
+ if (dataType === "unsigned") {
7944
+ this.writeOutputValue = (view2, byteOffset, value) => view2.setUint32(byteOffset, clamp((value + 1) * 2147483647.5, 0, 4294967295), littleEndian);
7945
+ } else if (dataType === "signed") {
7946
+ this.writeOutputValue = (view2, byteOffset, value) => view2.setInt32(byteOffset, clamp(Math.round(value * 2147483647), -2147483648, 2147483647), littleEndian);
7947
+ } else if (dataType === "float") {
7948
+ this.writeOutputValue = (view2, byteOffset, value) => view2.setFloat32(byteOffset, value, littleEndian);
7949
+ } else {
7950
+ assert(false);
7951
+ }
7952
+ }
7953
+ ;
7954
+ break;
7955
+ case 8:
7956
+ {
7957
+ if (dataType === "float") {
7958
+ this.writeOutputValue = (view2, byteOffset, value) => view2.setFloat64(byteOffset, value, littleEndian);
7959
+ } else {
7960
+ assert(false);
7961
+ }
7962
+ }
7963
+ ;
7964
+ break;
7965
+ default:
7966
+ {
7967
+ assertNever(sampleSize);
7968
+ assert(false);
7969
+ }
7970
+ ;
7971
+ }
7972
+ }
7973
+ async flushAndClose(forceClose) {
7974
+ if (!forceClose)
7975
+ this.checkForEncoderError();
7976
+ if (this.customEncoder) {
7977
+ if (!forceClose) {
7978
+ this.customEncoderCallSerializer.call(() => this.customEncoder.flush());
7979
+ }
7980
+ await this.customEncoderCallSerializer.call(() => this.customEncoder.close());
7981
+ } else if (this.encoder) {
7982
+ if (!forceClose) {
7983
+ await this.encoder.flush();
7984
+ }
7985
+ if (this.encoder.state !== "closed") {
7986
+ this.encoder.close();
7987
+ }
7988
+ }
7989
+ if (!forceClose)
7990
+ this.checkForEncoderError();
7991
+ }
7992
+ getQueueSize() {
7993
+ if (this.customEncoder) {
7994
+ return this.customEncoderQueueSize;
7995
+ } else if (this.isPcmEncoder) {
7996
+ return 0;
7997
+ } else {
7998
+ return this.encoder?.encodeQueueSize ?? 0;
7999
+ }
8000
+ }
8001
+ checkForEncoderError() {
8002
+ if (this.error) {
8003
+ if (this.errorNeedsNewStack) {
8004
+ this.error.stack = new Error().stack;
8005
+ }
8006
+ throw this.error;
8007
+ }
8008
+ }
8009
+ }
8010
+
8011
+ class AudioSampleSource extends AudioSource {
8012
+ constructor(encodingConfig) {
8013
+ validateAudioEncodingConfig(encodingConfig);
8014
+ super(encodingConfig.codec);
8015
+ this._encoder = new AudioEncoderWrapper(this, encodingConfig);
8016
+ }
8017
+ add(audioSample) {
8018
+ if (!(audioSample instanceof AudioSample)) {
8019
+ throw new TypeError("audioSample must be an AudioSample.");
8020
+ }
8021
+ return this._encoder.add(audioSample, false);
8022
+ }
8023
+ _flushAndClose(forceClose) {
8024
+ return this._encoder.flushAndClose(forceClose);
8025
+ }
8026
+ }
6901
8027
  class SubtitleSource extends MediaSource {
6902
8028
  constructor(codec) {
6903
8029
  super();
@@ -6909,7 +8035,7 @@ class SubtitleSource extends MediaSource {
6909
8035
  }
6910
8036
  }
6911
8037
 
6912
- // ../../node_modules/.bun/mediabunny@1.25.3/node_modules/mediabunny/dist/modules/src/output.js
8038
+ // ../../node_modules/.bun/mediabunny@1.25.8/node_modules/mediabunny/dist/modules/src/output.js
6913
8039
  /*!
6914
8040
  * Copyright (c) 2025-present, Vanilagy and contributors
6915
8041
  *
@@ -7123,7 +8249,7 @@ class Output {
7123
8249
  })();
7124
8250
  }
7125
8251
  }
7126
- // ../../node_modules/.bun/mediabunny@1.25.3/node_modules/mediabunny/dist/modules/src/index.js
8252
+ // ../../node_modules/.bun/mediabunny@1.25.8/node_modules/mediabunny/dist/modules/src/index.js
7127
8253
  /*!
7128
8254
  * Copyright (c) 2025-present, Vanilagy and contributors
7129
8255
  *
@@ -7135,6 +8261,25 @@ class Output {
7135
8261
  // src/render-media-on-web.tsx
7136
8262
  import { Internals as Internals3 } from "remotion";
7137
8263
 
8264
+ // src/add-sample.ts
8265
+ var addVideoSampleAndCloseFrame = async (frameToEncode, videoSampleSource) => {
8266
+ const sample = new VideoSample(frameToEncode);
8267
+ try {
8268
+ await videoSampleSource.add(sample);
8269
+ } finally {
8270
+ sample.close();
8271
+ frameToEncode.close();
8272
+ }
8273
+ };
8274
+ var addAudioSample = async (audio, audioSampleSource) => {
8275
+ const sample = new AudioSample(audio);
8276
+ try {
8277
+ await audioSampleSource.add(sample);
8278
+ } finally {
8279
+ sample.close();
8280
+ }
8281
+ };
8282
+
7138
8283
  // src/artifact.ts
7139
8284
  import { NoReactInternals } from "remotion/no-react";
7140
8285
  var onlyArtifact = async ({
@@ -7172,34 +8317,73 @@ var onlyArtifact = async ({
7172
8317
  }
7173
8318
  return result.filter(NoReactInternals.truthy);
7174
8319
  };
7175
- var handleArtifacts = ({
7176
- ref,
7177
- onArtifact
7178
- }) => {
8320
+ var handleArtifacts = () => {
7179
8321
  const previousArtifacts = [];
7180
8322
  const handle = async ({
7181
8323
  imageData,
7182
- frame
8324
+ frame,
8325
+ assets: artifactAssets,
8326
+ onArtifact
7183
8327
  }) => {
7184
- const artifactAssets = ref.current.collectAssets();
7185
- if (onArtifact) {
7186
- const artifacts = await onlyArtifact({
7187
- assets: artifactAssets,
7188
- frameBuffer: imageData
7189
- });
7190
- for (const artifact of artifacts) {
7191
- const previousArtifact = previousArtifacts.find((a) => a.filename === artifact.filename);
7192
- if (previousArtifact) {
7193
- throw new Error(`An artifact with output "${artifact.filename}" was already registered at frame ${previousArtifact.frame}, but now registered again at frame ${frame}. Artifacts must have unique names. https://remotion.dev/docs/artifacts`);
7194
- }
7195
- onArtifact(artifact);
7196
- previousArtifacts.push({ frame, filename: artifact.filename });
8328
+ const artifacts = await onlyArtifact({
8329
+ assets: artifactAssets,
8330
+ frameBuffer: imageData
8331
+ });
8332
+ for (const artifact of artifacts) {
8333
+ const previousArtifact = previousArtifacts.find((a) => a.filename === artifact.filename);
8334
+ if (previousArtifact) {
8335
+ throw new Error(`An artifact with output "${artifact.filename}" was already registered at frame ${previousArtifact.frame}, but now registered again at frame ${frame}. Artifacts must have unique names. https://remotion.dev/docs/artifacts`);
7197
8336
  }
8337
+ onArtifact(artifact);
8338
+ previousArtifacts.push({ frame, filename: artifact.filename });
7198
8339
  }
7199
8340
  };
7200
8341
  return { handle };
7201
8342
  };
7202
8343
 
8344
+ // src/audio.ts
8345
+ var TARGET_NUMBER_OF_CHANNELS = 2;
8346
+ var TARGET_SAMPLE_RATE = 48000;
8347
+ function mixAudio(waves, length) {
8348
+ if (waves.length === 1) {
8349
+ return waves[0];
8350
+ }
8351
+ const mixed = new Int16Array(length);
8352
+ for (let i = 0;i < length; i++) {
8353
+ const sum = waves.reduce((acc, wave2) => acc + wave2[i], 0);
8354
+ mixed[i] = Math.max(-32768, Math.min(32767, sum));
8355
+ }
8356
+ return mixed;
8357
+ }
8358
+ var onlyInlineAudio = (assets) => {
8359
+ const inlineAudio = assets.filter((asset) => asset.type === "inline-audio");
8360
+ let length = null;
8361
+ for (const asset of inlineAudio) {
8362
+ if (asset.toneFrequency !== 1) {
8363
+ throw new Error("Setting the toneFrequency is not supported yet in web rendering.");
8364
+ }
8365
+ if (length === null) {
8366
+ length = asset.audio.length;
8367
+ } else if (Math.abs(length - asset.audio.length) > TARGET_NUMBER_OF_CHANNELS) {
8368
+ throw new Error("All inline audio must have the same length");
8369
+ } else {
8370
+ length = Math.min(length, asset.audio.length);
8371
+ }
8372
+ }
8373
+ if (length === null) {
8374
+ return null;
8375
+ }
8376
+ const mixedAudio = mixAudio(inlineAudio.map((asset) => asset.audio), length);
8377
+ return new AudioData({
8378
+ data: mixedAudio,
8379
+ format: "s16",
8380
+ numberOfChannels: TARGET_NUMBER_OF_CHANNELS,
8381
+ numberOfFrames: length / TARGET_NUMBER_OF_CHANNELS,
8382
+ sampleRate: TARGET_SAMPLE_RATE,
8383
+ timestamp: inlineAudio[0].timestamp
8384
+ });
8385
+ };
8386
+
7203
8387
  // src/create-scaffold.tsx
7204
8388
  import { createRef } from "react";
7205
8389
  import { flushSync as flushSync2 } from "react-dom";
@@ -7399,6 +8583,25 @@ var getRealFrameRange = (durationInFrames, frameRange) => {
7399
8583
  return frameRange;
7400
8584
  };
7401
8585
 
8586
+ // src/get-audio-encoding-config.ts
8587
+ var getDefaultAudioEncodingConfig = async () => {
8588
+ const preferredDefaultAudioEncodingConfig = {
8589
+ codec: "aac",
8590
+ bitrate: QUALITY_MEDIUM
8591
+ };
8592
+ if (await canEncodeAudio(preferredDefaultAudioEncodingConfig.codec, preferredDefaultAudioEncodingConfig)) {
8593
+ return preferredDefaultAudioEncodingConfig;
8594
+ }
8595
+ const backupDefaultAudioEncodingConfig = {
8596
+ codec: "opus",
8597
+ bitrate: QUALITY_MEDIUM
8598
+ };
8599
+ if (await canEncodeAudio(backupDefaultAudioEncodingConfig.codec, backupDefaultAudioEncodingConfig)) {
8600
+ return backupDefaultAudioEncodingConfig;
8601
+ }
8602
+ return null;
8603
+ };
8604
+
7402
8605
  // src/mediabunny-mappings.ts
7403
8606
  var codecToMediabunnyCodec = (codec) => {
7404
8607
  switch (codec) {
@@ -7807,10 +9010,7 @@ var internalRenderMediaOnWeb = async ({
7807
9010
  defaultCodec: resolved.defaultCodec,
7808
9011
  defaultOutName: resolved.defaultOutName
7809
9012
  });
7810
- const artifactsHandler = handleArtifacts({
7811
- ref: collectAssets,
7812
- onArtifact
7813
- });
9013
+ const artifactsHandler = handleArtifacts();
7814
9014
  cleanupFns.push(() => {
7815
9015
  cleanupScaffold();
7816
9016
  });
@@ -7850,6 +9050,15 @@ var internalRenderMediaOnWeb = async ({
7850
9050
  videoSampleSource.close();
7851
9051
  });
7852
9052
  output.addVideoTrack(videoSampleSource);
9053
+ const defaultAudioEncodingConfig = await getDefaultAudioEncodingConfig();
9054
+ if (!defaultAudioEncodingConfig) {
9055
+ return Promise.reject(new Error("No default audio encoding config found"));
9056
+ }
9057
+ const audioSampleSource = new AudioSampleSource(defaultAudioEncodingConfig);
9058
+ cleanupFns.push(() => {
9059
+ audioSampleSource.close();
9060
+ });
9061
+ output.addAudioTrack(audioSampleSource);
7853
9062
  await output.start();
7854
9063
  if (signal?.aborted) {
7855
9064
  throw new Error("renderMediaOnWeb() was cancelled");
@@ -7875,7 +9084,16 @@ var internalRenderMediaOnWeb = async ({
7875
9084
  width: resolved.width,
7876
9085
  height: resolved.height
7877
9086
  });
7878
- await artifactsHandler.handle({ imageData, frame: i });
9087
+ const assets = collectAssets.current.collectAssets();
9088
+ if (onArtifact) {
9089
+ await artifactsHandler.handle({
9090
+ imageData,
9091
+ frame: i,
9092
+ assets,
9093
+ onArtifact
9094
+ });
9095
+ }
9096
+ const audio = onlyInlineAudio(assets);
7879
9097
  if (signal?.aborted) {
7880
9098
  throw new Error("renderMediaOnWeb() was cancelled");
7881
9099
  }
@@ -7896,16 +9114,19 @@ var internalRenderMediaOnWeb = async ({
7896
9114
  expectedTimestamp: timestamp
7897
9115
  });
7898
9116
  }
7899
- await videoSampleSource.add(new VideoSample(frameToEncode));
9117
+ await Promise.all([
9118
+ addVideoSampleAndCloseFrame(frameToEncode, videoSampleSource),
9119
+ audio ? addAudioSample(audio, audioSampleSource) : Promise.resolve()
9120
+ ]);
7900
9121
  progress.encodedFrames++;
7901
9122
  throttledOnProgress?.({ ...progress });
7902
- frameToEncode.close();
7903
9123
  if (signal?.aborted) {
7904
9124
  throw new Error("renderMediaOnWeb() was cancelled");
7905
9125
  }
7906
9126
  }
7907
9127
  onProgress?.({ ...progress });
7908
9128
  videoSampleSource.close();
9129
+ audioSampleSource.close();
7909
9130
  await output.finalize();
7910
9131
  return output.target.buffer;
7911
9132
  } finally {
@@ -7982,10 +9203,7 @@ async function internalRenderStillOnWeb({
7982
9203
  defaultCodec: resolved.defaultCodec,
7983
9204
  defaultOutName: resolved.defaultOutName
7984
9205
  });
7985
- const artifactsHandler = handleArtifacts({
7986
- ref: collectAssets,
7987
- onArtifact
7988
- });
9206
+ const artifactsHandler = handleArtifacts();
7989
9207
  try {
7990
9208
  if (signal?.aborted) {
7991
9209
  throw new Error("renderStillOnWeb() was cancelled");
@@ -8005,7 +9223,10 @@ async function internalRenderStillOnWeb({
8005
9223
  height: resolved.height,
8006
9224
  imageFormat
8007
9225
  });
8008
- await artifactsHandler.handle({ imageData, frame });
9226
+ const assets = collectAssets.current.collectAssets();
9227
+ if (onArtifact) {
9228
+ await artifactsHandler.handle({ imageData, frame, assets, onArtifact });
9229
+ }
8009
9230
  return imageData;
8010
9231
  } finally {
8011
9232
  cleanupScaffold();