mediabunny 1.58.0 → 1.59.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 (55) hide show
  1. package/dist/bundles/mediabunny.cjs +176 -87
  2. package/dist/bundles/mediabunny.min.cjs +9 -9
  3. package/dist/bundles/mediabunny.min.mjs +8 -8
  4. package/dist/bundles/mediabunny.mjs +176 -87
  5. package/dist/bundles/mediabunny.node.cjs +176 -87
  6. package/dist/mediabunny.d.ts +2 -0
  7. package/dist/modules/src/codec-data.d.ts.map +1 -1
  8. package/dist/modules/src/codec-data.js +15 -0
  9. package/dist/modules/src/hls/hls-demuxer.d.ts.map +1 -1
  10. package/dist/modules/src/hls/hls-demuxer.js +4 -28
  11. package/dist/modules/src/id3.d.ts.map +1 -1
  12. package/dist/modules/src/id3.js +18 -0
  13. package/dist/modules/src/input-track.d.ts.map +1 -1
  14. package/dist/modules/src/input-track.js +2 -3
  15. package/dist/modules/src/isobmff/isobmff-boxes.js +13 -0
  16. package/dist/modules/src/isobmff/isobmff-demuxer.d.ts.map +1 -1
  17. package/dist/modules/src/isobmff/isobmff-demuxer.js +11 -2
  18. package/dist/modules/src/matroska/matroska-demuxer.d.ts.map +1 -1
  19. package/dist/modules/src/matroska/matroska-demuxer.js +9 -2
  20. package/dist/modules/src/matroska/matroska-muxer.d.ts.map +1 -1
  21. package/dist/modules/src/matroska/matroska-muxer.js +7 -0
  22. package/dist/modules/src/media-source.d.ts.map +1 -1
  23. package/dist/modules/src/media-source.js +18 -19
  24. package/dist/modules/src/metadata.d.ts +2 -0
  25. package/dist/modules/src/metadata.d.ts.map +1 -1
  26. package/dist/modules/src/metadata.js +5 -0
  27. package/dist/modules/src/pcm.d.ts.map +1 -1
  28. package/dist/modules/src/pcm.js +8 -5
  29. package/dist/modules/src/resample.d.ts +1 -0
  30. package/dist/modules/src/resample.d.ts.map +1 -1
  31. package/dist/modules/src/resample.js +20 -9
  32. package/dist/modules/src/sample.js +3 -3
  33. package/dist/modules/src/source.d.ts.map +1 -1
  34. package/dist/modules/src/source.js +36 -9
  35. package/dist/modules/src/tsconfig.tsbuildinfo +1 -1
  36. package/dist/modules/src/wave/wave-muxer.d.ts.map +1 -1
  37. package/dist/modules/src/wave/wave-muxer.js +1 -0
  38. package/package.json +1 -1
  39. package/src/codec-data.ts +11 -0
  40. package/src/hls/hls-demuxer.ts +6 -45
  41. package/src/id3.ts +15 -0
  42. package/src/input-track.ts +4 -8
  43. package/src/isobmff/isobmff-boxes.ts +11 -0
  44. package/src/isobmff/isobmff-demuxer.ts +11 -4
  45. package/src/matroska/matroska-demuxer.ts +9 -4
  46. package/src/matroska/matroska-muxer.ts +5 -0
  47. package/src/media-source.ts +31 -16
  48. package/src/metadata.ts +9 -0
  49. package/src/mpeg-ts/mpeg-ts-demuxer.ts +2 -2
  50. package/src/pcm.ts +7 -5
  51. package/src/resample.ts +25 -11
  52. package/src/sample.ts +3 -3
  53. package/src/segmented-input.ts +2 -2
  54. package/src/source.ts +42 -9
  55. package/src/wave/wave-muxer.ts +1 -0
@@ -1491,6 +1491,9 @@ var Mediabunny = (() => {
1491
1491
  if (tags.date !== void 0 && (!(tags.date instanceof Date) || Number.isNaN(tags.date.getTime()))) {
1492
1492
  throw new TypeError("tags.date, when provided, must be a valid Date.");
1493
1493
  }
1494
+ if (tags.beatsPerMinute !== void 0 && (!Number.isInteger(tags.beatsPerMinute) || tags.beatsPerMinute <= 0)) {
1495
+ throw new TypeError("tags.beatsPerMinute, when provided, must be a positive integer.");
1496
+ }
1494
1497
  if (tags.lyrics !== void 0 && typeof tags.lyrics !== "string") {
1495
1498
  throw new TypeError("tags.lyrics, when provided, must be a string.");
1496
1499
  }
@@ -1530,7 +1533,7 @@ var Mediabunny = (() => {
1530
1533
  }
1531
1534
  };
1532
1535
  var metadataTagsAreEmpty = (tags) => {
1533
- return tags.title === void 0 && tags.description === void 0 && tags.artist === void 0 && tags.album === void 0 && tags.albumArtist === void 0 && tags.trackNumber === void 0 && tags.tracksTotal === void 0 && tags.discNumber === void 0 && tags.discsTotal === void 0 && tags.genre === void 0 && tags.date === void 0 && tags.lyrics === void 0 && (!tags.images || tags.images.length === 0) && tags.comment === void 0 && (tags.raw === void 0 || Object.keys(tags.raw).length === 0);
1536
+ return tags.title === void 0 && tags.description === void 0 && tags.artist === void 0 && tags.album === void 0 && tags.albumArtist === void 0 && tags.trackNumber === void 0 && tags.tracksTotal === void 0 && tags.discNumber === void 0 && tags.discsTotal === void 0 && tags.genre === void 0 && tags.beatsPerMinute === void 0 && tags.date === void 0 && tags.lyrics === void 0 && (!tags.images || tags.images.length === 0) && tags.comment === void 0 && (tags.raw === void 0 || Object.keys(tags.raw).length === 0);
1534
1537
  };
1535
1538
  var DEFAULT_TRACK_DISPOSITION = {
1536
1539
  default: true,
@@ -3814,6 +3817,15 @@ var Mediabunny = (() => {
3814
3817
  }
3815
3818
  ;
3816
3819
  break;
3820
+ case "BPM":
3821
+ {
3822
+ const bpm = Number.parseInt(value, 10);
3823
+ if (Number.isInteger(bpm) && bpm > 0) {
3824
+ metadataTags.beatsPerMinute ??= bpm;
3825
+ }
3826
+ }
3827
+ ;
3828
+ break;
3817
3829
  case "GENRE":
3818
3830
  {
3819
3831
  metadataTags.genre ??= value;
@@ -3953,6 +3965,12 @@ var Mediabunny = (() => {
3953
3965
  }
3954
3966
  ;
3955
3967
  break;
3968
+ case "beatsPerMinute":
3969
+ {
3970
+ addCommentTag("BPM", value.toString());
3971
+ }
3972
+ ;
3973
+ break;
3956
3974
  case "images":
3957
3975
  {
3958
3976
  if (!writeImages) {
@@ -8592,6 +8610,17 @@ var Mediabunny = (() => {
8592
8610
  }
8593
8611
  ;
8594
8612
  break;
8613
+ case "tmpo":
8614
+ {
8615
+ if (data instanceof Uint8Array && data.length >= 2) {
8616
+ const bpm = toDataView(data).getInt16(0, false);
8617
+ if (bpm > 0) {
8618
+ this.metadataTags.beatsPerMinute ??= bpm;
8619
+ }
8620
+ }
8621
+ }
8622
+ ;
8623
+ break;
8595
8624
  case "covr":
8596
8625
  case "com.apple.quicktime.artwork":
8597
8626
  {
@@ -9099,7 +9128,6 @@ var Mediabunny = (() => {
9099
9128
  constructor(internalTrack) {
9100
9129
  super(internalTrack);
9101
9130
  this.decoderConfigPromise = null;
9102
- this.internalTrack = internalTrack;
9103
9131
  }
9104
9132
  getType() {
9105
9133
  return "video";
@@ -9198,7 +9226,6 @@ var Mediabunny = (() => {
9198
9226
  constructor(internalTrack) {
9199
9227
  super(internalTrack);
9200
9228
  this.decoderConfigPromise = null;
9201
- this.internalTrack = internalTrack;
9202
9229
  }
9203
9230
  getType() {
9204
9231
  return "audio";
@@ -11608,6 +11635,15 @@ var Mediabunny = (() => {
11608
11635
  }
11609
11636
  ;
11610
11637
  break;
11638
+ case "bpm":
11639
+ {
11640
+ const bpm = Number.parseInt(value, 10);
11641
+ if (Number.isInteger(bpm) && bpm > 0) {
11642
+ metadataTags.beatsPerMinute ??= bpm;
11643
+ }
11644
+ }
11645
+ ;
11646
+ break;
11611
11647
  case "comment":
11612
11648
  {
11613
11649
  metadataTags.comment ??= value;
@@ -12060,7 +12096,6 @@ var Mediabunny = (() => {
12060
12096
  constructor(internalTrack) {
12061
12097
  super(internalTrack);
12062
12098
  this.decoderConfigPromise = null;
12063
- this.internalTrack = internalTrack;
12064
12099
  }
12065
12100
  getType() {
12066
12101
  return "video";
@@ -12154,7 +12189,6 @@ var Mediabunny = (() => {
12154
12189
  constructor(internalTrack) {
12155
12190
  super(internalTrack);
12156
12191
  this.decoderConfigPromise = null;
12157
- this.internalTrack = internalTrack;
12158
12192
  }
12159
12193
  getType() {
12160
12194
  return "audio";
@@ -17527,7 +17561,8 @@ var Mediabunny = (() => {
17527
17561
  }
17528
17562
  this._orchestrator.signalWorkerStoppedRunning(worker);
17529
17563
  if (worker.aborted) {
17530
- await reader?.cancel();
17564
+ await reader?.cancel().catch(() => {
17565
+ });
17531
17566
  }
17532
17567
  }
17533
17568
  /** @internal */
@@ -17610,6 +17645,10 @@ var Mediabunny = (() => {
17610
17645
  * @internal
17611
17646
  */
17612
17647
  this._sequentialBacking = null;
17648
+ /** @internal */
17649
+ this._abortControllers = /* @__PURE__ */ new Map();
17650
+ /** @internal */
17651
+ this._sequentialAbortController = null;
17613
17652
  this._url = url2;
17614
17653
  this._options = options;
17615
17654
  this._getRetryDelay = options.getRetryDelay ?? DEFAULT_RETRY_DELAY;
@@ -17645,6 +17684,7 @@ var Mediabunny = (() => {
17645
17684
  maxCacheSize: options.maxCacheSize ?? 64 * 2 ** 20,
17646
17685
  maxWorkerCount: options.parallelism ?? DEFAULT_PARALLELISM,
17647
17686
  runWorker: this._runWorker.bind(this),
17687
+ onIdleWorkerRemoved: (worker) => this._abortControllers.delete(worker),
17648
17688
  prefetchProfile: PREFETCH_PROFILES.network,
17649
17689
  handleUnhandledError: options.handleUnhandledError
17650
17690
  });
@@ -17688,7 +17728,12 @@ var Mediabunny = (() => {
17688
17728
  /** @internal */
17689
17729
  async _runWorker(worker) {
17690
17730
  while (true) {
17731
+ if (worker.aborted) {
17732
+ this._orchestrator.signalWorkerStoppedRunning(worker);
17733
+ return;
17734
+ }
17691
17735
  const abortController = new AbortController();
17736
+ this._abortControllers.set(worker, abortController);
17692
17737
  const response = await retriedFetch(
17693
17738
  this._options.fetchFn ?? fetch,
17694
17739
  this._url,
@@ -17700,7 +17745,7 @@ var Mediabunny = (() => {
17700
17745
  signal: abortController.signal
17701
17746
  }),
17702
17747
  this._getRetryDelay,
17703
- () => this._disposed
17748
+ () => abortController.signal.aborted
17704
17749
  );
17705
17750
  if (!response.ok) {
17706
17751
  throw new Error(`Error fetching ${String(this._url)}: ${response.status} ${response.statusText}`);
@@ -17732,7 +17777,8 @@ var Mediabunny = (() => {
17732
17777
  }
17733
17778
  if (response.status !== 206) {
17734
17779
  if (this._sequentialBacking) {
17735
- void response.body.cancel();
17780
+ void response.body.cancel().catch(() => {
17781
+ });
17736
17782
  return;
17737
17783
  }
17738
17784
  if (!this._usedForHls) {
@@ -17749,7 +17795,8 @@ var Mediabunny = (() => {
17749
17795
  }
17750
17796
  }
17751
17797
  }
17752
- this._transitionToSequentialMode(response.body);
17798
+ this._abortControllers.delete(worker);
17799
+ this._transitionToSequentialMode(response.body, abortController);
17753
17800
  return;
17754
17801
  }
17755
17802
  const reader = response.body.getReader();
@@ -17763,7 +17810,7 @@ var Mediabunny = (() => {
17763
17810
  try {
17764
17811
  readResult = await reader.read();
17765
17812
  } catch (error) {
17766
- if (this._disposed) {
17813
+ if (abortController.signal.aborted) {
17767
17814
  throw error;
17768
17815
  }
17769
17816
  const retryDelayInSeconds = this._getRetryDelay(1, error, this._url);
@@ -17797,7 +17844,8 @@ var Mediabunny = (() => {
17797
17844
  }
17798
17845
  }
17799
17846
  /** @internal */
17800
- _transitionToSequentialMode(body) {
17847
+ _transitionToSequentialMode(body, abortController) {
17848
+ this._sequentialAbortController = abortController;
17801
17849
  let currentReader = body.getReader();
17802
17850
  let streamPosition = 0;
17803
17851
  let skipRemaining = 0;
@@ -17817,6 +17865,10 @@ var Mediabunny = (() => {
17817
17865
  }
17818
17866
  Logging._error("Error while reading response stream. Attempting to resume.", error);
17819
17867
  await wait(1e3 * retryDelayInSeconds);
17868
+ this._sequentialAbortController = new AbortController();
17869
+ if (this._disposed) {
17870
+ this._sequentialAbortController.abort();
17871
+ }
17820
17872
  const newResponse = await retriedFetch(
17821
17873
  this._options.fetchFn ?? fetch,
17822
17874
  this._url,
@@ -17824,7 +17876,8 @@ var Mediabunny = (() => {
17824
17876
  headers: {
17825
17877
  // Who knows, maybe the server honors range requests this time
17826
17878
  Range: `bytes=${streamPosition}-`
17827
- }
17879
+ },
17880
+ signal: this._sequentialAbortController.signal
17828
17881
  }),
17829
17882
  this._getRetryDelay,
17830
17883
  () => this._disposed
@@ -17862,7 +17915,8 @@ var Mediabunny = (() => {
17862
17915
  return;
17863
17916
  }
17864
17917
  },
17865
- cancel: () => currentReader.cancel()
17918
+ cancel: () => currentReader.cancel().catch(() => {
17919
+ })
17866
17920
  });
17867
17921
  const backing = new ReadableStreamSource(wrappedStream, {
17868
17922
  maxCacheSize: this._orchestrator.options.maxCacheSize,
@@ -17887,6 +17941,10 @@ var Mediabunny = (() => {
17887
17941
  }
17888
17942
  this._orchestrator.workers.length = 0;
17889
17943
  this._orchestrator.queuedReads.length = 0;
17944
+ for (const [, otherAbortController] of this._abortControllers) {
17945
+ otherAbortController.abort();
17946
+ }
17947
+ this._abortControllers.clear();
17890
17948
  for (const slice of uniqueSlices) {
17891
17949
  const result = backing._read(slice.start, slice.start + slice.bytes.length);
17892
17950
  if (isThenable(result)) {
@@ -17907,6 +17965,11 @@ var Mediabunny = (() => {
17907
17965
  /** @internal */
17908
17966
  _dispose() {
17909
17967
  this._orchestrator.dispose();
17968
+ for (const [, abortController] of this._abortControllers) {
17969
+ abortController.abort();
17970
+ }
17971
+ this._abortControllers.clear();
17972
+ this._sequentialAbortController?.abort();
17910
17973
  if (this._sequentialBacking) {
17911
17974
  this._sequentialBacking._disposed = true;
17912
17975
  this._sequentialBacking._dispose();
@@ -18062,7 +18125,8 @@ var Mediabunny = (() => {
18062
18125
  if (isThenable(data)) data = await data;
18063
18126
  if (worker.aborted) {
18064
18127
  if (data instanceof ReadableStream) {
18065
- await data.cancel();
18128
+ await data.cancel().catch(() => {
18129
+ });
18066
18130
  }
18067
18131
  break;
18068
18132
  }
@@ -18307,7 +18371,8 @@ var Mediabunny = (() => {
18307
18371
  }
18308
18372
  this._pendingSlices.length = 0;
18309
18373
  this._cache.length = 0;
18310
- void this._reader?.cancel();
18374
+ void this._reader?.cancel().catch(() => {
18375
+ });
18311
18376
  }
18312
18377
  };
18313
18378
  var PREFETCH_PROFILES = {
@@ -19543,16 +19608,6 @@ var Mediabunny = (() => {
19543
19608
  return;
19544
19609
  }
19545
19610
  }
19546
- const videoGroupIds = [
19547
- ...new Set(
19548
- mediaTags.filter((tag) => tag.attributes.get("type").toLowerCase() === "video").map((tag) => tag.attributes.get("group-id"))
19549
- )
19550
- ];
19551
- const audioGroupIds = [
19552
- ...new Set(
19553
- mediaTags.filter((tag) => tag.attributes.get("type").toLowerCase() === "audio").map((tag) => tag.attributes.get("group-id"))
19554
- )
19555
- ];
19556
19611
  const internalTracksByVariant = await Promise.all(variantStreams.map(async (variantStream, i) => {
19557
19612
  const result = [];
19558
19613
  const codecsList = variantStream.attributes.get("codecs");
@@ -19578,11 +19633,6 @@ var Mediabunny = (() => {
19578
19633
  (x) => AUDIO_CODECS.includes(inferCodecFromCodecString(x))
19579
19634
  );
19580
19635
  if (videoGroupId !== null && !containsVideoCodecs) {
19581
- if (!videoGroupIds.includes(videoGroupId)) {
19582
- throw new Error(
19583
- `Invalid M3U8 file; variant stream references video group "${videoGroupId}" which is not defined in any #EXT-X-MEDIA tags.`
19584
- );
19585
- }
19586
19636
  const matchingVideoMediaTag = mediaTags.find((mediaTag) => {
19587
19637
  const groupId = mediaTag.attributes.get("group-id");
19588
19638
  const type = mediaTag.attributes.get("type");
@@ -19607,11 +19657,6 @@ var Mediabunny = (() => {
19607
19657
  }
19608
19658
  }
19609
19659
  if (audioGroupId !== null && !containsAudioCodecs) {
19610
- if (!audioGroupIds.includes(audioGroupId)) {
19611
- throw new Error(
19612
- `Invalid M3U8 file; variant stream references audio group "${audioGroupId}" which is not defined in any #EXT-X-MEDIA tags.`
19613
- );
19614
- }
19615
19660
  const matchingAudioMediaTag = mediaTags.find((tag) => {
19616
19661
  const groupId = tag.attributes.get("group-id");
19617
19662
  const type = tag.attributes.get("type");
@@ -19688,11 +19733,6 @@ var Mediabunny = (() => {
19688
19733
  }
19689
19734
  });
19690
19735
  } else {
19691
- if (!videoGroupIds.includes(videoGroupId2)) {
19692
- throw new Error(
19693
- `Invalid M3U8 file; variant stream references video group "${videoGroupId2}" which is not defined in any #EXT-X-MEDIA tags.`
19694
- );
19695
- }
19696
19736
  for (const mediaTag of mediaTags) {
19697
19737
  const groupId = mediaTag.attributes.get("group-id");
19698
19738
  const type = mediaTag.attributes.get("type");
@@ -19721,8 +19761,8 @@ var Mediabunny = (() => {
19721
19761
  fullPath: mediaTag.fullPath ?? variantStream.fullPath,
19722
19762
  fullCodecString: videoCodecString,
19723
19763
  pairingMask: 1n << BigInt(i),
19724
- peakBitrate: null,
19725
- averageBitrate: null,
19764
+ peakBitrate: mediaTag.fullPath === null ? bandwidth : null,
19765
+ averageBitrate: mediaTag.fullPath === null ? averageBandwidth : null,
19726
19766
  name: mediaTag.attributes.get("name"),
19727
19767
  hasOnlyKeyPackets: variantStream.hasOnlyKeyPackets,
19728
19768
  info: {
@@ -19765,11 +19805,6 @@ var Mediabunny = (() => {
19765
19805
  }
19766
19806
  });
19767
19807
  } else {
19768
- if (!audioGroupIds.includes(audioGroupId2)) {
19769
- throw new Error(
19770
- `Invalid M3U8 file; variant stream references audio group "${audioGroupId2}" which is not defined in any #EXT-X-MEDIA tags.`
19771
- );
19772
- }
19773
19808
  for (const mediaTag of mediaTags) {
19774
19809
  const groupId = mediaTag.attributes.get("group-id");
19775
19810
  const type = mediaTag.attributes.get("type");
@@ -19790,8 +19825,8 @@ var Mediabunny = (() => {
19790
19825
  fullPath: mediaTag.fullPath ?? variantStream.fullPath,
19791
19826
  fullCodecString: audioCodecString,
19792
19827
  pairingMask: 1n << BigInt(i),
19793
- peakBitrate: null,
19794
- averageBitrate: null,
19828
+ peakBitrate: mediaTag.fullPath === null ? bandwidth : null,
19829
+ averageBitrate: mediaTag.fullPath === null ? averageBandwidth : null,
19795
19830
  name: mediaTag.attributes.get("name"),
19796
19831
  hasOnlyKeyPackets: variantStream.hasOnlyKeyPackets,
19797
19832
  info: {
@@ -22860,13 +22895,13 @@ var Mediabunny = (() => {
22860
22895
  switch (format) {
22861
22896
  case "u8":
22862
22897
  case "u8-planar":
22863
- return (view2, offset, value) => view2.setUint8(offset, clamp((value + 1) * 127.5, 0, 255));
22898
+ return (view2, offset, value) => view2.setUint8(offset, clamp(Math.round(value * 128) + 128, 0, 255));
22864
22899
  case "s16":
22865
22900
  case "s16-planar":
22866
- return (view2, offset, value) => view2.setInt16(offset, clamp(Math.round(value * 32767), -32768, 32767), true);
22901
+ return (view2, offset, value) => view2.setInt16(offset, clamp(Math.round(value * 32768), -32768, 32767), true);
22867
22902
  case "s32":
22868
22903
  case "s32-planar":
22869
- return (view2, offset, value) => view2.setInt32(offset, clamp(Math.round(value * 2147483647), -2147483648, 2147483647), true);
22904
+ return (view2, offset, value) => view2.setInt32(offset, clamp(Math.round(value * 2147483648), -2147483648, 2147483647), true);
22870
22905
  case "f32":
22871
22906
  case "f32-planar":
22872
22907
  return (view2, offset, value) => view2.setFloat32(offset, value, true);
@@ -23780,7 +23815,7 @@ var Mediabunny = (() => {
23780
23815
  var toUlaw = (s16) => {
23781
23816
  const MULAW_MAX = 8191;
23782
23817
  const MULAW_BIAS = 33;
23783
- let number = s16;
23818
+ let number = s16 >> 2;
23784
23819
  let mask = 4096;
23785
23820
  let sign = 0;
23786
23821
  let position = 12;
@@ -23811,7 +23846,7 @@ var Mediabunny = (() => {
23811
23846
  }
23812
23847
  position = ((number & 240) >> 4) + 5;
23813
23848
  const decoded = (1 << position | (number & 15) << position - 4 | 1 << position - 5) - MULAW_BIAS;
23814
- return sign === 0 ? decoded : -decoded;
23849
+ return (sign === 0 ? decoded : -decoded) << 2;
23815
23850
  };
23816
23851
  var toAlaw = (s16) => {
23817
23852
  const ALAW_MAX = 4095;
@@ -23819,9 +23854,10 @@ var Mediabunny = (() => {
23819
23854
  let sign = 0;
23820
23855
  let position = 11;
23821
23856
  let lsb = 0;
23822
- let number = s16;
23857
+ let number = s16 >> 3;
23823
23858
  if (number < 0) {
23824
- number = -number;
23859
+ number = -number - 1;
23860
+ } else {
23825
23861
  sign = 128;
23826
23862
  }
23827
23863
  if (number > ALAW_MAX) {
@@ -23849,7 +23885,7 @@ var Mediabunny = (() => {
23849
23885
  } else {
23850
23886
  decoded = number << 1 | 1;
23851
23887
  }
23852
- return sign === 0 ? decoded : -decoded;
23888
+ return (sign === 0 ? -decoded : decoded) << 3;
23853
23889
  };
23854
23890
 
23855
23891
  // src/media-sink.ts
@@ -26056,7 +26092,6 @@ var Mediabunny = (() => {
26056
26092
  super(input, backing);
26057
26093
  /** @internal */
26058
26094
  this._pixelAspectRatioCache = null;
26059
- this._backing = backing;
26060
26095
  }
26061
26096
  get type() {
26062
26097
  return "video";
@@ -26395,7 +26430,6 @@ var Mediabunny = (() => {
26395
26430
  /** @internal */
26396
26431
  constructor(input, backing) {
26397
26432
  super(input, backing);
26398
- this._backing = backing;
26399
26433
  }
26400
26434
  get type() {
26401
26435
  return "audio";
@@ -26463,7 +26497,7 @@ var Mediabunny = (() => {
26463
26497
  if (customAudioDecoders.some((x) => x.supports(codec, decoderConfig))) {
26464
26498
  return true;
26465
26499
  }
26466
- if (decoderConfig.codec.startsWith("pcm-")) {
26500
+ if (PCM_AUDIO_CODECS.includes(decoderConfig.codec)) {
26467
26501
  return true;
26468
26502
  } else {
26469
26503
  if (typeof AudioDecoder === "undefined") {
@@ -27875,6 +27909,17 @@ var Mediabunny = (() => {
27875
27909
  }
27876
27910
  ;
27877
27911
  break;
27912
+ case "TBPM":
27913
+ case "TBP":
27914
+ {
27915
+ const bpmText = reader.readId3V2EncodingAndText(frameEndPos);
27916
+ const bpm = Number.parseInt(bpmText, 10);
27917
+ if (Number.isInteger(bpm)) {
27918
+ tags.beatsPerMinute ??= bpm;
27919
+ }
27920
+ }
27921
+ ;
27922
+ break;
27878
27923
  case "USLT":
27879
27924
  case "ULT":
27880
27925
  {
@@ -28204,6 +28249,13 @@ var Mediabunny = (() => {
28204
28249
  }
28205
28250
  ;
28206
28251
  break;
28252
+ case "beatsPerMinute":
28253
+ {
28254
+ this.writeId3V2TextFrame("TBPM", `${value}`);
28255
+ writtenTags.add("TBPM");
28256
+ }
28257
+ ;
28258
+ break;
28207
28259
  case "lyrics":
28208
28260
  {
28209
28261
  this.writeId3V2LyricsFrame(value);
@@ -30223,6 +30275,7 @@ var Mediabunny = (() => {
30223
30275
  case "discsTotal":
30224
30276
  case "trackNumber":
30225
30277
  case "tracksTotal":
30278
+ case "beatsPerMinute":
30226
30279
  case "images":
30227
30280
  {
30228
30281
  }
@@ -30305,6 +30358,20 @@ var Mediabunny = (() => {
30305
30358
  }
30306
30359
  ;
30307
30360
  break;
30361
+ case "beatsPerMinute":
30362
+ {
30363
+ if (!isMdta) {
30364
+ pairs.push({ key: "tmpo", value: box("data", [
30365
+ u32(21),
30366
+ // Type indicator
30367
+ u32(0),
30368
+ // Locale indicator
30369
+ u16(value)
30370
+ ]) });
30371
+ }
30372
+ }
30373
+ ;
30374
+ break;
30308
30375
  case "lyrics":
30309
30376
  {
30310
30377
  pairs.push({ key: isMdta ? "lyrics" : "\xA9lyr", value: dataStringBoxLong(value) });
@@ -32768,6 +32835,13 @@ var Mediabunny = (() => {
32768
32835
  }
32769
32836
  ;
32770
32837
  break;
32838
+ case "beatsPerMinute":
32839
+ {
32840
+ addSimpleTag("BPM", value.toString());
32841
+ writtenTags.add("BPM");
32842
+ }
32843
+ ;
32844
+ break;
32771
32845
  case "comment":
32772
32846
  {
32773
32847
  addSimpleTag("COMMENT", value);
@@ -34872,6 +34946,7 @@ ${cue.notes ?? ""}`;
34872
34946
  case "discNumber":
34873
34947
  case "tracksTotal":
34874
34948
  case "discsTotal":
34949
+ case "beatsPerMinute":
34875
34950
  case "description":
34876
34951
  case "lyrics":
34877
34952
  case "images":
@@ -34967,7 +35042,6 @@ ${cue.notes ?? ""}`;
34967
35042
  this.onSample = options.onSample;
34968
35043
  this.bufferSizeInFrames = Math.floor(this.targetSampleRate * 5);
34969
35044
  this.bufferSizeInSamples = this.bufferSizeInFrames * this.targetNumberOfChannels;
34970
- this.outputBuffer = new Float32Array(this.bufferSizeInSamples);
34971
35045
  }
34972
35046
  /**
34973
35047
  * Sets up the channel mixer to handle up/downmixing in the case where input and output channel counts don't match.
@@ -35058,6 +35132,11 @@ ${cue.notes ?? ""}`;
35058
35132
  this.sourceSampleRate = audioSample.sampleRate;
35059
35133
  this.sourceNumberOfChannels = audioSample.numberOfChannels;
35060
35134
  this.startTime = audioSample.timestamp;
35135
+ const overflowFrames = Math.ceil(this.targetSampleRate / this.sourceSampleRate);
35136
+ this.bufferCapacityInFrames = this.bufferSizeInFrames + overflowFrames;
35137
+ this.outputBuffer = new Float32Array(
35138
+ this.bufferCapacityInFrames * this.targetNumberOfChannels
35139
+ );
35061
35140
  this.tempSourceBuffer = new Float32Array(this.sourceSampleRate * this.sourceNumberOfChannels);
35062
35141
  this.doChannelMixerSetup();
35063
35142
  }
@@ -35075,12 +35154,12 @@ ${cue.notes ?? ""}`;
35075
35154
  if (outputFrame < this.bufferStartFrame) {
35076
35155
  continue;
35077
35156
  }
35078
- while (outputFrame >= this.bufferStartFrame + this.bufferSizeInFrames) {
35157
+ while (outputFrame >= this.bufferStartFrame + this.bufferCapacityInFrames) {
35079
35158
  await this.finalizeCurrentBuffer();
35080
35159
  this.bufferStartFrame += this.bufferSizeInFrames;
35081
35160
  }
35082
35161
  const bufferFrameIndex = outputFrame - this.bufferStartFrame;
35083
- assert(bufferFrameIndex < this.bufferSizeInFrames);
35162
+ assert(bufferFrameIndex < this.bufferCapacityInFrames);
35084
35163
  const outputTime = outputFrame / this.targetSampleRate;
35085
35164
  const inputTime = outputTime - inputStartTime;
35086
35165
  const sourcePosition = inputTime * this.sourceSampleRate;
@@ -35112,7 +35191,7 @@ ${cue.notes ?? ""}`;
35112
35191
  return;
35113
35192
  }
35114
35193
  assert(this.startTime !== null);
35115
- const samplesWritten = (this.maxWrittenFrame + 1) * this.targetNumberOfChannels;
35194
+ const samplesWritten = Math.min(this.maxWrittenFrame + 1, this.bufferSizeInFrames) * this.targetNumberOfChannels;
35116
35195
  const outputData = new Float32Array(samplesWritten);
35117
35196
  outputData.set(this.outputBuffer.subarray(0, samplesWritten));
35118
35197
  const audioSample = new AudioSample({
@@ -35123,11 +35202,15 @@ ${cue.notes ?? ""}`;
35123
35202
  data: outputData
35124
35203
  });
35125
35204
  await this.onSample(audioSample);
35126
- this.outputBuffer.fill(0);
35127
- this.maxWrittenFrame = null;
35205
+ this.outputBuffer.copyWithin(0, this.bufferSizeInSamples);
35206
+ this.outputBuffer.fill(0, this.outputBuffer.length - this.bufferSizeInSamples);
35207
+ this.maxWrittenFrame = this.maxWrittenFrame >= this.bufferSizeInFrames ? this.maxWrittenFrame - this.bufferSizeInFrames : null;
35128
35208
  }
35129
- finalize() {
35130
- return this.finalizeCurrentBuffer();
35209
+ async finalize() {
35210
+ while (this.maxWrittenFrame !== null) {
35211
+ await this.finalizeCurrentBuffer();
35212
+ this.bufferStartFrame += this.bufferSizeInFrames;
35213
+ }
35131
35214
  }
35132
35215
  };
35133
35216
 
@@ -35208,8 +35291,6 @@ ${cue.notes ?? ""}`;
35208
35291
  /** Internal constructor. */
35209
35292
  constructor(codec) {
35210
35293
  super();
35211
- /** @internal */
35212
- this._connectedTrack = null;
35213
35294
  if (!VIDEO_CODECS.includes(codec)) {
35214
35295
  throw new TypeError(`Invalid video codec '${codec}'. Must be one of: ${VIDEO_CODECS.join(", ")}.`);
35215
35296
  }
@@ -36350,8 +36431,6 @@ ${cue.notes ?? ""}`;
36350
36431
  /** Internal constructor. */
36351
36432
  constructor(codec) {
36352
36433
  super();
36353
- /** @internal */
36354
- this._connectedTrack = null;
36355
36434
  if (!AUDIO_CODECS.includes(codec)) {
36356
36435
  throw new TypeError(`Invalid audio codec '${codec}'. Must be one of: ${AUDIO_CODECS.join(", ")}.`);
36357
36436
  }
@@ -36596,17 +36675,20 @@ ${cue.notes ?? ""}`;
36596
36675
  const outputView = new DataView(outputBuffer);
36597
36676
  outputs.push({ frameCount, view: outputView });
36598
36677
  }
36599
- const allocationSize = audioSample.allocationSize({ planeIndex: 0, format: "f32-planar" });
36600
- const floats = new Float32Array(allocationSize / Float32Array.BYTES_PER_ELEMENT);
36678
+ const isS32 = audioSample.format === "s32" || audioSample.format === "s32-planar";
36679
+ const readFormat = isS32 ? "s32-planar" : "f32-planar";
36680
+ const scale = isS32 ? 1 / 2147483648 : 1;
36681
+ const allocationSize = audioSample.allocationSize({ planeIndex: 0, format: readFormat });
36682
+ const values = isS32 ? new Int32Array(allocationSize / 4) : new Float32Array(allocationSize / 4);
36601
36683
  for (let i = 0; i < numberOfChannels; i++) {
36602
- audioSample.copyTo(floats, { planeIndex: i, format: "f32-planar" });
36684
+ audioSample.copyTo(values, { planeIndex: i, format: readFormat });
36603
36685
  for (let j = 0; j < outputs.length; j++) {
36604
36686
  const { frameCount, view: view2 } = outputs[j];
36605
36687
  for (let k = 0; k < frameCount; k++) {
36606
36688
  this.writeOutputValue(
36607
36689
  view2,
36608
36690
  (k * numberOfChannels + i) * this.outputSampleSize,
36609
- floats[j * CHUNK_SIZE + k]
36691
+ values[j * CHUNK_SIZE + k] * scale
36610
36692
  );
36611
36693
  }
36612
36694
  }
@@ -36742,19 +36824,19 @@ ${cue.notes ?? ""}`;
36742
36824
  case 1:
36743
36825
  {
36744
36826
  if (dataType === "unsigned") {
36745
- this.writeOutputValue = (view2, byteOffset, value) => view2.setUint8(byteOffset, clamp((value + 1) * 127.5, 0, 255));
36827
+ this.writeOutputValue = (view2, byteOffset, value) => view2.setUint8(byteOffset, clamp(Math.round(value * 128) + 128, 0, 255));
36746
36828
  } else if (dataType === "signed") {
36747
36829
  this.writeOutputValue = (view2, byteOffset, value) => {
36748
36830
  view2.setInt8(byteOffset, clamp(Math.round(value * 128), -128, 127));
36749
36831
  };
36750
36832
  } else if (dataType === "ulaw") {
36751
36833
  this.writeOutputValue = (view2, byteOffset, value) => {
36752
- const int16 = clamp(Math.floor(value * 32767), -32768, 32767);
36834
+ const int16 = clamp(Math.round(value * 32768), -32768, 32767);
36753
36835
  view2.setUint8(byteOffset, toUlaw(int16));
36754
36836
  };
36755
36837
  } else if (dataType === "alaw") {
36756
36838
  this.writeOutputValue = (view2, byteOffset, value) => {
36757
- const int16 = clamp(Math.floor(value * 32767), -32768, 32767);
36839
+ const int16 = clamp(Math.round(value * 32768), -32768, 32767);
36758
36840
  view2.setUint8(byteOffset, toAlaw(int16));
36759
36841
  };
36760
36842
  } else {
@@ -36766,9 +36848,9 @@ ${cue.notes ?? ""}`;
36766
36848
  case 2:
36767
36849
  {
36768
36850
  if (dataType === "unsigned") {
36769
- this.writeOutputValue = (view2, byteOffset, value) => view2.setUint16(byteOffset, clamp((value + 1) * 32767.5, 0, 65535), littleEndian);
36851
+ this.writeOutputValue = (view2, byteOffset, value) => view2.setUint16(byteOffset, clamp(Math.round(value * 32768) + 32768, 0, 65535), littleEndian);
36770
36852
  } else if (dataType === "signed") {
36771
- this.writeOutputValue = (view2, byteOffset, value) => view2.setInt16(byteOffset, clamp(Math.round(value * 32767), -32768, 32767), littleEndian);
36853
+ this.writeOutputValue = (view2, byteOffset, value) => view2.setInt16(byteOffset, clamp(Math.round(value * 32768), -32768, 32767), littleEndian);
36772
36854
  } else {
36773
36855
  assert(false);
36774
36856
  }
@@ -36778,12 +36860,17 @@ ${cue.notes ?? ""}`;
36778
36860
  case 3:
36779
36861
  {
36780
36862
  if (dataType === "unsigned") {
36781
- this.writeOutputValue = (view2, byteOffset, value) => setUint24(view2, byteOffset, clamp((value + 1) * 83886075e-1, 0, 16777215), littleEndian);
36863
+ this.writeOutputValue = (view2, byteOffset, value) => setUint24(
36864
+ view2,
36865
+ byteOffset,
36866
+ clamp(Math.round(value * 8388608) + 8388608, 0, 16777215),
36867
+ littleEndian
36868
+ );
36782
36869
  } else if (dataType === "signed") {
36783
36870
  this.writeOutputValue = (view2, byteOffset, value) => setInt24(
36784
36871
  view2,
36785
36872
  byteOffset,
36786
- clamp(Math.round(value * 8388607), -8388608, 8388607),
36873
+ clamp(Math.round(value * 8388608), -8388608, 8388607),
36787
36874
  littleEndian
36788
36875
  );
36789
36876
  } else {
@@ -36795,11 +36882,15 @@ ${cue.notes ?? ""}`;
36795
36882
  case 4:
36796
36883
  {
36797
36884
  if (dataType === "unsigned") {
36798
- this.writeOutputValue = (view2, byteOffset, value) => view2.setUint32(byteOffset, clamp((value + 1) * 21474836475e-1, 0, 4294967295), littleEndian);
36885
+ this.writeOutputValue = (view2, byteOffset, value) => view2.setUint32(
36886
+ byteOffset,
36887
+ clamp(Math.round(value * 2147483648) + 2147483648, 0, 4294967295),
36888
+ littleEndian
36889
+ );
36799
36890
  } else if (dataType === "signed") {
36800
36891
  this.writeOutputValue = (view2, byteOffset, value) => view2.setInt32(
36801
36892
  byteOffset,
36802
- clamp(Math.round(value * 2147483647), -2147483648, 2147483647),
36893
+ clamp(Math.round(value * 2147483648), -2147483648, 2147483647),
36803
36894
  littleEndian
36804
36895
  );
36805
36896
  } else if (dataType === "float") {
@@ -37220,8 +37311,6 @@ ${cue.notes ?? ""}`;
37220
37311
  /** Internal constructor. */
37221
37312
  constructor(codec) {
37222
37313
  super();
37223
- /** @internal */
37224
- this._connectedTrack = null;
37225
37314
  if (!SUBTITLE_CODECS.includes(codec)) {
37226
37315
  throw new TypeError(`Invalid subtitle codec '${codec}'. Must be one of: ${SUBTITLE_CODECS.join(", ")}.`);
37227
37316
  }