mediabunny 1.8.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/bundles/mediabunny.cjs +547 -123
  2. package/dist/bundles/mediabunny.min.cjs +5 -5
  3. package/dist/bundles/mediabunny.min.mjs +4 -4
  4. package/dist/bundles/mediabunny.mjs +547 -123
  5. package/dist/mediabunny.d.ts +27 -0
  6. package/dist/modules/src/adts/adts-demuxer.d.ts +38 -0
  7. package/dist/modules/src/adts/adts-demuxer.d.ts.map +1 -0
  8. package/dist/modules/src/adts/adts-demuxer.js +221 -0
  9. package/dist/modules/src/adts/adts-muxer.d.ts +26 -0
  10. package/dist/modules/src/adts/adts-muxer.d.ts.map +1 -0
  11. package/dist/modules/src/adts/adts-muxer.js +79 -0
  12. package/dist/modules/src/adts/adts-reader.d.ts +26 -0
  13. package/dist/modules/src/adts/adts-reader.d.ts.map +1 -0
  14. package/dist/modules/src/adts/adts-reader.js +72 -0
  15. package/dist/modules/src/codec.d.ts +4 -1
  16. package/dist/modules/src/codec.d.ts.map +1 -1
  17. package/dist/modules/src/codec.js +8 -16
  18. package/dist/modules/src/index.d.ts +1 -1
  19. package/dist/modules/src/index.d.ts.map +1 -1
  20. package/dist/modules/src/index.js +1 -1
  21. package/dist/modules/src/input-format.d.ts +13 -0
  22. package/dist/modules/src/input-format.d.ts.map +1 -1
  23. package/dist/modules/src/input-format.js +48 -1
  24. package/dist/modules/src/isobmff/isobmff-demuxer.js +1 -1
  25. package/dist/modules/src/misc.d.ts +1 -0
  26. package/dist/modules/src/misc.d.ts.map +1 -1
  27. package/dist/modules/src/misc.js +13 -0
  28. package/dist/modules/src/mp3/mp3-demuxer.d.ts +1 -1
  29. package/dist/modules/src/mp3/mp3-demuxer.d.ts.map +1 -1
  30. package/dist/modules/src/mp3/mp3-demuxer.js +51 -49
  31. package/dist/modules/src/output-format.d.ts +25 -0
  32. package/dist/modules/src/output-format.d.ts.map +1 -1
  33. package/dist/modules/src/output-format.js +45 -0
  34. package/dist/modules/src/tsconfig.tsbuildinfo +1 -1
  35. package/package.json +1 -1
  36. package/src/adts/adts-demuxer.ts +312 -0
  37. package/src/adts/adts-muxer.ts +109 -0
  38. package/src/adts/adts-reader.ts +96 -0
  39. package/src/codec.ts +19 -17
  40. package/src/index.ts +2 -0
  41. package/src/input-format.ts +56 -1
  42. package/src/isobmff/isobmff-demuxer.ts +1 -1
  43. package/src/misc.ts +16 -0
  44. package/src/mp3/mp3-demuxer.ts +64 -62
  45. package/src/output-format.ts +72 -0
@@ -31,6 +31,7 @@ var Mediabunny = (() => {
31
31
  ALL_FORMATS: () => ALL_FORMATS,
32
32
  ALL_TRACK_TYPES: () => ALL_TRACK_TYPES,
33
33
  AUDIO_CODECS: () => AUDIO_CODECS,
34
+ AdtsOutputFormat: () => AdtsOutputFormat,
34
35
  AudioBufferSink: () => AudioBufferSink,
35
36
  AudioBufferSource: () => AudioBufferSource,
36
37
  AudioSample: () => AudioSample,
@@ -169,6 +170,18 @@ var Mediabunny = (() => {
169
170
  }
170
171
  return result;
171
172
  }
173
+ writeBits(n, value) {
174
+ const end = this.pos + n;
175
+ for (let i = this.pos; i < end; i++) {
176
+ const byteIndex = Math.floor(i / 8);
177
+ let byte = this.bytes[byteIndex];
178
+ const bitIndex = 7 - (i & 7);
179
+ byte &= ~(1 << bitIndex);
180
+ byte |= (value & 1 << end - i - 1) >> end - i - 1 << bitIndex;
181
+ this.bytes[byteIndex] = byte;
182
+ }
183
+ this.pos = end;
184
+ }
172
185
  readAlignedByte() {
173
186
  if (this.pos % 8 !== 0) {
174
187
  throw new Error("Bitstream is not byte-aligned.");
@@ -1098,6 +1111,22 @@ var Mediabunny = (() => {
1098
1111
  }
1099
1112
  throw new TypeError(`Unhandled codec '${codec}'.`);
1100
1113
  };
1114
+ var aacFrequencyTable = [
1115
+ 96e3,
1116
+ 88200,
1117
+ 64e3,
1118
+ 48e3,
1119
+ 44100,
1120
+ 32e3,
1121
+ 24e3,
1122
+ 22050,
1123
+ 16e3,
1124
+ 12e3,
1125
+ 11025,
1126
+ 8e3,
1127
+ 7350
1128
+ ];
1129
+ var aacChannelMap = [-1, 1, 2, 3, 4, 5, 6, 8];
1101
1130
  var parseAacAudioSpecificConfig = (bytes2) => {
1102
1131
  if (!bytes2 || bytes2.byteLength < 2) {
1103
1132
  throw new TypeError("AAC description must be at least 2 bytes long.");
@@ -1112,38 +1141,14 @@ var Mediabunny = (() => {
1112
1141
  if (frequencyIndex === 15) {
1113
1142
  sampleRate = bitstream.readBits(24);
1114
1143
  } else {
1115
- const freqTable = [
1116
- 96e3,
1117
- 88200,
1118
- 64e3,
1119
- 48e3,
1120
- 44100,
1121
- 32e3,
1122
- 24e3,
1123
- 22050,
1124
- 16e3,
1125
- 12e3,
1126
- 11025,
1127
- 8e3,
1128
- 7350
1129
- ];
1130
- if (frequencyIndex < freqTable.length) {
1131
- sampleRate = freqTable[frequencyIndex];
1144
+ if (frequencyIndex < aacFrequencyTable.length) {
1145
+ sampleRate = aacFrequencyTable[frequencyIndex];
1132
1146
  }
1133
1147
  }
1134
1148
  const channelConfiguration = bitstream.readBits(4);
1135
1149
  let numberOfChannels = null;
1136
1150
  if (channelConfiguration >= 1 && channelConfiguration <= 7) {
1137
- const channelMap = {
1138
- 1: 1,
1139
- 2: 2,
1140
- 3: 3,
1141
- 4: 4,
1142
- 5: 5,
1143
- 6: 6,
1144
- 7: 8
1145
- };
1146
- numberOfChannels = channelMap[channelConfiguration];
1151
+ numberOfChannels = aacChannelMap[channelConfiguration];
1147
1152
  }
1148
1153
  return {
1149
1154
  objectType,
@@ -1695,6 +1700,119 @@ var Mediabunny = (() => {
1695
1700
  return null;
1696
1701
  };
1697
1702
 
1703
+ // src/muxer.ts
1704
+ var Muxer = class {
1705
+ constructor(output) {
1706
+ this.mutex = new AsyncMutex();
1707
+ /**
1708
+ * This field is used to synchronize multiple MediaStreamTracks. They use the same time coordinate system across
1709
+ * tracks, and to ensure correct audio-video sync, we must use the same offset for all of them. The reason an offset
1710
+ * is needed at all is because the timestamps typically don't start at zero.
1711
+ */
1712
+ this.firstMediaStreamTimestamp = null;
1713
+ this.trackTimestampInfo = /* @__PURE__ */ new WeakMap();
1714
+ this.output = output;
1715
+ }
1716
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1717
+ onTrackClose(track) {
1718
+ }
1719
+ validateAndNormalizeTimestamp(track, timestampInSeconds, isKeyFrame) {
1720
+ timestampInSeconds += track.source._timestampOffset;
1721
+ let timestampInfo = this.trackTimestampInfo.get(track);
1722
+ if (!timestampInfo) {
1723
+ if (!isKeyFrame) {
1724
+ throw new Error("First frame must be a key frame.");
1725
+ }
1726
+ timestampInfo = {
1727
+ maxTimestamp: timestampInSeconds,
1728
+ maxTimestampBeforeLastKeyFrame: timestampInSeconds
1729
+ };
1730
+ this.trackTimestampInfo.set(track, timestampInfo);
1731
+ }
1732
+ if (timestampInSeconds < 0) {
1733
+ throw new Error(`Timestamps must be non-negative (got ${timestampInSeconds}s).`);
1734
+ }
1735
+ if (isKeyFrame) {
1736
+ timestampInfo.maxTimestampBeforeLastKeyFrame = timestampInfo.maxTimestamp;
1737
+ }
1738
+ if (timestampInSeconds < timestampInfo.maxTimestampBeforeLastKeyFrame) {
1739
+ throw new Error(
1740
+ `Timestamps cannot be smaller than the highest timestamp of the previous run (a run begins with a key frame and ends right before the next key frame). Got ${timestampInSeconds}s, but highest timestamp is ${timestampInfo.maxTimestampBeforeLastKeyFrame}s.`
1741
+ );
1742
+ }
1743
+ timestampInfo.maxTimestamp = Math.max(timestampInfo.maxTimestamp, timestampInSeconds);
1744
+ return timestampInSeconds;
1745
+ }
1746
+ };
1747
+
1748
+ // src/adts/adts-muxer.ts
1749
+ var AdtsMuxer = class extends Muxer {
1750
+ constructor(output, format) {
1751
+ super(output);
1752
+ this.header = new Uint8Array(7);
1753
+ this.headerBitstream = new Bitstream(this.header);
1754
+ this.audioSpecificConfig = null;
1755
+ this.format = format;
1756
+ this.writer = output._writer;
1757
+ }
1758
+ async start() {
1759
+ }
1760
+ async getMimeType() {
1761
+ return "audio/aac";
1762
+ }
1763
+ async addEncodedVideoPacket() {
1764
+ throw new Error("ADTS does not support video.");
1765
+ }
1766
+ async addEncodedAudioPacket(track, packet, meta) {
1767
+ const release = await this.mutex.acquire();
1768
+ try {
1769
+ if (!this.audioSpecificConfig) {
1770
+ validateAudioChunkMetadata(meta);
1771
+ const description = meta?.decoderConfig?.description;
1772
+ assert(description);
1773
+ this.audioSpecificConfig = parseAacAudioSpecificConfig(toUint8Array(description));
1774
+ const { objectType, frequencyIndex, channelConfiguration } = this.audioSpecificConfig;
1775
+ const profile = objectType - 1;
1776
+ this.headerBitstream.writeBits(12, 4095);
1777
+ this.headerBitstream.writeBits(1, 0);
1778
+ this.headerBitstream.writeBits(2, 0);
1779
+ this.headerBitstream.writeBits(1, 1);
1780
+ this.headerBitstream.writeBits(2, profile);
1781
+ this.headerBitstream.writeBits(4, frequencyIndex);
1782
+ this.headerBitstream.writeBits(1, 0);
1783
+ this.headerBitstream.writeBits(3, channelConfiguration);
1784
+ this.headerBitstream.writeBits(1, 0);
1785
+ this.headerBitstream.writeBits(1, 0);
1786
+ this.headerBitstream.writeBits(1, 0);
1787
+ this.headerBitstream.writeBits(1, 0);
1788
+ this.headerBitstream.skipBits(13);
1789
+ this.headerBitstream.writeBits(11, 2047);
1790
+ this.headerBitstream.writeBits(2, 0);
1791
+ }
1792
+ const frameLength = packet.data.byteLength + this.header.byteLength;
1793
+ this.headerBitstream.pos = 30;
1794
+ this.headerBitstream.writeBits(13, frameLength);
1795
+ const startPos = this.writer.getPos();
1796
+ this.writer.write(this.header);
1797
+ this.writer.write(packet.data);
1798
+ if (this.format._options.onFrame) {
1799
+ const frameBytes = new Uint8Array(frameLength);
1800
+ frameBytes.set(this.header, 0);
1801
+ frameBytes.set(packet.data, this.header.byteLength);
1802
+ this.format._options.onFrame(frameBytes, startPos);
1803
+ }
1804
+ await this.writer.flush();
1805
+ } finally {
1806
+ release();
1807
+ }
1808
+ }
1809
+ async addSubtitleCue() {
1810
+ throw new Error("ADTS does not support subtitles.");
1811
+ }
1812
+ async finalize() {
1813
+ }
1814
+ };
1815
+
1698
1816
  // src/subtitles.ts
1699
1817
  var cueBlockHeaderRegex = /(?:(.+?)\n)?((?:\d{2}:)?\d{2}:\d{2}.\d{3})\s+-->\s+((?:\d{2}:)?\d{2}:\d{2}.\d{3})/g;
1700
1818
  var preambleStartRegex = /^WEBVTT(.|\n)*?\n{2}/;
@@ -4064,51 +4182,6 @@ var Mediabunny = (() => {
4064
4182
  webvtt: vttC
4065
4183
  };
4066
4184
 
4067
- // src/muxer.ts
4068
- var Muxer = class {
4069
- constructor(output) {
4070
- this.mutex = new AsyncMutex();
4071
- /**
4072
- * This field is used to synchronize multiple MediaStreamTracks. They use the same time coordinate system across
4073
- * tracks, and to ensure correct audio-video sync, we must use the same offset for all of them. The reason an offset
4074
- * is needed at all is because the timestamps typically don't start at zero.
4075
- */
4076
- this.firstMediaStreamTimestamp = null;
4077
- this.trackTimestampInfo = /* @__PURE__ */ new WeakMap();
4078
- this.output = output;
4079
- }
4080
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
4081
- onTrackClose(track) {
4082
- }
4083
- validateAndNormalizeTimestamp(track, timestampInSeconds, isKeyFrame) {
4084
- timestampInSeconds += track.source._timestampOffset;
4085
- let timestampInfo = this.trackTimestampInfo.get(track);
4086
- if (!timestampInfo) {
4087
- if (!isKeyFrame) {
4088
- throw new Error("First frame must be a key frame.");
4089
- }
4090
- timestampInfo = {
4091
- maxTimestamp: timestampInSeconds,
4092
- maxTimestampBeforeLastKeyFrame: timestampInSeconds
4093
- };
4094
- this.trackTimestampInfo.set(track, timestampInfo);
4095
- }
4096
- if (timestampInSeconds < 0) {
4097
- throw new Error(`Timestamps must be non-negative (got ${timestampInSeconds}s).`);
4098
- }
4099
- if (isKeyFrame) {
4100
- timestampInfo.maxTimestampBeforeLastKeyFrame = timestampInfo.maxTimestamp;
4101
- }
4102
- if (timestampInSeconds < timestampInfo.maxTimestampBeforeLastKeyFrame) {
4103
- throw new Error(
4104
- `Timestamps cannot be smaller than the highest timestamp of the previous run (a run begins with a key frame and ends right before the next key frame). Got ${timestampInSeconds}s, but highest timestamp is ${timestampInfo.maxTimestampBeforeLastKeyFrame}s.`
4105
- );
4106
- }
4107
- timestampInfo.maxTimestamp = Math.max(timestampInfo.maxTimestamp, timestampInSeconds);
4108
- return timestampInSeconds;
4109
- }
4110
- };
4111
-
4112
4185
  // src/writer.ts
4113
4186
  var Writer = class {
4114
4187
  constructor() {
@@ -10625,6 +10698,46 @@ ${cue.notes ?? ""}`;
10625
10698
  return false;
10626
10699
  }
10627
10700
  };
10701
+ var AdtsOutputFormat = class extends OutputFormat {
10702
+ constructor(options = {}) {
10703
+ if (!options || typeof options !== "object") {
10704
+ throw new TypeError("options must be an object.");
10705
+ }
10706
+ if (options.onFrame !== void 0 && typeof options.onFrame !== "function") {
10707
+ throw new TypeError("options.onFrame, when provided, must be a function.");
10708
+ }
10709
+ super();
10710
+ this._options = options;
10711
+ }
10712
+ /** @internal */
10713
+ _createMuxer(output) {
10714
+ return new AdtsMuxer(output, this);
10715
+ }
10716
+ /** @internal */
10717
+ get _name() {
10718
+ return "ADTS";
10719
+ }
10720
+ getSupportedTrackCounts() {
10721
+ return {
10722
+ video: { min: 0, max: 0 },
10723
+ audio: { min: 1, max: 1 },
10724
+ subtitle: { min: 0, max: 0 },
10725
+ total: { min: 1, max: 1 }
10726
+ };
10727
+ }
10728
+ get fileExtension() {
10729
+ return ".aac";
10730
+ }
10731
+ get mimeType() {
10732
+ return "audio/aac";
10733
+ }
10734
+ getSupportedCodecs() {
10735
+ return ["aac"];
10736
+ }
10737
+ get supportsVideoRotationMetadata() {
10738
+ return false;
10739
+ }
10740
+ };
10628
10741
 
10629
10742
  // src/media-source.ts
10630
10743
  var MediaSource = class {
@@ -12954,7 +13067,7 @@ ${cue.notes ?? ""}`;
12954
13067
  const chromaSubsamplingX = thirdByte >> 3 & 1;
12955
13068
  const chromaSubsamplingY = thirdByte >> 2 & 1;
12956
13069
  const chromaSamplePosition = thirdByte & 3;
12957
- const bitDepth = profile == 2 && highBitDepth ? twelveBit ? 12 : 10 : highBitDepth ? 10 : 8;
13070
+ const bitDepth = profile === 2 && highBitDepth ? twelveBit ? 12 : 10 : highBitDepth ? 10 : 8;
12958
13071
  track.info.av1CodecInfo = {
12959
13072
  profile,
12960
13073
  level,
@@ -15764,7 +15877,7 @@ ${cue.notes ?? ""}`;
15764
15877
  this.loadedSamples = [];
15765
15878
  // All samples from the start of the file to lastLoadedPos
15766
15879
  this.tracks = [];
15767
- this.loadingMutex = new AsyncMutex();
15880
+ this.readingMutex = new AsyncMutex();
15768
15881
  this.lastLoadedPos = 0;
15769
15882
  this.fileSize = 0;
15770
15883
  this.nextTimestampInSamples = 0;
@@ -15777,32 +15890,25 @@ ${cue.notes ?? ""}`;
15777
15890
  while (!this.firstFrameHeader && this.lastLoadedPos < this.fileSize) {
15778
15891
  await this.loadNextChunk();
15779
15892
  }
15780
- if (!this.firstFrameHeader) {
15781
- throw new Error("No MP3 frames found.");
15782
- }
15893
+ assert(this.firstFrameHeader);
15783
15894
  this.tracks = [new InputAudioTrack(new Mp3AudioTrackBacking(this))];
15784
15895
  })();
15785
15896
  }
15786
15897
  /** Loads the next 0.5 MiB of frames. */
15787
15898
  async loadNextChunk() {
15788
- const release = await this.loadingMutex.acquire();
15789
- try {
15790
- assert(this.lastLoadedPos < this.fileSize);
15791
- const chunkSize = 0.5 * 1024 * 1024;
15792
- const endPos = Math.min(this.lastLoadedPos + chunkSize, this.fileSize);
15793
- await this.reader.reader.loadRange(this.lastLoadedPos, endPos);
15794
- this.lastLoadedPos = endPos;
15795
- assert(this.lastLoadedPos <= this.fileSize);
15796
- if (this.reader.pos === 0) {
15797
- const id3Tag = this.reader.readId3();
15798
- if (id3Tag) {
15799
- this.reader.pos += id3Tag.size;
15800
- }
15801
- }
15802
- this.parseFramesFromLoadedData();
15803
- } finally {
15804
- release();
15899
+ assert(this.lastLoadedPos < this.fileSize);
15900
+ const chunkSize = 0.5 * 1024 * 1024;
15901
+ const endPos = Math.min(this.lastLoadedPos + chunkSize, this.fileSize);
15902
+ await this.reader.reader.loadRange(this.lastLoadedPos, endPos);
15903
+ this.lastLoadedPos = endPos;
15904
+ assert(this.lastLoadedPos <= this.fileSize);
15905
+ if (this.reader.pos === 0) {
15906
+ const id3Tag = this.reader.readId3();
15907
+ if (id3Tag) {
15908
+ this.reader.pos += id3Tag.size;
15909
+ }
15805
15910
  }
15911
+ this.parseFramesFromLoadedData();
15806
15912
  }
15807
15913
  parseFramesFromLoadedData() {
15808
15914
  while (true) {
@@ -15917,43 +16023,50 @@ ${cue.notes ?? ""}`;
15917
16023
  );
15918
16024
  }
15919
16025
  async getFirstPacket(options) {
15920
- while (this.demuxer.loadedSamples.length === 0 && this.demuxer.lastLoadedPos < this.demuxer.fileSize) {
15921
- await this.demuxer.loadNextChunk();
15922
- }
15923
16026
  return this.getPacketAtIndex(0, options);
15924
16027
  }
15925
16028
  async getNextPacket(packet, options) {
15926
- const sampleIndex = binarySearchExact(
15927
- this.demuxer.loadedSamples,
15928
- packet.timestamp,
15929
- (x) => x.timestamp
15930
- );
15931
- if (sampleIndex === -1) {
15932
- throw new Error("Packet was not created from this track.");
15933
- }
15934
- const nextIndex = sampleIndex + 1;
15935
- while (nextIndex >= this.demuxer.loadedSamples.length && this.demuxer.lastLoadedPos < this.demuxer.fileSize) {
15936
- await this.demuxer.loadNextChunk();
15937
- }
15938
- return this.getPacketAtIndex(nextIndex, options);
15939
- }
15940
- async getPacket(timestamp, options) {
15941
- while (true) {
15942
- const index = binarySearchLessOrEqual(
16029
+ const release = await this.demuxer.readingMutex.acquire();
16030
+ try {
16031
+ const sampleIndex = binarySearchExact(
15943
16032
  this.demuxer.loadedSamples,
15944
- timestamp,
16033
+ packet.timestamp,
15945
16034
  (x) => x.timestamp
15946
16035
  );
15947
- if (index === -1 && this.demuxer.loadedSamples.length > 0) {
15948
- return null;
16036
+ if (sampleIndex === -1) {
16037
+ throw new Error("Packet was not created from this track.");
15949
16038
  }
15950
- if (this.demuxer.lastLoadedPos === this.demuxer.fileSize) {
15951
- return this.getPacketAtIndex(index, options);
16039
+ const nextIndex = sampleIndex + 1;
16040
+ while (nextIndex >= this.demuxer.loadedSamples.length && this.demuxer.lastLoadedPos < this.demuxer.fileSize) {
16041
+ await this.demuxer.loadNextChunk();
15952
16042
  }
15953
- if (index >= 0 && index + 1 < this.demuxer.loadedSamples.length) {
15954
- return this.getPacketAtIndex(index, options);
16043
+ return this.getPacketAtIndex(nextIndex, options);
16044
+ } finally {
16045
+ release();
16046
+ }
16047
+ }
16048
+ async getPacket(timestamp, options) {
16049
+ const release = await this.demuxer.readingMutex.acquire();
16050
+ try {
16051
+ while (true) {
16052
+ const index = binarySearchLessOrEqual(
16053
+ this.demuxer.loadedSamples,
16054
+ timestamp,
16055
+ (x) => x.timestamp
16056
+ );
16057
+ if (index === -1 && this.demuxer.loadedSamples.length > 0) {
16058
+ return null;
16059
+ }
16060
+ if (this.demuxer.lastLoadedPos === this.demuxer.fileSize) {
16061
+ return this.getPacketAtIndex(index, options);
16062
+ }
16063
+ if (index >= 0 && index + 1 < this.demuxer.loadedSamples.length) {
16064
+ return this.getPacketAtIndex(index, options);
16065
+ }
16066
+ await this.demuxer.loadNextChunk();
15955
16067
  }
15956
- await this.demuxer.loadNextChunk();
16068
+ } finally {
16069
+ release();
15957
16070
  }
15958
16071
  }
15959
16072
  getKeyPacket(timestamp, options) {
@@ -16659,6 +16772,282 @@ ${cue.notes ?? ""}`;
16659
16772
  return { page: previousPage, segmentIndex: previousPage.lacingValues.length - 1 };
16660
16773
  };
16661
16774
 
16775
+ // src/adts/adts-reader.ts
16776
+ var MAX_FRAME_HEADER_SIZE = 9;
16777
+ var AdtsReader = class {
16778
+ constructor(reader) {
16779
+ this.reader = reader;
16780
+ this.pos = 0;
16781
+ }
16782
+ readBytes(length) {
16783
+ const { view: view2, offset } = this.reader.getViewAndOffset(this.pos, this.pos + length);
16784
+ this.pos += length;
16785
+ return new Uint8Array(view2.buffer, offset, length);
16786
+ }
16787
+ readFrameHeader() {
16788
+ const startPos = this.pos;
16789
+ const bytes2 = this.readBytes(9);
16790
+ const bitstream = new Bitstream(bytes2);
16791
+ const syncword = bitstream.readBits(12);
16792
+ if (syncword !== 4095) {
16793
+ return null;
16794
+ }
16795
+ bitstream.skipBits(1);
16796
+ const layer = bitstream.readBits(2);
16797
+ if (layer !== 0) {
16798
+ return null;
16799
+ }
16800
+ const protectionAbsence = bitstream.readBits(1);
16801
+ const objectType = bitstream.readBits(2) + 1;
16802
+ const samplingFrequencyIndex = bitstream.readBits(4);
16803
+ if (samplingFrequencyIndex === 15) {
16804
+ return null;
16805
+ }
16806
+ bitstream.skipBits(1);
16807
+ const channelConfiguration = bitstream.readBits(3);
16808
+ if (channelConfiguration === 0) {
16809
+ throw new Error("ADTS frames with channel configuration 0 are not supported.");
16810
+ }
16811
+ bitstream.skipBits(1);
16812
+ bitstream.skipBits(1);
16813
+ bitstream.skipBits(1);
16814
+ bitstream.skipBits(1);
16815
+ const frameLength = bitstream.readBits(13);
16816
+ bitstream.skipBits(11);
16817
+ const numberOfAacFrames = bitstream.readBits(2) + 1;
16818
+ if (numberOfAacFrames !== 1) {
16819
+ throw new Error("ADTS frames with more than one AAC frame are not supported.");
16820
+ }
16821
+ let crcCheck = null;
16822
+ if (protectionAbsence === 1) {
16823
+ this.pos -= 2;
16824
+ } else {
16825
+ crcCheck = bitstream.readBits(16);
16826
+ }
16827
+ return {
16828
+ objectType,
16829
+ samplingFrequencyIndex,
16830
+ channelConfiguration,
16831
+ frameLength,
16832
+ numberOfAacFrames,
16833
+ crcCheck,
16834
+ startPos
16835
+ };
16836
+ }
16837
+ };
16838
+
16839
+ // src/adts/adts-demuxer.ts
16840
+ var SAMPLES_PER_AAC_FRAME = 1024;
16841
+ var AdtsDemuxer = class extends Demuxer {
16842
+ constructor(input) {
16843
+ super(input);
16844
+ this.metadataPromise = null;
16845
+ this.firstFrameHeader = null;
16846
+ this.loadedSamples = [];
16847
+ // All samples from the start of the file to lastLoadedPos
16848
+ this.tracks = [];
16849
+ this.readingMutex = new AsyncMutex();
16850
+ this.lastLoadedPos = 0;
16851
+ this.fileSize = 0;
16852
+ this.nextTimestampInSamples = 0;
16853
+ this.reader = new AdtsReader(input._mainReader);
16854
+ }
16855
+ async readMetadata() {
16856
+ return this.metadataPromise ??= (async () => {
16857
+ this.fileSize = await this.input.source.getSize();
16858
+ await this.loadNextChunk();
16859
+ assert(this.firstFrameHeader);
16860
+ this.tracks = [new InputAudioTrack(new AdtsAudioTrackBacking(this))];
16861
+ })();
16862
+ }
16863
+ async loadNextChunk() {
16864
+ assert(this.lastLoadedPos < this.fileSize);
16865
+ const chunkSize = 0.5 * 1024 * 1024;
16866
+ const endPos = Math.min(this.lastLoadedPos + chunkSize, this.fileSize);
16867
+ await this.reader.reader.loadRange(this.lastLoadedPos, endPos);
16868
+ this.lastLoadedPos = endPos;
16869
+ assert(this.lastLoadedPos <= this.fileSize);
16870
+ this.parseFramesFromLoadedData();
16871
+ }
16872
+ parseFramesFromLoadedData() {
16873
+ while (this.reader.pos <= this.fileSize - MAX_FRAME_HEADER_SIZE) {
16874
+ const startPos = this.reader.pos;
16875
+ const header = this.reader.readFrameHeader();
16876
+ if (!header) {
16877
+ break;
16878
+ }
16879
+ if (startPos + header.frameLength > this.lastLoadedPos) {
16880
+ this.reader.pos = startPos;
16881
+ this.lastLoadedPos = startPos;
16882
+ break;
16883
+ }
16884
+ if (!this.firstFrameHeader) {
16885
+ this.firstFrameHeader = header;
16886
+ }
16887
+ const sampleRate = aacFrequencyTable[header.samplingFrequencyIndex];
16888
+ assert(sampleRate !== void 0);
16889
+ const sampleDuration = SAMPLES_PER_AAC_FRAME / sampleRate;
16890
+ const headerSize = header.crcCheck ? MAX_FRAME_HEADER_SIZE : MAX_FRAME_HEADER_SIZE - 2;
16891
+ const sample = {
16892
+ timestamp: this.nextTimestampInSamples / sampleRate,
16893
+ duration: sampleDuration,
16894
+ dataStart: startPos + headerSize,
16895
+ dataSize: header.frameLength - headerSize
16896
+ };
16897
+ this.loadedSamples.push(sample);
16898
+ this.nextTimestampInSamples += SAMPLES_PER_AAC_FRAME;
16899
+ this.reader.pos = startPos + header.frameLength;
16900
+ }
16901
+ }
16902
+ async getMimeType() {
16903
+ return "audio/aac";
16904
+ }
16905
+ async getTracks() {
16906
+ await this.readMetadata();
16907
+ return this.tracks;
16908
+ }
16909
+ async computeDuration() {
16910
+ await this.readMetadata();
16911
+ const track = this.tracks[0];
16912
+ assert(track);
16913
+ return track.computeDuration();
16914
+ }
16915
+ };
16916
+ var AdtsAudioTrackBacking = class {
16917
+ constructor(demuxer) {
16918
+ this.demuxer = demuxer;
16919
+ }
16920
+ getId() {
16921
+ return 1;
16922
+ }
16923
+ async getFirstTimestamp() {
16924
+ return 0;
16925
+ }
16926
+ getTimeResolution() {
16927
+ const sampleRate = this.getSampleRate();
16928
+ return sampleRate / SAMPLES_PER_AAC_FRAME;
16929
+ }
16930
+ async computeDuration() {
16931
+ const lastPacket = await this.getPacket(Infinity, { metadataOnly: true });
16932
+ return (lastPacket?.timestamp ?? 0) + (lastPacket?.duration ?? 0);
16933
+ }
16934
+ getLanguageCode() {
16935
+ return UNDETERMINED_LANGUAGE;
16936
+ }
16937
+ getCodec() {
16938
+ return "aac";
16939
+ }
16940
+ getNumberOfChannels() {
16941
+ assert(this.demuxer.firstFrameHeader);
16942
+ const numberOfChannels = aacChannelMap[this.demuxer.firstFrameHeader.channelConfiguration];
16943
+ assert(numberOfChannels !== void 0);
16944
+ return numberOfChannels;
16945
+ }
16946
+ getSampleRate() {
16947
+ assert(this.demuxer.firstFrameHeader);
16948
+ const sampleRate = aacFrequencyTable[this.demuxer.firstFrameHeader.samplingFrequencyIndex];
16949
+ assert(sampleRate !== void 0);
16950
+ return sampleRate;
16951
+ }
16952
+ async getDecoderConfig() {
16953
+ assert(this.demuxer.firstFrameHeader);
16954
+ const bytes2 = new Uint8Array(3);
16955
+ const bitstream = new Bitstream(bytes2);
16956
+ const { objectType, samplingFrequencyIndex, channelConfiguration } = this.demuxer.firstFrameHeader;
16957
+ if (objectType > 31) {
16958
+ bitstream.writeBits(5, 31);
16959
+ bitstream.writeBits(6, objectType - 32);
16960
+ } else {
16961
+ bitstream.writeBits(5, objectType);
16962
+ }
16963
+ bitstream.writeBits(4, samplingFrequencyIndex);
16964
+ bitstream.writeBits(4, channelConfiguration);
16965
+ return {
16966
+ codec: `mp4a.40.${this.demuxer.firstFrameHeader.objectType}`,
16967
+ numberOfChannels: this.getNumberOfChannels(),
16968
+ sampleRate: this.getSampleRate(),
16969
+ description: bytes2.subarray(0, Math.ceil((bitstream.pos - 1) / 8))
16970
+ };
16971
+ }
16972
+ getPacketAtIndex(sampleIndex, options) {
16973
+ if (sampleIndex === -1) {
16974
+ return null;
16975
+ }
16976
+ const rawSample = this.demuxer.loadedSamples[sampleIndex];
16977
+ if (!rawSample) {
16978
+ return null;
16979
+ }
16980
+ let data;
16981
+ if (options.metadataOnly) {
16982
+ data = PLACEHOLDER_DATA;
16983
+ } else {
16984
+ this.demuxer.reader.pos = rawSample.dataStart;
16985
+ data = this.demuxer.reader.readBytes(rawSample.dataSize);
16986
+ }
16987
+ return new EncodedPacket(
16988
+ data,
16989
+ "key",
16990
+ rawSample.timestamp,
16991
+ rawSample.duration,
16992
+ sampleIndex,
16993
+ rawSample.dataSize
16994
+ );
16995
+ }
16996
+ async getFirstPacket(options) {
16997
+ return this.getPacketAtIndex(0, options);
16998
+ }
16999
+ async getNextPacket(packet, options) {
17000
+ const release = await this.demuxer.readingMutex.acquire();
17001
+ try {
17002
+ const sampleIndex = binarySearchExact(
17003
+ this.demuxer.loadedSamples,
17004
+ packet.timestamp,
17005
+ (x) => x.timestamp
17006
+ );
17007
+ if (sampleIndex === -1) {
17008
+ throw new Error("Packet was not created from this track.");
17009
+ }
17010
+ const nextIndex = sampleIndex + 1;
17011
+ while (nextIndex >= this.demuxer.loadedSamples.length && this.demuxer.lastLoadedPos < this.demuxer.fileSize) {
17012
+ await this.demuxer.loadNextChunk();
17013
+ }
17014
+ return this.getPacketAtIndex(nextIndex, options);
17015
+ } finally {
17016
+ release();
17017
+ }
17018
+ }
17019
+ async getPacket(timestamp, options) {
17020
+ const release = await this.demuxer.readingMutex.acquire();
17021
+ try {
17022
+ while (true) {
17023
+ const index = binarySearchLessOrEqual(
17024
+ this.demuxer.loadedSamples,
17025
+ timestamp,
17026
+ (x) => x.timestamp
17027
+ );
17028
+ if (index === -1 && this.demuxer.loadedSamples.length > 0) {
17029
+ return null;
17030
+ }
17031
+ if (this.demuxer.lastLoadedPos === this.demuxer.fileSize) {
17032
+ return this.getPacketAtIndex(index, options);
17033
+ }
17034
+ if (index >= 0 && index + 1 < this.demuxer.loadedSamples.length) {
17035
+ return this.getPacketAtIndex(index, options);
17036
+ }
17037
+ await this.demuxer.loadNextChunk();
17038
+ }
17039
+ } finally {
17040
+ release();
17041
+ }
17042
+ }
17043
+ getKeyPacket(timestamp, options) {
17044
+ return this.getPacket(timestamp, options);
17045
+ }
17046
+ getNextKeyPacket(packet, options) {
17047
+ return this.getNextPacket(packet, options);
17048
+ }
17049
+ };
17050
+
16662
17051
  // src/input-format.ts
16663
17052
  var InputFormat = class {
16664
17053
  };
@@ -16903,6 +17292,40 @@ ${cue.notes ?? ""}`;
16903
17292
  return "application/ogg";
16904
17293
  }
16905
17294
  };
17295
+ var AdtsInputFormat = class extends InputFormat {
17296
+ /** @internal */
17297
+ async _canReadInput(input) {
17298
+ const sourceSize = await input._mainReader.source.getSize();
17299
+ if (sourceSize < MAX_FRAME_HEADER_SIZE) {
17300
+ return false;
17301
+ }
17302
+ const adtsReader = new AdtsReader(input._mainReader);
17303
+ const firstHeader = adtsReader.readFrameHeader();
17304
+ if (!firstHeader) {
17305
+ return false;
17306
+ }
17307
+ if (sourceSize < firstHeader.frameLength + MAX_FRAME_HEADER_SIZE) {
17308
+ return false;
17309
+ }
17310
+ adtsReader.pos = firstHeader.frameLength;
17311
+ await adtsReader.reader.loadRange(adtsReader.pos, adtsReader.pos + MAX_FRAME_HEADER_SIZE);
17312
+ const secondHeader = adtsReader.readFrameHeader();
17313
+ if (!secondHeader) {
17314
+ return false;
17315
+ }
17316
+ return firstHeader.objectType === secondHeader.objectType && firstHeader.samplingFrequencyIndex === secondHeader.samplingFrequencyIndex && firstHeader.channelConfiguration === secondHeader.channelConfiguration;
17317
+ }
17318
+ /** @internal */
17319
+ _createDemuxer(input) {
17320
+ return new AdtsDemuxer(input);
17321
+ }
17322
+ get name() {
17323
+ return "ADTS";
17324
+ }
17325
+ get mimeType() {
17326
+ return "audio/aac";
17327
+ }
17328
+ };
16906
17329
  var MP4 = new Mp4InputFormat();
16907
17330
  var QTFF = new QuickTimeInputFormat();
16908
17331
  var MATROSKA = new MatroskaInputFormat();
@@ -16910,7 +17333,8 @@ ${cue.notes ?? ""}`;
16910
17333
  var MP3 = new Mp3InputFormat();
16911
17334
  var WAVE = new WaveInputFormat();
16912
17335
  var OGG = new OggInputFormat();
16913
- var ALL_FORMATS = [MP4, QTFF, MATROSKA, WEBM, WAVE, OGG, MP3];
17336
+ var ADTS = new AdtsInputFormat();
17337
+ var ALL_FORMATS = [MP4, QTFF, MATROSKA, WEBM, WAVE, OGG, MP3, ADTS];
16914
17338
 
16915
17339
  // src/input.ts
16916
17340
  var Input = class {