mediabunny 1.58.1 → 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 (46) hide show
  1. package/dist/bundles/mediabunny.cjs +100 -55
  2. package/dist/bundles/mediabunny.min.cjs +9 -9
  3. package/dist/bundles/mediabunny.min.mjs +8 -8
  4. package/dist/bundles/mediabunny.mjs +100 -55
  5. package/dist/bundles/mediabunny.node.cjs +100 -55
  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 +0 -2
  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.js +0 -6
  23. package/dist/modules/src/metadata.d.ts +2 -0
  24. package/dist/modules/src/metadata.d.ts.map +1 -1
  25. package/dist/modules/src/metadata.js +5 -0
  26. package/dist/modules/src/resample.d.ts +1 -0
  27. package/dist/modules/src/resample.d.ts.map +1 -1
  28. package/dist/modules/src/resample.js +20 -9
  29. package/dist/modules/src/tsconfig.tsbuildinfo +1 -1
  30. package/dist/modules/src/wave/wave-muxer.d.ts.map +1 -1
  31. package/dist/modules/src/wave/wave-muxer.js +1 -0
  32. package/package.json +1 -1
  33. package/src/codec-data.ts +11 -0
  34. package/src/hls/hls-demuxer.ts +6 -45
  35. package/src/id3.ts +15 -0
  36. package/src/input-track.ts +2 -6
  37. package/src/isobmff/isobmff-boxes.ts +11 -0
  38. package/src/isobmff/isobmff-demuxer.ts +11 -4
  39. package/src/matroska/matroska-demuxer.ts +9 -4
  40. package/src/matroska/matroska-muxer.ts +5 -0
  41. package/src/media-source.ts +3 -3
  42. package/src/metadata.ts +9 -0
  43. package/src/mpeg-ts/mpeg-ts-demuxer.ts +2 -2
  44. package/src/resample.ts +25 -11
  45. package/src/segmented-input.ts +2 -2
  46. 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";
@@ -19574,16 +19608,6 @@ var Mediabunny = (() => {
19574
19608
  return;
19575
19609
  }
19576
19610
  }
19577
- const videoGroupIds = [
19578
- ...new Set(
19579
- mediaTags.filter((tag) => tag.attributes.get("type").toLowerCase() === "video").map((tag) => tag.attributes.get("group-id"))
19580
- )
19581
- ];
19582
- const audioGroupIds = [
19583
- ...new Set(
19584
- mediaTags.filter((tag) => tag.attributes.get("type").toLowerCase() === "audio").map((tag) => tag.attributes.get("group-id"))
19585
- )
19586
- ];
19587
19611
  const internalTracksByVariant = await Promise.all(variantStreams.map(async (variantStream, i) => {
19588
19612
  const result = [];
19589
19613
  const codecsList = variantStream.attributes.get("codecs");
@@ -19609,11 +19633,6 @@ var Mediabunny = (() => {
19609
19633
  (x) => AUDIO_CODECS.includes(inferCodecFromCodecString(x))
19610
19634
  );
19611
19635
  if (videoGroupId !== null && !containsVideoCodecs) {
19612
- if (!videoGroupIds.includes(videoGroupId)) {
19613
- throw new Error(
19614
- `Invalid M3U8 file; variant stream references video group "${videoGroupId}" which is not defined in any #EXT-X-MEDIA tags.`
19615
- );
19616
- }
19617
19636
  const matchingVideoMediaTag = mediaTags.find((mediaTag) => {
19618
19637
  const groupId = mediaTag.attributes.get("group-id");
19619
19638
  const type = mediaTag.attributes.get("type");
@@ -19638,11 +19657,6 @@ var Mediabunny = (() => {
19638
19657
  }
19639
19658
  }
19640
19659
  if (audioGroupId !== null && !containsAudioCodecs) {
19641
- if (!audioGroupIds.includes(audioGroupId)) {
19642
- throw new Error(
19643
- `Invalid M3U8 file; variant stream references audio group "${audioGroupId}" which is not defined in any #EXT-X-MEDIA tags.`
19644
- );
19645
- }
19646
19660
  const matchingAudioMediaTag = mediaTags.find((tag) => {
19647
19661
  const groupId = tag.attributes.get("group-id");
19648
19662
  const type = tag.attributes.get("type");
@@ -19719,11 +19733,6 @@ var Mediabunny = (() => {
19719
19733
  }
19720
19734
  });
19721
19735
  } else {
19722
- if (!videoGroupIds.includes(videoGroupId2)) {
19723
- throw new Error(
19724
- `Invalid M3U8 file; variant stream references video group "${videoGroupId2}" which is not defined in any #EXT-X-MEDIA tags.`
19725
- );
19726
- }
19727
19736
  for (const mediaTag of mediaTags) {
19728
19737
  const groupId = mediaTag.attributes.get("group-id");
19729
19738
  const type = mediaTag.attributes.get("type");
@@ -19752,8 +19761,8 @@ var Mediabunny = (() => {
19752
19761
  fullPath: mediaTag.fullPath ?? variantStream.fullPath,
19753
19762
  fullCodecString: videoCodecString,
19754
19763
  pairingMask: 1n << BigInt(i),
19755
- peakBitrate: null,
19756
- averageBitrate: null,
19764
+ peakBitrate: mediaTag.fullPath === null ? bandwidth : null,
19765
+ averageBitrate: mediaTag.fullPath === null ? averageBandwidth : null,
19757
19766
  name: mediaTag.attributes.get("name"),
19758
19767
  hasOnlyKeyPackets: variantStream.hasOnlyKeyPackets,
19759
19768
  info: {
@@ -19796,11 +19805,6 @@ var Mediabunny = (() => {
19796
19805
  }
19797
19806
  });
19798
19807
  } else {
19799
- if (!audioGroupIds.includes(audioGroupId2)) {
19800
- throw new Error(
19801
- `Invalid M3U8 file; variant stream references audio group "${audioGroupId2}" which is not defined in any #EXT-X-MEDIA tags.`
19802
- );
19803
- }
19804
19808
  for (const mediaTag of mediaTags) {
19805
19809
  const groupId = mediaTag.attributes.get("group-id");
19806
19810
  const type = mediaTag.attributes.get("type");
@@ -19821,8 +19825,8 @@ var Mediabunny = (() => {
19821
19825
  fullPath: mediaTag.fullPath ?? variantStream.fullPath,
19822
19826
  fullCodecString: audioCodecString,
19823
19827
  pairingMask: 1n << BigInt(i),
19824
- peakBitrate: null,
19825
- averageBitrate: null,
19828
+ peakBitrate: mediaTag.fullPath === null ? bandwidth : null,
19829
+ averageBitrate: mediaTag.fullPath === null ? averageBandwidth : null,
19826
19830
  name: mediaTag.attributes.get("name"),
19827
19831
  hasOnlyKeyPackets: variantStream.hasOnlyKeyPackets,
19828
19832
  info: {
@@ -26088,7 +26092,6 @@ var Mediabunny = (() => {
26088
26092
  super(input, backing);
26089
26093
  /** @internal */
26090
26094
  this._pixelAspectRatioCache = null;
26091
- this._backing = backing;
26092
26095
  }
26093
26096
  get type() {
26094
26097
  return "video";
@@ -26427,7 +26430,6 @@ var Mediabunny = (() => {
26427
26430
  /** @internal */
26428
26431
  constructor(input, backing) {
26429
26432
  super(input, backing);
26430
- this._backing = backing;
26431
26433
  }
26432
26434
  get type() {
26433
26435
  return "audio";
@@ -27907,6 +27909,17 @@ var Mediabunny = (() => {
27907
27909
  }
27908
27910
  ;
27909
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;
27910
27923
  case "USLT":
27911
27924
  case "ULT":
27912
27925
  {
@@ -28236,6 +28249,13 @@ var Mediabunny = (() => {
28236
28249
  }
28237
28250
  ;
28238
28251
  break;
28252
+ case "beatsPerMinute":
28253
+ {
28254
+ this.writeId3V2TextFrame("TBPM", `${value}`);
28255
+ writtenTags.add("TBPM");
28256
+ }
28257
+ ;
28258
+ break;
28239
28259
  case "lyrics":
28240
28260
  {
28241
28261
  this.writeId3V2LyricsFrame(value);
@@ -30255,6 +30275,7 @@ var Mediabunny = (() => {
30255
30275
  case "discsTotal":
30256
30276
  case "trackNumber":
30257
30277
  case "tracksTotal":
30278
+ case "beatsPerMinute":
30258
30279
  case "images":
30259
30280
  {
30260
30281
  }
@@ -30337,6 +30358,20 @@ var Mediabunny = (() => {
30337
30358
  }
30338
30359
  ;
30339
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;
30340
30375
  case "lyrics":
30341
30376
  {
30342
30377
  pairs.push({ key: isMdta ? "lyrics" : "\xA9lyr", value: dataStringBoxLong(value) });
@@ -32800,6 +32835,13 @@ var Mediabunny = (() => {
32800
32835
  }
32801
32836
  ;
32802
32837
  break;
32838
+ case "beatsPerMinute":
32839
+ {
32840
+ addSimpleTag("BPM", value.toString());
32841
+ writtenTags.add("BPM");
32842
+ }
32843
+ ;
32844
+ break;
32803
32845
  case "comment":
32804
32846
  {
32805
32847
  addSimpleTag("COMMENT", value);
@@ -34904,6 +34946,7 @@ ${cue.notes ?? ""}`;
34904
34946
  case "discNumber":
34905
34947
  case "tracksTotal":
34906
34948
  case "discsTotal":
34949
+ case "beatsPerMinute":
34907
34950
  case "description":
34908
34951
  case "lyrics":
34909
34952
  case "images":
@@ -34999,7 +35042,6 @@ ${cue.notes ?? ""}`;
34999
35042
  this.onSample = options.onSample;
35000
35043
  this.bufferSizeInFrames = Math.floor(this.targetSampleRate * 5);
35001
35044
  this.bufferSizeInSamples = this.bufferSizeInFrames * this.targetNumberOfChannels;
35002
- this.outputBuffer = new Float32Array(this.bufferSizeInSamples);
35003
35045
  }
35004
35046
  /**
35005
35047
  * Sets up the channel mixer to handle up/downmixing in the case where input and output channel counts don't match.
@@ -35090,6 +35132,11 @@ ${cue.notes ?? ""}`;
35090
35132
  this.sourceSampleRate = audioSample.sampleRate;
35091
35133
  this.sourceNumberOfChannels = audioSample.numberOfChannels;
35092
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
+ );
35093
35140
  this.tempSourceBuffer = new Float32Array(this.sourceSampleRate * this.sourceNumberOfChannels);
35094
35141
  this.doChannelMixerSetup();
35095
35142
  }
@@ -35107,12 +35154,12 @@ ${cue.notes ?? ""}`;
35107
35154
  if (outputFrame < this.bufferStartFrame) {
35108
35155
  continue;
35109
35156
  }
35110
- while (outputFrame >= this.bufferStartFrame + this.bufferSizeInFrames) {
35157
+ while (outputFrame >= this.bufferStartFrame + this.bufferCapacityInFrames) {
35111
35158
  await this.finalizeCurrentBuffer();
35112
35159
  this.bufferStartFrame += this.bufferSizeInFrames;
35113
35160
  }
35114
35161
  const bufferFrameIndex = outputFrame - this.bufferStartFrame;
35115
- assert(bufferFrameIndex < this.bufferSizeInFrames);
35162
+ assert(bufferFrameIndex < this.bufferCapacityInFrames);
35116
35163
  const outputTime = outputFrame / this.targetSampleRate;
35117
35164
  const inputTime = outputTime - inputStartTime;
35118
35165
  const sourcePosition = inputTime * this.sourceSampleRate;
@@ -35144,7 +35191,7 @@ ${cue.notes ?? ""}`;
35144
35191
  return;
35145
35192
  }
35146
35193
  assert(this.startTime !== null);
35147
- const samplesWritten = (this.maxWrittenFrame + 1) * this.targetNumberOfChannels;
35194
+ const samplesWritten = Math.min(this.maxWrittenFrame + 1, this.bufferSizeInFrames) * this.targetNumberOfChannels;
35148
35195
  const outputData = new Float32Array(samplesWritten);
35149
35196
  outputData.set(this.outputBuffer.subarray(0, samplesWritten));
35150
35197
  const audioSample = new AudioSample({
@@ -35155,11 +35202,15 @@ ${cue.notes ?? ""}`;
35155
35202
  data: outputData
35156
35203
  });
35157
35204
  await this.onSample(audioSample);
35158
- this.outputBuffer.fill(0);
35159
- 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;
35160
35208
  }
35161
- finalize() {
35162
- return this.finalizeCurrentBuffer();
35209
+ async finalize() {
35210
+ while (this.maxWrittenFrame !== null) {
35211
+ await this.finalizeCurrentBuffer();
35212
+ this.bufferStartFrame += this.bufferSizeInFrames;
35213
+ }
35163
35214
  }
35164
35215
  };
35165
35216
 
@@ -35240,8 +35291,6 @@ ${cue.notes ?? ""}`;
35240
35291
  /** Internal constructor. */
35241
35292
  constructor(codec) {
35242
35293
  super();
35243
- /** @internal */
35244
- this._connectedTrack = null;
35245
35294
  if (!VIDEO_CODECS.includes(codec)) {
35246
35295
  throw new TypeError(`Invalid video codec '${codec}'. Must be one of: ${VIDEO_CODECS.join(", ")}.`);
35247
35296
  }
@@ -36382,8 +36431,6 @@ ${cue.notes ?? ""}`;
36382
36431
  /** Internal constructor. */
36383
36432
  constructor(codec) {
36384
36433
  super();
36385
- /** @internal */
36386
- this._connectedTrack = null;
36387
36434
  if (!AUDIO_CODECS.includes(codec)) {
36388
36435
  throw new TypeError(`Invalid audio codec '${codec}'. Must be one of: ${AUDIO_CODECS.join(", ")}.`);
36389
36436
  }
@@ -37264,8 +37311,6 @@ ${cue.notes ?? ""}`;
37264
37311
  /** Internal constructor. */
37265
37312
  constructor(codec) {
37266
37313
  super();
37267
- /** @internal */
37268
- this._connectedTrack = null;
37269
37314
  if (!SUBTITLE_CODECS.includes(codec)) {
37270
37315
  throw new TypeError(`Invalid subtitle codec '${codec}'. Must be one of: ${SUBTITLE_CODECS.join(", ")}.`);
37271
37316
  }