mediabunny 1.6.2 → 1.7.1

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 (50) hide show
  1. package/dist/bundles/mediabunny.cjs +296 -202
  2. package/dist/bundles/mediabunny.min.cjs +5 -5
  3. package/dist/bundles/mediabunny.min.mjs +5 -5
  4. package/dist/bundles/mediabunny.mjs +296 -202
  5. package/dist/mediabunny.d.ts +88 -55
  6. package/dist/modules/src/conversion.d.ts +77 -57
  7. package/dist/modules/src/conversion.d.ts.map +1 -1
  8. package/dist/modules/src/conversion.js +139 -100
  9. package/dist/modules/src/index.d.ts +2 -2
  10. package/dist/modules/src/index.d.ts.map +1 -1
  11. package/dist/modules/src/input-format.js +1 -1
  12. package/dist/modules/src/isobmff/isobmff-demuxer.d.ts.map +1 -1
  13. package/dist/modules/src/isobmff/isobmff-demuxer.js +4 -7
  14. package/dist/modules/src/matroska/ebml.d.ts +2 -2
  15. package/dist/modules/src/matroska/ebml.d.ts.map +1 -1
  16. package/dist/modules/src/matroska/ebml.js +3 -4
  17. package/dist/modules/src/matroska/matroska-demuxer.d.ts.map +1 -1
  18. package/dist/modules/src/matroska/matroska-demuxer.js +7 -10
  19. package/dist/modules/src/media-sink.d.ts.map +1 -1
  20. package/dist/modules/src/media-sink.js +42 -21
  21. package/dist/modules/src/media-source.d.ts.map +1 -1
  22. package/dist/modules/src/media-source.js +2 -0
  23. package/dist/modules/src/misc.d.ts +8 -0
  24. package/dist/modules/src/misc.d.ts.map +1 -1
  25. package/dist/modules/src/misc.js +18 -0
  26. package/dist/modules/src/mp3/mp3-demuxer.d.ts +9 -1
  27. package/dist/modules/src/mp3/mp3-demuxer.d.ts.map +1 -1
  28. package/dist/modules/src/mp3/mp3-demuxer.js +106 -49
  29. package/dist/modules/src/mp3/mp3-muxer.d.ts.map +1 -1
  30. package/dist/modules/src/mp3/mp3-muxer.js +5 -2
  31. package/dist/modules/src/output-format.d.ts +5 -0
  32. package/dist/modules/src/output-format.d.ts.map +1 -1
  33. package/dist/modules/src/output-format.js +3 -0
  34. package/dist/modules/src/source.d.ts.map +1 -1
  35. package/dist/modules/src/source.js +17 -1
  36. package/dist/modules/src/tsconfig.tsbuildinfo +1 -1
  37. package/package.json +1 -1
  38. package/src/conversion.ts +243 -180
  39. package/src/index.ts +2 -2
  40. package/src/input-format.ts +1 -1
  41. package/src/isobmff/isobmff-demuxer.ts +5 -15
  42. package/src/matroska/ebml.ts +3 -4
  43. package/src/matroska/matroska-demuxer.ts +7 -22
  44. package/src/media-sink.ts +50 -28
  45. package/src/media-source.ts +4 -0
  46. package/src/misc.ts +30 -0
  47. package/src/mp3/mp3-demuxer.ts +123 -54
  48. package/src/mp3/mp3-muxer.ts +7 -2
  49. package/src/output-format.ts +9 -0
  50. package/src/source.ts +22 -2
@@ -344,6 +344,10 @@ var Mediabunny = (() => {
344
344
  }
345
345
  return ans;
346
346
  };
347
+ var insertSorted = (arr, item, valueGetter) => {
348
+ const insertionIndex = binarySearchLessOrEqual(arr, valueGetter(item), valueGetter);
349
+ arr.splice(insertionIndex + 1, 0, item);
350
+ };
347
351
  var promiseWithResolvers = () => {
348
352
  let resolve;
349
353
  let reject;
@@ -550,6 +554,15 @@ var Mediabunny = (() => {
550
554
  return this.currentPromise = this.currentPromise.then(fn);
551
555
  }
552
556
  };
557
+ var isSafariCache = null;
558
+ var isSafari = () => {
559
+ if (isSafariCache !== null) {
560
+ return isSafariCache;
561
+ }
562
+ const result = !!(typeof navigator !== "undefined" && navigator.vendor?.match(/apple/i) && !navigator.userAgent?.match(/crios/i) && !navigator.userAgent?.match(/fxios/i) && !navigator.userAgent?.match(/Opera|OPT\//));
563
+ isSafariCache = result;
564
+ return result;
565
+ };
553
566
 
554
567
  // src/custom-coder.ts
555
568
  var CustomVideoDecoder = class {
@@ -5536,8 +5549,7 @@ var Mediabunny = (() => {
5536
5549
  }
5537
5550
  this.writer.write(this.helper.subarray(0, pos));
5538
5551
  }
5539
- // Assumes the string is ASCII
5540
- writeString(str) {
5552
+ writeAsciiString(str) {
5541
5553
  this.writer.write(new Uint8Array(str.split("").map((x) => x.charCodeAt(0))));
5542
5554
  }
5543
5555
  writeEBML(data) {
@@ -5575,7 +5587,7 @@ var Mediabunny = (() => {
5575
5587
  this.writeUnsignedInt(data.data, size);
5576
5588
  } else if (typeof data.data === "string") {
5577
5589
  this.writeVarInt(data.data.length);
5578
- this.writeString(data.data);
5590
+ this.writeAsciiString(data.data);
5579
5591
  } else if (data.data instanceof Uint8Array) {
5580
5592
  this.writeVarInt(data.data.byteLength, data.size);
5581
5593
  this.writer.write(data.data);
@@ -5677,7 +5689,7 @@ var Mediabunny = (() => {
5677
5689
  this.pos += width;
5678
5690
  return value;
5679
5691
  }
5680
- readString(length) {
5692
+ readAsciiString(length) {
5681
5693
  const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + length);
5682
5694
  this.pos += length;
5683
5695
  let strLength = 0;
@@ -6637,7 +6649,8 @@ ${cue.notes ?? ""}`;
6637
6649
  async addEncodedAudioPacket(track, packet) {
6638
6650
  const release = await this.mutex.acquire();
6639
6651
  try {
6640
- if (!this.xingFrameData) {
6652
+ const writeXingHeader = this.format._options.xingHeader !== false;
6653
+ if (!this.xingFrameData && writeXingHeader) {
6641
6654
  const view2 = toDataView(packet.data);
6642
6655
  if (view2.byteLength < 4) {
6643
6656
  throw new Error("Invalid MP3 header in sample.");
@@ -6672,10 +6685,12 @@ ${cue.notes ?? ""}`;
6672
6685
  this.frameCount++;
6673
6686
  }
6674
6687
  this.validateAndNormalizeTimestamp(track, packet.timestamp, packet.type === "key");
6675
- this.framePositions.push(this.writer.getPos());
6676
6688
  this.writer.write(packet.data);
6677
6689
  this.frameCount++;
6678
6690
  await this.writer.flush();
6691
+ if (writeXingHeader) {
6692
+ this.framePositions.push(this.writer.getPos());
6693
+ }
6679
6694
  } finally {
6680
6695
  release();
6681
6696
  }
@@ -8745,6 +8760,7 @@ ${cue.notes ?? ""}`;
8745
8760
  return decodedSampleQueueSize === 0 ? 40 : 8;
8746
8761
  };
8747
8762
  var VideoDecoderWrapper = class extends DecoderWrapper {
8763
+ // Safari-specific thing, check usage.
8748
8764
  constructor(onSample, onError, codec, decoderConfig, rotation, timeResolution) {
8749
8765
  super(onSample, onError);
8750
8766
  this.rotation = rotation;
@@ -8753,21 +8769,9 @@ ${cue.notes ?? ""}`;
8753
8769
  this.customDecoder = null;
8754
8770
  this.customDecoderCallSerializer = new CallSerializer();
8755
8771
  this.customDecoderQueueSize = 0;
8772
+ this.inputTimestamps = [];
8773
+ // Timestamps input into the decoder, sorted.
8756
8774
  this.sampleQueue = [];
8757
- const sampleHandler = (sample) => {
8758
- if (this.sampleQueue.length > 0 && sample.timestamp >= last(this.sampleQueue).timestamp) {
8759
- for (const sample2 of this.sampleQueue) {
8760
- this.finalizeAndEmitSample(sample2);
8761
- }
8762
- this.sampleQueue.length = 0;
8763
- }
8764
- const insertionIndex = binarySearchLessOrEqual(
8765
- this.sampleQueue,
8766
- sample.timestamp,
8767
- (x) => x.timestamp
8768
- );
8769
- this.sampleQueue.splice(insertionIndex + 1, 0, sample);
8770
- };
8771
8775
  const MatchingCustomDecoder = customVideoDecoders.find((x) => x.supports(codec, decoderConfig));
8772
8776
  if (MatchingCustomDecoder) {
8773
8777
  this.customDecoder = new MatchingCustomDecoder();
@@ -8777,10 +8781,26 @@ ${cue.notes ?? ""}`;
8777
8781
  if (!(sample instanceof VideoSample)) {
8778
8782
  throw new TypeError("The argument passed to onSample must be a VideoSample.");
8779
8783
  }
8780
- sampleHandler(sample);
8784
+ this.finalizeAndEmitSample(sample);
8781
8785
  };
8782
8786
  void this.customDecoderCallSerializer.call(() => this.customDecoder.init());
8783
8787
  } else {
8788
+ const sampleHandler = (sample) => {
8789
+ if (isSafari()) {
8790
+ if (this.sampleQueue.length > 0 && sample.timestamp >= last(this.sampleQueue).timestamp) {
8791
+ for (const sample2 of this.sampleQueue) {
8792
+ this.finalizeAndEmitSample(sample2);
8793
+ }
8794
+ this.sampleQueue.length = 0;
8795
+ }
8796
+ insertSorted(this.sampleQueue, sample, (x) => x.timestamp);
8797
+ } else {
8798
+ const timestamp = this.inputTimestamps.shift();
8799
+ assert(timestamp !== void 0);
8800
+ sample.setTimestamp(timestamp);
8801
+ this.finalizeAndEmitSample(sample);
8802
+ }
8803
+ };
8784
8804
  this.decoder = new VideoDecoder({
8785
8805
  output: (frame) => sampleHandler(new VideoSample(frame)),
8786
8806
  error: onError
@@ -8808,6 +8828,9 @@ ${cue.notes ?? ""}`;
8808
8828
  void this.customDecoderCallSerializer.call(() => this.customDecoder.decode(packet)).then(() => this.customDecoderQueueSize--);
8809
8829
  } else {
8810
8830
  assert(this.decoder);
8831
+ if (!isSafari()) {
8832
+ insertSorted(this.inputTimestamps, packet.timestamp, (x) => x);
8833
+ }
8811
8834
  this.decoder.decode(packet.toEncodedVideoChunk());
8812
8835
  }
8813
8836
  }
@@ -8818,10 +8841,12 @@ ${cue.notes ?? ""}`;
8818
8841
  assert(this.decoder);
8819
8842
  await this.decoder.flush();
8820
8843
  }
8821
- for (const sample of this.sampleQueue) {
8822
- this.finalizeAndEmitSample(sample);
8844
+ if (isSafari()) {
8845
+ for (const sample of this.sampleQueue) {
8846
+ this.finalizeAndEmitSample(sample);
8847
+ }
8848
+ this.sampleQueue.length = 0;
8823
8849
  }
8824
- this.sampleQueue.length = 0;
8825
8850
  }
8826
8851
  close() {
8827
8852
  if (this.customDecoder) {
@@ -10420,6 +10445,9 @@ ${cue.notes ?? ""}`;
10420
10445
  if (!options || typeof options !== "object") {
10421
10446
  throw new TypeError("options must be an object.");
10422
10447
  }
10448
+ if (options.xingHeader !== void 0 && typeof options.xingHeader !== "boolean") {
10449
+ throw new TypeError("options.xingHeader, when provided, must be a boolean.");
10450
+ }
10423
10451
  if (options.onXingFrame !== void 0 && typeof options.onXingFrame !== "function") {
10424
10452
  throw new TypeError("options.onXingFrame, when provided, must be a function.");
10425
10453
  }
@@ -11567,6 +11595,7 @@ ${cue.notes ?? ""}`;
11567
11595
  this._promiseWithResolvers.reject(error);
11568
11596
  });
11569
11597
  } else {
11598
+ const AudioContext = window.AudioContext || window.webkitAudioContext;
11570
11599
  this._audioContext = new AudioContext({ sampleRate: this._track.getSettings().sampleRate });
11571
11600
  const sourceNode = this._audioContext.createMediaStreamSource(new MediaStream([this._track]));
11572
11601
  this._scriptProcessorNode = this._audioContext.createScriptProcessor(4096);
@@ -12100,7 +12129,9 @@ ${cue.notes ?? ""}`;
12100
12129
  super();
12101
12130
  /** @internal */
12102
12131
  this._fullData = null;
12103
- this._url = url2;
12132
+ /** @internal */
12133
+ this._nextUrlVersion = null;
12134
+ this._url = url2 instanceof URL ? url2 : new URL(url2);
12104
12135
  this._options = options;
12105
12136
  }
12106
12137
  /** @internal */
@@ -12109,6 +12140,10 @@ ${cue.notes ?? ""}`;
12109
12140
  if (range) {
12110
12141
  headers["Range"] = `bytes=${range.start}-${range.end - 1}`;
12111
12142
  }
12143
+ if (this._nextUrlVersion !== null) {
12144
+ this._url.searchParams.set("mediabunny_version", this._nextUrlVersion.toString());
12145
+ this._nextUrlVersion++;
12146
+ }
12112
12147
  const response = await retriedFetch(
12113
12148
  this._url,
12114
12149
  mergeObjectsDeeply(this._options.requestInit ?? {}, {
@@ -12121,6 +12156,10 @@ ${cue.notes ?? ""}`;
12121
12156
  throw new Error(`Error fetching ${this._url}: ${response.status} ${response.statusText}`);
12122
12157
  }
12123
12158
  const buffer = await response.arrayBuffer();
12159
+ if (response.status === 206 && range && buffer.byteLength !== range.end - range.start && this._nextUrlVersion === null) {
12160
+ this._nextUrlVersion = 1;
12161
+ return this._makeRequest(range);
12162
+ }
12124
12163
  if (response.status === 200) {
12125
12164
  this._fullData = buffer;
12126
12165
  }
@@ -13365,12 +13404,7 @@ ${cue.notes ?? ""}`;
13365
13404
  isKnownToBeFirstFragment: false
13366
13405
  };
13367
13406
  this.readContiguousBoxes(boxInfo.contentSize);
13368
- const insertionIndex = binarySearchLessOrEqual(
13369
- this.fragments,
13370
- this.currentFragment.moofOffset,
13371
- (x) => x.moofOffset
13372
- );
13373
- this.fragments.splice(insertionIndex + 1, 0, this.currentFragment);
13407
+ insertSorted(this.fragments, this.currentFragment, (x) => x.moofOffset);
13374
13408
  for (const [, trackData] of this.currentFragment.trackData) {
13375
13409
  const firstSample = trackData.samples[0];
13376
13410
  const lastSample = last(trackData.samples);
@@ -13394,20 +13428,14 @@ ${cue.notes ?? ""}`;
13394
13428
  if (this.currentTrack) {
13395
13429
  const trackData = this.currentFragment.trackData.get(this.currentTrack.id);
13396
13430
  if (trackData) {
13397
- const insertionIndex = binarySearchLessOrEqual(
13398
- this.currentTrack.fragments,
13399
- this.currentFragment.moofOffset,
13400
- (x) => x.moofOffset
13401
- );
13402
- this.currentTrack.fragments.splice(insertionIndex + 1, 0, this.currentFragment);
13431
+ insertSorted(this.currentTrack.fragments, this.currentFragment, (x) => x.moofOffset);
13403
13432
  const hasKeyFrame = trackData.firstKeyFrameTimestamp !== null;
13404
13433
  if (hasKeyFrame) {
13405
- const insertionIndex2 = binarySearchLessOrEqual(
13434
+ insertSorted(
13406
13435
  this.currentTrack.fragmentsWithKeyFrame,
13407
- this.currentFragment.moofOffset,
13436
+ this.currentFragment,
13408
13437
  (x) => x.moofOffset
13409
13438
  );
13410
- this.currentTrack.fragmentsWithKeyFrame.splice(insertionIndex2 + 1, 0, this.currentFragment);
13411
13439
  }
13412
13440
  const { currentFragmentState } = this.currentTrack;
13413
13441
  assert(currentFragmentState);
@@ -14496,29 +14524,14 @@ ${cue.notes ?? ""}`;
14496
14524
  trackData.startTimestamp = firstBlock.timestamp;
14497
14525
  trackData.endTimestamp = lastBlock.timestamp + lastBlock.duration;
14498
14526
  if (track) {
14499
- const insertionIndex2 = binarySearchLessOrEqual(
14500
- track.clusters,
14501
- cluster.elementStartPos,
14502
- (x) => x.elementStartPos
14503
- );
14504
- track.clusters.splice(insertionIndex2 + 1, 0, cluster);
14527
+ insertSorted(track.clusters, cluster, (x) => x.elementStartPos);
14505
14528
  const hasKeyFrame = trackData.firstKeyFrameTimestamp !== null;
14506
14529
  if (hasKeyFrame) {
14507
- const insertionIndex3 = binarySearchLessOrEqual(
14508
- track.clustersWithKeyFrame,
14509
- cluster.elementStartPos,
14510
- (x) => x.elementStartPos
14511
- );
14512
- track.clustersWithKeyFrame.splice(insertionIndex3 + 1, 0, cluster);
14530
+ insertSorted(track.clustersWithKeyFrame, cluster, (x) => x.elementStartPos);
14513
14531
  }
14514
14532
  }
14515
14533
  }
14516
- const insertionIndex = binarySearchLessOrEqual(
14517
- segment.clusters,
14518
- elementStartPos,
14519
- (x) => x.elementStartPos
14520
- );
14521
- segment.clusters.splice(insertionIndex + 1, 0, cluster);
14534
+ insertSorted(segment.clusters, cluster, (x) => x.elementStartPos);
14522
14535
  this.currentCluster = null;
14523
14536
  return cluster;
14524
14537
  }
@@ -14638,7 +14651,7 @@ ${cue.notes ?? ""}`;
14638
14651
  switch (id) {
14639
14652
  case 17026 /* DocType */:
14640
14653
  {
14641
- this.isWebM = reader.readString(size) === "webm";
14654
+ this.isWebM = reader.readAsciiString(size) === "webm";
14642
14655
  }
14643
14656
  ;
14644
14657
  break;
@@ -14836,7 +14849,7 @@ ${cue.notes ?? ""}`;
14836
14849
  case 134 /* CodecID */:
14837
14850
  {
14838
14851
  if (!this.currentTrack) break;
14839
- this.currentTrack.codecId = reader.readString(size);
14852
+ this.currentTrack.codecId = reader.readAsciiString(size);
14840
14853
  }
14841
14854
  ;
14842
14855
  break;
@@ -14857,7 +14870,7 @@ ${cue.notes ?? ""}`;
14857
14870
  case 2274716 /* Language */:
14858
14871
  {
14859
14872
  if (!this.currentTrack) break;
14860
- this.currentTrack.languageCode = reader.readString(size);
14873
+ this.currentTrack.languageCode = reader.readAsciiString(size);
14861
14874
  if (!isIso639Dash2LanguageCode(this.currentTrack.languageCode)) {
14862
14875
  this.currentTrack.languageCode = UNDETERMINED_LANGUAGE;
14863
14876
  }
@@ -15672,45 +15685,21 @@ ${cue.notes ?? ""}`;
15672
15685
  super(input);
15673
15686
  this.metadataPromise = null;
15674
15687
  this.firstFrameHeader = null;
15675
- this.allSamples = [];
15688
+ this.loadedSamples = [];
15689
+ // All samples from the start of the file to lastLoadedPos
15676
15690
  this.tracks = [];
15691
+ this.loadingMutex = new AsyncMutex();
15692
+ this.lastLoadedPos = 0;
15693
+ this.fileSize = 0;
15694
+ this.nextTimestampInSamples = 0;
15677
15695
  this.reader = new Mp3Reader(input._mainReader);
15678
15696
  }
15679
15697
  async readMetadata() {
15680
15698
  return this.metadataPromise ??= (async () => {
15681
- const fileSize = await this.input.source.getSize();
15682
- this.reader.fileSize = fileSize;
15683
- await this.reader.reader.loadRange(0, fileSize);
15684
- const id3Tag = this.reader.readId3();
15685
- if (id3Tag) {
15686
- this.reader.pos += id3Tag.size;
15687
- }
15688
- let nextTimestampInSamples = 0;
15689
- while (true) {
15690
- const header = this.reader.readNextFrameHeader();
15691
- if (!header) {
15692
- break;
15693
- }
15694
- const xingOffset = getXingOffset(header.mpegVersionId, header.channel);
15695
- this.reader.pos = header.startPos + xingOffset;
15696
- const word = this.reader.readU32();
15697
- const isXing = word === XING || word === INFO;
15698
- this.reader.pos = header.startPos + header.totalSize - 1;
15699
- if (isXing) {
15700
- continue;
15701
- }
15702
- if (!this.firstFrameHeader) {
15703
- this.firstFrameHeader = header;
15704
- }
15705
- const sampleDuration = header.audioSamplesInFrame / header.sampleRate;
15706
- const sample = {
15707
- timestamp: nextTimestampInSamples / header.sampleRate,
15708
- duration: sampleDuration,
15709
- dataStart: header.startPos,
15710
- dataSize: header.totalSize
15711
- };
15712
- this.allSamples.push(sample);
15713
- nextTimestampInSamples += header.audioSamplesInFrame;
15699
+ this.fileSize = await this.input.source.getSize();
15700
+ this.reader.fileSize = this.fileSize;
15701
+ while (!this.firstFrameHeader && this.lastLoadedPos < this.fileSize) {
15702
+ await this.loadNextChunk();
15714
15703
  }
15715
15704
  if (!this.firstFrameHeader) {
15716
15705
  throw new Error("No MP3 frames found.");
@@ -15718,6 +15707,61 @@ ${cue.notes ?? ""}`;
15718
15707
  this.tracks = [new InputAudioTrack(new Mp3AudioTrackBacking(this))];
15719
15708
  })();
15720
15709
  }
15710
+ /** Loads the next 0.5 MiB of frames. */
15711
+ async loadNextChunk() {
15712
+ const release = await this.loadingMutex.acquire();
15713
+ try {
15714
+ assert(this.lastLoadedPos < this.fileSize);
15715
+ const chunkSize = 0.5 * 1024 * 1024;
15716
+ const endPos = Math.min(this.lastLoadedPos + chunkSize, this.fileSize);
15717
+ await this.reader.reader.loadRange(this.lastLoadedPos, endPos);
15718
+ this.lastLoadedPos = endPos;
15719
+ assert(this.lastLoadedPos <= this.fileSize);
15720
+ if (this.reader.pos === 0) {
15721
+ const id3Tag = this.reader.readId3();
15722
+ if (id3Tag) {
15723
+ this.reader.pos += id3Tag.size;
15724
+ }
15725
+ }
15726
+ this.parseFramesFromLoadedData();
15727
+ } finally {
15728
+ release();
15729
+ }
15730
+ }
15731
+ parseFramesFromLoadedData() {
15732
+ while (true) {
15733
+ const startPos = this.reader.pos;
15734
+ const header = this.reader.readNextFrameHeader();
15735
+ if (!header) {
15736
+ break;
15737
+ }
15738
+ if (header.startPos + header.totalSize > this.lastLoadedPos) {
15739
+ this.reader.pos = startPos;
15740
+ this.lastLoadedPos = startPos;
15741
+ break;
15742
+ }
15743
+ const xingOffset = getXingOffset(header.mpegVersionId, header.channel);
15744
+ this.reader.pos = header.startPos + xingOffset;
15745
+ const word = this.reader.readU32();
15746
+ const isXing = word === XING || word === INFO;
15747
+ this.reader.pos = header.startPos + header.totalSize - 1;
15748
+ if (isXing) {
15749
+ continue;
15750
+ }
15751
+ if (!this.firstFrameHeader) {
15752
+ this.firstFrameHeader = header;
15753
+ }
15754
+ const sampleDuration = header.audioSamplesInFrame / header.sampleRate;
15755
+ const sample = {
15756
+ timestamp: this.nextTimestampInSamples / header.sampleRate,
15757
+ duration: sampleDuration,
15758
+ dataStart: header.startPos,
15759
+ dataSize: header.totalSize
15760
+ };
15761
+ this.loadedSamples.push(sample);
15762
+ this.nextTimestampInSamples += header.audioSamplesInFrame;
15763
+ }
15764
+ }
15721
15765
  async getMimeType() {
15722
15766
  return "audio/mpeg";
15723
15767
  }
@@ -15727,9 +15771,9 @@ ${cue.notes ?? ""}`;
15727
15771
  }
15728
15772
  async computeDuration() {
15729
15773
  await this.readMetadata();
15730
- const lastSample = last(this.allSamples);
15731
- assert(lastSample);
15732
- return lastSample.timestamp + lastSample.duration;
15774
+ const track = this.tracks[0];
15775
+ assert(track);
15776
+ return track.computeDuration();
15733
15777
  }
15734
15778
  };
15735
15779
  var Mp3AudioTrackBacking = class {
@@ -15746,8 +15790,9 @@ ${cue.notes ?? ""}`;
15746
15790
  assert(this.demuxer.firstFrameHeader);
15747
15791
  return this.demuxer.firstFrameHeader.sampleRate / this.demuxer.firstFrameHeader.audioSamplesInFrame;
15748
15792
  }
15749
- computeDuration() {
15750
- return this.demuxer.computeDuration();
15793
+ async computeDuration() {
15794
+ const lastPacket = await this.getPacket(Infinity, { metadataOnly: true });
15795
+ return (lastPacket?.timestamp ?? 0) + (lastPacket?.duration ?? 0);
15751
15796
  }
15752
15797
  getLanguageCode() {
15753
15798
  return UNDETERMINED_LANGUAGE;
@@ -15775,7 +15820,7 @@ ${cue.notes ?? ""}`;
15775
15820
  if (sampleIndex === -1) {
15776
15821
  return null;
15777
15822
  }
15778
- const rawSample = this.demuxer.allSamples[sampleIndex];
15823
+ const rawSample = this.demuxer.loadedSamples[sampleIndex];
15779
15824
  if (!rawSample) {
15780
15825
  return null;
15781
15826
  }
@@ -15796,26 +15841,44 @@ ${cue.notes ?? ""}`;
15796
15841
  );
15797
15842
  }
15798
15843
  async getFirstPacket(options) {
15844
+ while (this.demuxer.loadedSamples.length === 0 && this.demuxer.lastLoadedPos < this.demuxer.fileSize) {
15845
+ await this.demuxer.loadNextChunk();
15846
+ }
15799
15847
  return this.getPacketAtIndex(0, options);
15800
15848
  }
15801
15849
  async getNextPacket(packet, options) {
15802
15850
  const sampleIndex = binarySearchExact(
15803
- this.demuxer.allSamples,
15851
+ this.demuxer.loadedSamples,
15804
15852
  packet.timestamp,
15805
15853
  (x) => x.timestamp
15806
15854
  );
15807
15855
  if (sampleIndex === -1) {
15808
15856
  throw new Error("Packet was not created from this track.");
15809
15857
  }
15810
- return this.getPacketAtIndex(sampleIndex + 1, options);
15858
+ const nextIndex = sampleIndex + 1;
15859
+ while (nextIndex >= this.demuxer.loadedSamples.length && this.demuxer.lastLoadedPos < this.demuxer.fileSize) {
15860
+ await this.demuxer.loadNextChunk();
15861
+ }
15862
+ return this.getPacketAtIndex(nextIndex, options);
15811
15863
  }
15812
15864
  async getPacket(timestamp, options) {
15813
- const index = binarySearchLessOrEqual(
15814
- this.demuxer.allSamples,
15815
- timestamp,
15816
- (x) => x.timestamp
15817
- );
15818
- return this.getPacketAtIndex(index, options);
15865
+ while (true) {
15866
+ const index = binarySearchLessOrEqual(
15867
+ this.demuxer.loadedSamples,
15868
+ timestamp,
15869
+ (x) => x.timestamp
15870
+ );
15871
+ if (index === -1 && this.demuxer.loadedSamples.length > 0) {
15872
+ return null;
15873
+ }
15874
+ if (this.demuxer.lastLoadedPos === this.demuxer.fileSize) {
15875
+ return this.getPacketAtIndex(index, options);
15876
+ }
15877
+ if (index >= 0 && index + 1 < this.demuxer.loadedSamples.length) {
15878
+ return this.getPacketAtIndex(index, options);
15879
+ }
15880
+ await this.demuxer.loadNextChunk();
15881
+ }
15819
15882
  }
15820
15883
  getKeyPacket(timestamp, options) {
15821
15884
  return this.getPacket(timestamp, options);
@@ -16615,7 +16678,7 @@ ${cue.notes ?? ""}`;
16615
16678
  break;
16616
16679
  case 17026 /* DocType */:
16617
16680
  {
16618
- const docType = ebmlReader.readString(size);
16681
+ const docType = ebmlReader.readAsciiString(size);
16619
16682
  if (docType !== desiredDocType) {
16620
16683
  return false;
16621
16684
  }
@@ -16856,6 +16919,70 @@ ${cue.notes ?? ""}`;
16856
16919
  };
16857
16920
 
16858
16921
  // src/conversion.ts
16922
+ var validateVideoOptions = (videoOptions) => {
16923
+ if (videoOptions !== void 0 && (!videoOptions || typeof videoOptions !== "object")) {
16924
+ throw new TypeError("options.video, when provided, must be an object.");
16925
+ }
16926
+ if (videoOptions?.discard !== void 0 && typeof videoOptions.discard !== "boolean") {
16927
+ throw new TypeError("options.video.discard, when provided, must be a boolean.");
16928
+ }
16929
+ if (videoOptions?.forceTranscode !== void 0 && typeof videoOptions.forceTranscode !== "boolean") {
16930
+ throw new TypeError("options.video.forceTranscode, when provided, must be a boolean.");
16931
+ }
16932
+ if (videoOptions?.codec !== void 0 && !VIDEO_CODECS.includes(videoOptions.codec)) {
16933
+ throw new TypeError(
16934
+ `options.video.codec, when provided, must be one of: ${VIDEO_CODECS.join(", ")}.`
16935
+ );
16936
+ }
16937
+ if (videoOptions?.bitrate !== void 0 && !(videoOptions.bitrate instanceof Quality) && (!Number.isInteger(videoOptions.bitrate) || videoOptions.bitrate <= 0)) {
16938
+ throw new TypeError("options.video.bitrate, when provided, must be a positive integer or a quality.");
16939
+ }
16940
+ if (videoOptions?.width !== void 0 && (!Number.isInteger(videoOptions.width) || videoOptions.width <= 0)) {
16941
+ throw new TypeError("options.video.width, when provided, must be a positive integer.");
16942
+ }
16943
+ if (videoOptions?.height !== void 0 && (!Number.isInteger(videoOptions.height) || videoOptions.height <= 0)) {
16944
+ throw new TypeError("options.video.height, when provided, must be a positive integer.");
16945
+ }
16946
+ if (videoOptions?.fit !== void 0 && !["fill", "contain", "cover"].includes(videoOptions.fit)) {
16947
+ throw new TypeError('options.video.fit, when provided, must be one of "fill", "contain", or "cover".');
16948
+ }
16949
+ if (videoOptions?.width !== void 0 && videoOptions.height !== void 0 && videoOptions.fit === void 0) {
16950
+ throw new TypeError(
16951
+ "When both options.video.width and options.video.height are provided, options.video.fit must also be provided."
16952
+ );
16953
+ }
16954
+ if (videoOptions?.rotate !== void 0 && ![0, 90, 180, 270].includes(videoOptions.rotate)) {
16955
+ throw new TypeError("options.video.rotate, when provided, must be 0, 90, 180 or 270.");
16956
+ }
16957
+ if (videoOptions?.frameRate !== void 0 && (!Number.isFinite(videoOptions.frameRate) || videoOptions.frameRate <= 0)) {
16958
+ throw new TypeError("options.video.frameRate, when provided, must be a finite positive number.");
16959
+ }
16960
+ };
16961
+ var validateAudioOptions = (audioOptions) => {
16962
+ if (audioOptions !== void 0 && (!audioOptions || typeof audioOptions !== "object")) {
16963
+ throw new TypeError("options.audio, when provided, must be an object.");
16964
+ }
16965
+ if (audioOptions?.discard !== void 0 && typeof audioOptions.discard !== "boolean") {
16966
+ throw new TypeError("options.audio.discard, when provided, must be a boolean.");
16967
+ }
16968
+ if (audioOptions?.forceTranscode !== void 0 && typeof audioOptions.forceTranscode !== "boolean") {
16969
+ throw new TypeError("options.audio.forceTranscode, when provided, must be a boolean.");
16970
+ }
16971
+ if (audioOptions?.codec !== void 0 && !AUDIO_CODECS.includes(audioOptions.codec)) {
16972
+ throw new TypeError(
16973
+ `options.audio.codec, when provided, must be one of: ${AUDIO_CODECS.join(", ")}.`
16974
+ );
16975
+ }
16976
+ if (audioOptions?.bitrate !== void 0 && !(audioOptions.bitrate instanceof Quality) && (!Number.isInteger(audioOptions.bitrate) || audioOptions.bitrate <= 0)) {
16977
+ throw new TypeError("options.audio.bitrate, when provided, must be a positive integer or a quality.");
16978
+ }
16979
+ if (audioOptions?.numberOfChannels !== void 0 && (!Number.isInteger(audioOptions.numberOfChannels) || audioOptions.numberOfChannels <= 0)) {
16980
+ throw new TypeError("options.audio.numberOfChannels, when provided, must be a positive integer.");
16981
+ }
16982
+ if (audioOptions?.sampleRate !== void 0 && (!Number.isInteger(audioOptions.sampleRate) || audioOptions.sampleRate <= 0)) {
16983
+ throw new TypeError("options.audio.sampleRate, when provided, must be a positive integer.");
16984
+ }
16985
+ };
16859
16986
  var FALLBACK_NUMBER_OF_CHANNELS = 2;
16860
16987
  var FALLBACK_SAMPLE_RATE = 48e3;
16861
16988
  var Conversion = class _Conversion {
@@ -16909,65 +17036,13 @@ ${cue.notes ?? ""}`;
16909
17036
  if (options.output._tracks.length > 0 || options.output.state !== "pending") {
16910
17037
  throw new TypeError("options.output must be fresh: no tracks added and not started.");
16911
17038
  }
16912
- if (options.video !== void 0 && (!options.video || typeof options.video !== "object")) {
16913
- throw new TypeError("options.video, when provided, must be an object.");
16914
- }
16915
- if (options.video?.discard !== void 0 && typeof options.video.discard !== "boolean") {
16916
- throw new TypeError("options.video.discard, when provided, must be a boolean.");
16917
- }
16918
- if (options.video?.forceTranscode !== void 0 && typeof options.video.forceTranscode !== "boolean") {
16919
- throw new TypeError("options.video.forceTranscode, when provided, must be a boolean.");
16920
- }
16921
- if (options.video?.codec !== void 0 && !VIDEO_CODECS.includes(options.video.codec)) {
16922
- throw new TypeError(
16923
- `options.video.codec, when provided, must be one of: ${VIDEO_CODECS.join(", ")}.`
16924
- );
16925
- }
16926
- if (options.video?.bitrate !== void 0 && !(options.video.bitrate instanceof Quality) && (!Number.isInteger(options.video.bitrate) || options.video.bitrate <= 0)) {
16927
- throw new TypeError("options.video.bitrate, when provided, must be a positive integer or a quality.");
16928
- }
16929
- if (options.video?.width !== void 0 && (!Number.isInteger(options.video.width) || options.video.width <= 0)) {
16930
- throw new TypeError("options.video.width, when provided, must be a positive integer.");
16931
- }
16932
- if (options.video?.height !== void 0 && (!Number.isInteger(options.video.height) || options.video.height <= 0)) {
16933
- throw new TypeError("options.video.height, when provided, must be a positive integer.");
16934
- }
16935
- if (options.video?.fit !== void 0 && !["fill", "contain", "cover"].includes(options.video.fit)) {
16936
- throw new TypeError('options.video.fit, when provided, must be one of "fill", "contain", or "cover".');
16937
- }
16938
- if (options.video?.width !== void 0 && options.video.height !== void 0 && options.video.fit === void 0) {
16939
- throw new TypeError(
16940
- "When both options.video.width and options.video.height are provided, options.video.fit must also be provided."
16941
- );
16942
- }
16943
- if (options.video?.rotate !== void 0 && ![0, 90, 180, 270].includes(options.video.rotate)) {
16944
- throw new TypeError("options.video.rotate, when provided, must be 0, 90, 180 or 270.");
16945
- }
16946
- if (options.video?.frameRate !== void 0 && (!Number.isFinite(options.video.frameRate) || options.video.frameRate <= 0)) {
16947
- throw new TypeError("options.video.frameRate, when provided, must be a finite positive number.");
16948
- }
16949
- if (options.audio !== void 0 && (!options.audio || typeof options.audio !== "object")) {
16950
- throw new TypeError("options.audio, when provided, must be an object.");
16951
- }
16952
- if (options.audio?.discard !== void 0 && typeof options.audio.discard !== "boolean") {
16953
- throw new TypeError("options.audio.discard, when provided, must be a boolean.");
16954
- }
16955
- if (options.audio?.forceTranscode !== void 0 && typeof options.audio.forceTranscode !== "boolean") {
16956
- throw new TypeError("options.audio.forceTranscode, when provided, must be a boolean.");
16957
- }
16958
- if (options.audio?.codec !== void 0 && !AUDIO_CODECS.includes(options.audio.codec)) {
16959
- throw new TypeError(
16960
- `options.audio.codec, when provided, must be one of: ${AUDIO_CODECS.join(", ")}.`
16961
- );
16962
- }
16963
- if (options.audio?.bitrate !== void 0 && !(options.audio.bitrate instanceof Quality) && (!Number.isInteger(options.audio.bitrate) || options.audio.bitrate <= 0)) {
16964
- throw new TypeError("options.audio.bitrate, when provided, must be a positive integer or a quality.");
16965
- }
16966
- if (options.audio?.numberOfChannels !== void 0 && (!Number.isInteger(options.audio.numberOfChannels) || options.audio.numberOfChannels <= 0)) {
16967
- throw new TypeError("options.audio.numberOfChannels, when provided, must be a positive integer.");
17039
+ if (typeof options.video !== "function") {
17040
+ validateVideoOptions(options.video);
17041
+ } else {
16968
17042
  }
16969
- if (options.audio?.sampleRate !== void 0 && (!Number.isInteger(options.audio.sampleRate) || options.audio.sampleRate <= 0)) {
16970
- throw new TypeError("options.audio.sampleRate, when provided, must be a positive integer.");
17043
+ if (typeof options.audio !== "function") {
17044
+ validateAudioOptions(options.audio);
17045
+ } else {
16971
17046
  }
16972
17047
  if (options.trim !== void 0 && (!options.trim || typeof options.trim !== "object")) {
16973
17048
  throw new TypeError("options.trim, when provided, must be an object.");
@@ -17000,15 +17075,34 @@ ${cue.notes ?? ""}`;
17000
17075
  async _init() {
17001
17076
  const inputTracks = await this.input.getTracks();
17002
17077
  const outputTrackCounts = this.output.format.getSupportedTrackCounts();
17078
+ let nVideo = 1;
17079
+ let nAudio = 1;
17003
17080
  for (const track of inputTracks) {
17004
- if (track.isVideoTrack() && this._options.video?.discard) {
17005
- this.discardedTracks.push({
17006
- track,
17007
- reason: "discarded_by_user"
17008
- });
17009
- continue;
17081
+ let trackOptions = void 0;
17082
+ if (track.isVideoTrack()) {
17083
+ if (this._options.video) {
17084
+ if (typeof this._options.video === "function") {
17085
+ trackOptions = await this._options.video(track, nVideo);
17086
+ validateVideoOptions(trackOptions);
17087
+ nVideo++;
17088
+ } else {
17089
+ trackOptions = this._options.video;
17090
+ }
17091
+ }
17092
+ } else if (track.isAudioTrack()) {
17093
+ if (this._options.audio) {
17094
+ if (typeof this._options.audio === "function") {
17095
+ trackOptions = await this._options.audio(track, nAudio);
17096
+ validateAudioOptions(trackOptions);
17097
+ nAudio++;
17098
+ } else {
17099
+ trackOptions = this._options.audio;
17100
+ }
17101
+ }
17102
+ } else {
17103
+ assert(false);
17010
17104
  }
17011
- if (track.isAudioTrack() && this._options.audio?.discard) {
17105
+ if (trackOptions?.discard) {
17012
17106
  this.discardedTracks.push({
17013
17107
  track,
17014
17108
  reason: "discarded_by_user"
@@ -17030,9 +17124,9 @@ ${cue.notes ?? ""}`;
17030
17124
  continue;
17031
17125
  }
17032
17126
  if (track.isVideoTrack()) {
17033
- await this._processVideoTrack(track);
17127
+ await this._processVideoTrack(track, trackOptions ?? {});
17034
17128
  } else if (track.isAudioTrack()) {
17035
- await this._processAudioTrack(track);
17129
+ await this._processAudioTrack(track, trackOptions ?? {});
17036
17130
  }
17037
17131
  }
17038
17132
  const unintentionallyDiscardedTracks = this.discardedTracks.filter((x) => x.reason !== "discarded_by_user");
@@ -17086,7 +17180,7 @@ ${cue.notes ?? ""}`;
17086
17180
  await this.output.cancel();
17087
17181
  }
17088
17182
  /** @internal */
17089
- async _processVideoTrack(track) {
17183
+ async _processVideoTrack(track, trackOptions) {
17090
17184
  const sourceCodec = track.codec;
17091
17185
  if (!sourceCodec) {
17092
17186
  this.discardedTracks.push({
@@ -17096,28 +17190,28 @@ ${cue.notes ?? ""}`;
17096
17190
  return;
17097
17191
  }
17098
17192
  let videoSource;
17099
- const totalRotation = normalizeRotation(track.rotation + (this._options.video?.rotate ?? 0));
17193
+ const totalRotation = normalizeRotation(track.rotation + (trackOptions.rotate ?? 0));
17100
17194
  const outputSupportsRotation = this.output.format.supportsVideoRotationMetadata;
17101
17195
  const [originalWidth, originalHeight] = totalRotation % 180 === 0 ? [track.codedWidth, track.codedHeight] : [track.codedHeight, track.codedWidth];
17102
17196
  let width = originalWidth;
17103
17197
  let height = originalHeight;
17104
17198
  const aspectRatio = width / height;
17105
17199
  const ceilToMultipleOfTwo = (value) => Math.ceil(value / 2) * 2;
17106
- if (this._options.video?.width !== void 0 && this._options.video.height === void 0) {
17107
- width = ceilToMultipleOfTwo(this._options.video.width);
17200
+ if (trackOptions.width !== void 0 && trackOptions.height === void 0) {
17201
+ width = ceilToMultipleOfTwo(trackOptions.width);
17108
17202
  height = ceilToMultipleOfTwo(Math.round(width / aspectRatio));
17109
- } else if (this._options.video?.width === void 0 && this._options.video?.height !== void 0) {
17110
- height = ceilToMultipleOfTwo(this._options.video.height);
17203
+ } else if (trackOptions.width === void 0 && trackOptions.height !== void 0) {
17204
+ height = ceilToMultipleOfTwo(trackOptions.height);
17111
17205
  width = ceilToMultipleOfTwo(Math.round(height * aspectRatio));
17112
- } else if (this._options.video?.width !== void 0 && this._options.video.height !== void 0) {
17113
- width = ceilToMultipleOfTwo(this._options.video.width);
17114
- height = ceilToMultipleOfTwo(this._options.video.height);
17206
+ } else if (trackOptions.width !== void 0 && trackOptions.height !== void 0) {
17207
+ width = ceilToMultipleOfTwo(trackOptions.width);
17208
+ height = ceilToMultipleOfTwo(trackOptions.height);
17115
17209
  }
17116
17210
  const firstTimestamp = await track.getFirstTimestamp();
17117
- const needsTranscode = !!this._options.video?.forceTranscode || this._startTimestamp > 0 || firstTimestamp < 0 || !!this._options.video?.frameRate;
17211
+ const needsTranscode = !!trackOptions.forceTranscode || this._startTimestamp > 0 || firstTimestamp < 0 || !!trackOptions.frameRate;
17118
17212
  const needsRerender = width !== originalWidth || height !== originalHeight || totalRotation !== 0 && !outputSupportsRotation;
17119
17213
  let videoCodecs = this.output.format.getSupportedVideoCodecs();
17120
- if (!needsTranscode && !this._options.video?.bitrate && !needsRerender && videoCodecs.includes(sourceCodec) && (!this._options.video?.codec || this._options.video?.codec === sourceCodec)) {
17214
+ if (!needsTranscode && !trackOptions.bitrate && !needsRerender && videoCodecs.includes(sourceCodec) && (!trackOptions.codec || trackOptions.codec === sourceCodec)) {
17121
17215
  const source = new EncodedVideoPacketSource(sourceCodec);
17122
17216
  videoSource = source;
17123
17217
  this._trackPromises.push((async () => {
@@ -17148,10 +17242,10 @@ ${cue.notes ?? ""}`;
17148
17242
  });
17149
17243
  return;
17150
17244
  }
17151
- if (this._options.video?.codec) {
17152
- videoCodecs = videoCodecs.filter((codec) => codec === this._options.video?.codec);
17245
+ if (trackOptions.codec) {
17246
+ videoCodecs = videoCodecs.filter((codec) => codec === trackOptions.codec);
17153
17247
  }
17154
- const bitrate = this._options.video?.bitrate ?? QUALITY_HIGH;
17248
+ const bitrate = trackOptions.bitrate ?? QUALITY_HIGH;
17155
17249
  const encodableCodec = await getFirstEncodableVideoCodec(videoCodecs, { width, height, bitrate });
17156
17250
  if (!encodableCodec) {
17157
17251
  this.discardedTracks.push({
@@ -17173,13 +17267,13 @@ ${cue.notes ?? ""}`;
17173
17267
  const sink = new CanvasSink(track, {
17174
17268
  width,
17175
17269
  height,
17176
- fit: this._options.video?.fit ?? "fill",
17270
+ fit: trackOptions.fit ?? "fill",
17177
17271
  rotation: totalRotation,
17178
17272
  // Bake the rotation into the output
17179
17273
  poolSize: 1
17180
17274
  });
17181
17275
  const iterator = sink.canvases(this._startTimestamp, this._endTimestamp);
17182
- const frameRate = this._options.video?.frameRate;
17276
+ const frameRate = trackOptions.frameRate;
17183
17277
  let lastCanvas = null;
17184
17278
  let lastCanvasTimestamp = null;
17185
17279
  let lastCanvasEndTimestamp = null;
@@ -17241,7 +17335,7 @@ ${cue.notes ?? ""}`;
17241
17335
  this._trackPromises.push((async () => {
17242
17336
  await this._started;
17243
17337
  const sink = new VideoSampleSink(track);
17244
- const frameRate = this._options.video?.frameRate;
17338
+ const frameRate = trackOptions.frameRate;
17245
17339
  let lastSample = null;
17246
17340
  let lastSampleTimestamp = null;
17247
17341
  let lastSampleEndTimestamp = null;
@@ -17301,7 +17395,7 @@ ${cue.notes ?? ""}`;
17301
17395
  }
17302
17396
  }
17303
17397
  this.output.addVideoTrack(videoSource, {
17304
- frameRate: this._options.video?.frameRate,
17398
+ frameRate: trackOptions.frameRate,
17305
17399
  languageCode: track.languageCode,
17306
17400
  rotation: needsRerender ? 0 : totalRotation
17307
17401
  // Rerendering will bake the rotation into the output
@@ -17311,7 +17405,7 @@ ${cue.notes ?? ""}`;
17311
17405
  this.utilizedTracks.push(track);
17312
17406
  }
17313
17407
  /** @internal */
17314
- async _processAudioTrack(track) {
17408
+ async _processAudioTrack(track, trackOptions) {
17315
17409
  const sourceCodec = track.codec;
17316
17410
  if (!sourceCodec) {
17317
17411
  this.discardedTracks.push({
@@ -17324,11 +17418,11 @@ ${cue.notes ?? ""}`;
17324
17418
  const originalNumberOfChannels = track.numberOfChannels;
17325
17419
  const originalSampleRate = track.sampleRate;
17326
17420
  const firstTimestamp = await track.getFirstTimestamp();
17327
- let numberOfChannels = this._options.audio?.numberOfChannels ?? originalNumberOfChannels;
17328
- let sampleRate = this._options.audio?.sampleRate ?? originalSampleRate;
17421
+ let numberOfChannels = trackOptions.numberOfChannels ?? originalNumberOfChannels;
17422
+ let sampleRate = trackOptions.sampleRate ?? originalSampleRate;
17329
17423
  let needsResample = numberOfChannels !== originalNumberOfChannels || sampleRate !== originalSampleRate || this._startTimestamp > 0 || firstTimestamp < 0;
17330
17424
  let audioCodecs = this.output.format.getSupportedAudioCodecs();
17331
- if (!this._options.audio?.forceTranscode && !this._options.audio?.bitrate && !needsResample && audioCodecs.includes(sourceCodec) && (!this._options.audio?.codec || this._options.audio.codec === sourceCodec)) {
17425
+ if (!trackOptions.forceTranscode && !trackOptions.bitrate && !needsResample && audioCodecs.includes(sourceCodec) && (!trackOptions.codec || trackOptions.codec === sourceCodec)) {
17332
17426
  const source = new EncodedAudioPacketSource(sourceCodec);
17333
17427
  audioSource = source;
17334
17428
  this._trackPromises.push((async () => {
@@ -17360,10 +17454,10 @@ ${cue.notes ?? ""}`;
17360
17454
  return;
17361
17455
  }
17362
17456
  let codecOfChoice = null;
17363
- if (this._options.audio?.codec) {
17364
- audioCodecs = audioCodecs.filter((codec) => codec === this._options.audio.codec);
17457
+ if (trackOptions.codec) {
17458
+ audioCodecs = audioCodecs.filter((codec) => codec === trackOptions.codec);
17365
17459
  }
17366
- const bitrate = this._options.audio?.bitrate ?? QUALITY_HIGH;
17460
+ const bitrate = trackOptions.bitrate ?? QUALITY_HIGH;
17367
17461
  const encodableCodecs = await getEncodableAudioCodecs(audioCodecs, {
17368
17462
  numberOfChannels,
17369
17463
  sampleRate,