mediabunny 1.42.0 → 1.43.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 (56) hide show
  1. package/dist/bundles/mediabunny.cjs +289 -66
  2. package/dist/bundles/mediabunny.min.cjs +14 -14
  3. package/dist/bundles/mediabunny.min.mjs +14 -14
  4. package/dist/bundles/mediabunny.mjs +289 -66
  5. package/dist/bundles/mediabunny.node.cjs +289 -66
  6. package/dist/mediabunny.d.ts +12 -0
  7. package/dist/modules/src/codec-data.d.ts +2 -0
  8. package/dist/modules/src/codec-data.d.ts.map +1 -1
  9. package/dist/modules/src/codec-data.js +103 -0
  10. package/dist/modules/src/codec.d.ts.map +1 -1
  11. package/dist/modules/src/codec.js +15 -0
  12. package/dist/modules/src/conversion.d.ts +7 -0
  13. package/dist/modules/src/conversion.d.ts.map +1 -1
  14. package/dist/modules/src/conversion.js +25 -11
  15. package/dist/modules/src/encode.d.ts +5 -0
  16. package/dist/modules/src/encode.d.ts.map +1 -1
  17. package/dist/modules/src/encode.js +5 -1
  18. package/dist/modules/src/index.d.ts +20 -20
  19. package/dist/modules/src/index.d.ts.map +1 -1
  20. package/dist/modules/src/isobmff/isobmff-boxes.d.ts +2 -0
  21. package/dist/modules/src/isobmff/isobmff-boxes.d.ts.map +1 -1
  22. package/dist/modules/src/isobmff/isobmff-boxes.js +36 -4
  23. package/dist/modules/src/isobmff/isobmff-demuxer.d.ts.map +1 -1
  24. package/dist/modules/src/isobmff/isobmff-demuxer.js +8 -5
  25. package/dist/modules/src/isobmff/isobmff-muxer.d.ts +4 -2
  26. package/dist/modules/src/isobmff/isobmff-muxer.d.ts.map +1 -1
  27. package/dist/modules/src/isobmff/isobmff-muxer.js +47 -21
  28. package/dist/modules/src/matroska/matroska-demuxer.d.ts.map +1 -1
  29. package/dist/modules/src/matroska/matroska-demuxer.js +7 -5
  30. package/dist/modules/src/media-sink.d.ts.map +1 -1
  31. package/dist/modules/src/media-sink.js +20 -11
  32. package/dist/modules/src/media-source.d.ts.map +1 -1
  33. package/dist/modules/src/media-source.js +14 -1
  34. package/dist/modules/src/misc.d.ts.map +1 -1
  35. package/dist/modules/src/misc.js +2 -0
  36. package/dist/modules/src/mpeg-ts/mpeg-ts-demuxer.d.ts.map +1 -1
  37. package/dist/modules/src/mpeg-ts/mpeg-ts-demuxer.js +11 -9
  38. package/dist/modules/src/sample.d.ts +2 -0
  39. package/dist/modules/src/sample.d.ts.map +1 -1
  40. package/dist/modules/src/sample.js +27 -0
  41. package/dist/modules/src/tsconfig.tsbuildinfo +1 -1
  42. package/package.json +1 -1
  43. package/src/codec-data.ts +112 -0
  44. package/src/codec.ts +31 -0
  45. package/src/conversion.ts +45 -11
  46. package/src/encode.ts +12 -1
  47. package/src/index.ts +82 -82
  48. package/src/isobmff/isobmff-boxes.ts +46 -8
  49. package/src/isobmff/isobmff-demuxer.ts +7 -4
  50. package/src/isobmff/isobmff-muxer.ts +71 -28
  51. package/src/matroska/matroska-demuxer.ts +10 -8
  52. package/src/media-sink.ts +20 -11
  53. package/src/media-source.ts +26 -1
  54. package/src/misc.ts +2 -0
  55. package/src/mpeg-ts/mpeg-ts-demuxer.ts +16 -12
  56. package/src/sample.ts +30 -0
@@ -881,6 +881,8 @@ var Mediabunny = (() => {
881
881
  return maxIndex;
882
882
  };
883
883
  var simplifyRational = (rational) => {
884
+ assert(Number.isInteger(rational.num));
885
+ assert(Number.isInteger(rational.den));
884
886
  assert(rational.den !== 0);
885
887
  let a = Math.abs(rational.num);
886
888
  let b = Math.abs(rational.den);
@@ -2069,6 +2071,21 @@ var Mediabunny = (() => {
2069
2071
  "Video chunk metadata decoder configuration must specify a valid codedHeight (positive integer)."
2070
2072
  );
2071
2073
  }
2074
+ if (metadata.decoderConfig.displayAspectWidth !== void 0 && (!Number.isInteger(metadata.decoderConfig.displayAspectWidth) || metadata.decoderConfig.displayAspectWidth <= 0)) {
2075
+ throw new TypeError(
2076
+ "Video chunk metadata decoder configuration displayAspectWidth, when defined, must be a positive integer."
2077
+ );
2078
+ }
2079
+ if (metadata.decoderConfig.displayAspectHeight !== void 0 && (!Number.isInteger(metadata.decoderConfig.displayAspectHeight) || metadata.decoderConfig.displayAspectHeight <= 0)) {
2080
+ throw new TypeError(
2081
+ "Video chunk metadata decoder configuration displayAspectHeight, when defined, must be a positive integer."
2082
+ );
2083
+ }
2084
+ if (metadata.decoderConfig.displayAspectWidth !== void 0 !== (metadata.decoderConfig.displayAspectHeight !== void 0)) {
2085
+ throw new TypeError(
2086
+ "Video chunk metadata decoder configuration must specify both displayAspectWidth and displayAspectHeight, or neither."
2087
+ );
2088
+ }
2072
2089
  if (metadata.decoderConfig.description !== void 0) {
2073
2090
  if (!isAllowSharedBufferSource(metadata.decoderConfig.description)) {
2074
2091
  throw new TypeError(
@@ -3064,6 +3081,16 @@ var Mediabunny = (() => {
3064
3081
  bitstream.skipBits(5);
3065
3082
  bitstream.skipBits(5);
3066
3083
  };
3084
+ var concatHevcNalUnits = (nalUnits, decoderConfig) => {
3085
+ if (decoderConfig.description) {
3086
+ const bytes2 = toUint8Array(decoderConfig.description);
3087
+ const lengthSizeMinusOne = bytes2[21] & 3;
3088
+ const lengthSize = lengthSizeMinusOne + 1;
3089
+ return concatNalUnitsInLengthPrefixed(nalUnits, lengthSize);
3090
+ } else {
3091
+ return concatNalUnitsInAnnexB(nalUnits);
3092
+ }
3093
+ };
3067
3094
  var iterateHevcNalUnits = (packetData, decoderConfig) => {
3068
3095
  if (decoderConfig.description) {
3069
3096
  const bytes2 = toUint8Array(decoderConfig.description);
@@ -3663,6 +3690,70 @@ var Mediabunny = (() => {
3663
3690
  return null;
3664
3691
  }
3665
3692
  };
3693
+ var sanitizeHevcPacketForChromium = (packetData, decoderConfig) => {
3694
+ const removedNalUnits = /* @__PURE__ */ new Set();
3695
+ let orderState = 0 /* audAllowed */;
3696
+ for (const loc of iterateHevcNalUnits(packetData, decoderConfig)) {
3697
+ if (orderState === 4 /* noMoreDataAllowed */) {
3698
+ removedNalUnits.add(loc.offset);
3699
+ continue;
3700
+ }
3701
+ const type = extractNalUnitTypeForHevc(packetData[loc.offset]);
3702
+ if (orderState === 3 /* eoBitstreamAllowed */ && type !== 37) {
3703
+ removedNalUnits.add(loc.offset);
3704
+ continue;
3705
+ }
3706
+ let remove = false;
3707
+ if (type === 35) {
3708
+ if (orderState > 0 /* audAllowed */) {
3709
+ remove = true;
3710
+ } else {
3711
+ orderState = 1 /* beforeFirstVcl */;
3712
+ }
3713
+ } else if (type <= 31) {
3714
+ if (orderState > 2 /* afterFirstVcl */) {
3715
+ remove = true;
3716
+ } else {
3717
+ orderState = 2 /* afterFirstVcl */;
3718
+ }
3719
+ } else if (type === 36) {
3720
+ if (orderState !== 2 /* afterFirstVcl */) {
3721
+ remove = true;
3722
+ } else {
3723
+ orderState = 3 /* eoBitstreamAllowed */;
3724
+ }
3725
+ } else if (type === 37) {
3726
+ if (orderState < 2 /* afterFirstVcl */) {
3727
+ remove = true;
3728
+ } else {
3729
+ orderState = 4 /* noMoreDataAllowed */;
3730
+ }
3731
+ } else if (type === 32 || type === 33 || type === 34 || type === 39 || type >= 41 && type <= 44 || type >= 48 && type <= 55) {
3732
+ if (orderState > 1 /* beforeFirstVcl */) {
3733
+ remove = true;
3734
+ } else {
3735
+ orderState = 1 /* beforeFirstVcl */;
3736
+ }
3737
+ } else if (type === 38 || type === 40 || type >= 45 && type <= 47 || type >= 56 && type <= 63) {
3738
+ if (orderState < 2 /* afterFirstVcl */) {
3739
+ remove = true;
3740
+ }
3741
+ }
3742
+ if (remove) {
3743
+ removedNalUnits.add(loc.offset);
3744
+ }
3745
+ }
3746
+ if (removedNalUnits.size === 0) {
3747
+ return null;
3748
+ }
3749
+ const filteredNalUnits = [];
3750
+ for (const loc of iterateHevcNalUnits(packetData, decoderConfig)) {
3751
+ if (!removedNalUnits.has(loc.offset)) {
3752
+ filteredNalUnits.push(packetData.subarray(loc.offset, loc.offset + loc.length));
3753
+ }
3754
+ }
3755
+ return concatHevcNalUnits(filteredNalUnits, decoderConfig);
3756
+ };
3666
3757
  var extractVp9CodecInfoFromPacket = (packet) => {
3667
3758
  const bitstream = new Bitstream(packet);
3668
3759
  const frameMarker = bitstream.readBits(2);
@@ -6280,10 +6371,12 @@ var Mediabunny = (() => {
6280
6371
  assert(track.info?.type === "video");
6281
6372
  const num = readU32Be(slice);
6282
6373
  const den = readU32Be(slice);
6283
- if (num > den) {
6284
- track.info.squarePixelWidth = Math.round(track.info.width * num / den);
6285
- } else {
6286
- track.info.squarePixelHeight = Math.round(track.info.height * den / num);
6374
+ if (num > 0 && den > 0) {
6375
+ if (num > den) {
6376
+ track.info.squarePixelWidth = Math.round(track.info.width * num / den);
6377
+ } else {
6378
+ track.info.squarePixelHeight = Math.round(track.info.height * den / num);
6379
+ }
6287
6380
  }
6288
6381
  }
6289
6382
  ;
@@ -9414,14 +9507,16 @@ var Mediabunny = (() => {
9414
9507
  if (this.currentTrack.info.displayWidth !== null && this.currentTrack.info.displayHeight !== null) {
9415
9508
  const num = this.currentTrack.info.displayWidth * this.currentTrack.info.height;
9416
9509
  const den = this.currentTrack.info.displayHeight * this.currentTrack.info.width;
9417
- if (num > den) {
9418
- this.currentTrack.info.squarePixelWidth = Math.round(
9419
- this.currentTrack.info.width * num / den
9420
- );
9421
- } else {
9422
- this.currentTrack.info.squarePixelHeight = Math.round(
9423
- this.currentTrack.info.height * den / num
9424
- );
9510
+ if (num > 0 && den > 0) {
9511
+ if (num > den) {
9512
+ this.currentTrack.info.squarePixelWidth = Math.round(
9513
+ this.currentTrack.info.width * num / den
9514
+ );
9515
+ } else {
9516
+ this.currentTrack.info.squarePixelHeight = Math.round(
9517
+ this.currentTrack.info.height * den / num
9518
+ );
9519
+ }
9425
9520
  }
9426
9521
  }
9427
9522
  if (this.currentTrack.codecId === CODEC_STRING_MAP.avc) {
@@ -13637,16 +13732,20 @@ var Mediabunny = (() => {
13637
13732
  const spsInfo = parseAvcSps(spsUnit);
13638
13733
  elementaryStream.info.width = spsInfo.displayWidth;
13639
13734
  elementaryStream.info.height = spsInfo.displayHeight;
13640
- if (spsInfo.pixelAspectRatio.num > spsInfo.pixelAspectRatio.den) {
13641
- elementaryStream.info.squarePixelWidth = Math.round(
13642
- elementaryStream.info.width * spsInfo.pixelAspectRatio.num / spsInfo.pixelAspectRatio.den
13643
- );
13644
- elementaryStream.info.squarePixelHeight = elementaryStream.info.height;
13645
- } else {
13646
- elementaryStream.info.squarePixelWidth = elementaryStream.info.width;
13647
- elementaryStream.info.squarePixelHeight = Math.round(
13648
- elementaryStream.info.height * spsInfo.pixelAspectRatio.den / spsInfo.pixelAspectRatio.num
13649
- );
13735
+ const num = spsInfo.pixelAspectRatio.num;
13736
+ const den = spsInfo.pixelAspectRatio.den;
13737
+ if (num > 0 && den > 0) {
13738
+ if (num > den) {
13739
+ elementaryStream.info.squarePixelWidth = Math.round(
13740
+ elementaryStream.info.width * num / den
13741
+ );
13742
+ elementaryStream.info.squarePixelHeight = elementaryStream.info.height;
13743
+ } else {
13744
+ elementaryStream.info.squarePixelWidth = elementaryStream.info.width;
13745
+ elementaryStream.info.squarePixelHeight = Math.round(
13746
+ elementaryStream.info.height * den / num
13747
+ );
13748
+ }
13650
13749
  }
13651
13750
  elementaryStream.info.colorSpace = {
13652
13751
  primaries: COLOR_PRIMARIES_MAP_INVERSE[spsInfo.colourPrimaries],
@@ -20021,6 +20120,20 @@ var Mediabunny = (() => {
20021
20120
  var isAudioData = (x) => {
20022
20121
  return typeof AudioData !== "undefined" && x instanceof AudioData;
20023
20122
  };
20123
+ var toInterleavedAudioFormat = (format) => {
20124
+ switch (format) {
20125
+ case "u8-planar":
20126
+ return "u8";
20127
+ case "s16-planar":
20128
+ return "s16";
20129
+ case "s32-planar":
20130
+ return "s32";
20131
+ case "f32-planar":
20132
+ return "f32";
20133
+ default:
20134
+ return format;
20135
+ }
20136
+ };
20024
20137
  var doAudioDataCopyToWebKitWorkaround = (audioData, destView, srcFormat, destFormat, numChannels, planeIndex, frameOffset, copyFrameCount) => {
20025
20138
  const readFn = getReadFunction(srcFormat);
20026
20139
  const writeFn = getWriteFunction(destFormat);
@@ -20100,6 +20213,19 @@ var Mediabunny = (() => {
20100
20213
  }
20101
20214
  }
20102
20215
  };
20216
+ var audioSampleToInterleavedFormat = (sample, format) => {
20217
+ const size = sample.allocationSize({ format, planeIndex: 0 });
20218
+ const buffer = new ArrayBuffer(size);
20219
+ sample.copyTo(buffer, { format, planeIndex: 0 });
20220
+ return new AudioSample({
20221
+ data: buffer,
20222
+ format,
20223
+ numberOfChannels: sample.numberOfChannels,
20224
+ sampleRate: sample.sampleRate,
20225
+ timestamp: sample.timestamp,
20226
+ duration: sample.duration
20227
+ });
20228
+ };
20103
20229
 
20104
20230
  // src/encode.ts
20105
20231
  var canEncodeVideoMemo = /* @__PURE__ */ new Map();
@@ -20233,7 +20359,7 @@ var Mediabunny = (() => {
20233
20359
  if (!AUDIO_CODECS.includes(config.codec)) {
20234
20360
  throw new TypeError(`Invalid audio codec '${config.codec}'. Must be one of: ${AUDIO_CODECS.join(", ")}.`);
20235
20361
  }
20236
- if (config.bitrate === void 0 && (!PCM_AUDIO_CODECS.includes(config.codec) || config.codec === "flac")) {
20362
+ if (config.bitrate === void 0 && !(PCM_AUDIO_CODECS.includes(config.codec) || config.codec === "flac")) {
20237
20363
  throw new TypeError("config.bitrate must be provided for compressed audio codecs.");
20238
20364
  }
20239
20365
  if (config.bitrate !== void 0 && !(config.bitrate instanceof Quality) && (!Number.isInteger(config.bitrate) || config.bitrate <= 0)) {
@@ -20249,6 +20375,9 @@ var Mediabunny = (() => {
20249
20375
  if (config.transform.sampleRate !== void 0 && (!Number.isInteger(config.transform.sampleRate) || config.transform.sampleRate <= 0)) {
20250
20376
  throw new TypeError("config.transform.sampleRate, when provided, must be a positive integer.");
20251
20377
  }
20378
+ if (config.transform.sampleFormat !== void 0 && !["u8", "s16", "s32", "f32"].includes(config.transform.sampleFormat)) {
20379
+ throw new TypeError("config.transform.sampleFormat, when provided, must be one of: u8, s16, s32, f32.");
20380
+ }
20252
20381
  if (config.transform.process !== void 0 && typeof config.transform.process !== "function") {
20253
20382
  throw new TypeError("config.transform.process, when provided, must be a function.");
20254
20383
  }
@@ -21389,16 +21518,23 @@ var Mediabunny = (() => {
21389
21518
  if (!isWebKit()) {
21390
21519
  insertSorted(this.inputTimestamps, packet.timestamp, (x) => x);
21391
21520
  }
21392
- if (isChromium() && this.currentPacketIndex === 0 && this.codec === "avc") {
21393
- const filteredNalUnits = [];
21394
- for (const loc of iterateAvcNalUnits(packet.data, this.decoderConfig)) {
21395
- const type = extractNalUnitTypeForAvc(packet.data[loc.offset]);
21396
- if (!(type >= 20 && type <= 31)) {
21397
- filteredNalUnits.push(packet.data.subarray(loc.offset, loc.offset + loc.length));
21521
+ if (isChromium() && this.currentPacketIndex === 0) {
21522
+ if (this.codec === "avc") {
21523
+ const filteredNalUnits = [];
21524
+ for (const loc of iterateAvcNalUnits(packet.data, this.decoderConfig)) {
21525
+ const type = extractNalUnitTypeForAvc(packet.data[loc.offset]);
21526
+ if (!(type >= 20 && type <= 31)) {
21527
+ filteredNalUnits.push(packet.data.subarray(loc.offset, loc.offset + loc.length));
21528
+ }
21529
+ }
21530
+ const newData = concatAvcNalUnits(filteredNalUnits, this.decoderConfig);
21531
+ packet = new EncodedPacket(newData, packet.type, packet.timestamp, packet.duration);
21532
+ } else if (this.codec === "hevc") {
21533
+ const sanitizedData = sanitizeHevcPacketForChromium(packet.data, this.decoderConfig);
21534
+ if (sanitizedData) {
21535
+ packet = new EncodedPacket(sanitizedData, packet.type, packet.timestamp, packet.duration);
21398
21536
  }
21399
21537
  }
21400
- const newData = concatAvcNalUnits(filteredNalUnits, this.decoderConfig);
21401
- packet = new EncodedPacket(newData, packet.type, packet.timestamp, packet.duration);
21402
21538
  }
21403
21539
  this.decoder.decode(packet.toEncodedVideoChunk());
21404
21540
  this.decodeAlphaData(packet);
@@ -25266,6 +25402,11 @@ var Mediabunny = (() => {
25266
25402
  view.setUint32(4, value, false);
25267
25403
  return [bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7]];
25268
25404
  };
25405
+ var i64 = (value) => {
25406
+ view.setInt32(0, Math.floor(value / 2 ** 32), false);
25407
+ view.setUint32(4, value, false);
25408
+ return [bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7]];
25409
+ };
25269
25410
  var fixed_8_8 = (value) => {
25270
25411
  view.setInt16(0, 2 ** 8 * value, false);
25271
25412
  return [bytes[0], bytes[1]];
@@ -25436,10 +25577,10 @@ var Mediabunny = (() => {
25436
25577
  udta(muxer)
25437
25578
  ]);
25438
25579
  var mvhd = (creationTime, trackDatas) => {
25439
- const duration = intoTimescale(Math.max(
25580
+ const duration = Math.max(
25440
25581
  0,
25441
- ...trackDatas.map((x) => presentationSpan(x))
25442
- ), GLOBAL_TIMESCALE);
25582
+ ...trackDatas.map((trackData) => intoTimescale(presentationSpan(trackData), GLOBAL_TIMESCALE) + intoTimescale(trackData.startTimestampOffset ?? 0, GLOBAL_TIMESCALE))
25583
+ );
25443
25584
  const nextTrackId = Math.max(0, ...trackDatas.map((x) => x.track.id)) + 1;
25444
25585
  const needsU64 = !isU32(creationTime) || !isU32(duration);
25445
25586
  const u32OrU64 = needsU64 ? u64 : u32;
@@ -25472,7 +25613,8 @@ var Mediabunny = (() => {
25472
25613
  }
25473
25614
  let minTimestamp = Infinity;
25474
25615
  let maxEndTimestamp = -Infinity;
25475
- for (const sample of trackData.samples) {
25616
+ for (let i = 0; i < trackData.samples.length; i++) {
25617
+ const sample = trackData.samples[i];
25476
25618
  if (sample.timestamp < minTimestamp) {
25477
25619
  minTimestamp = sample.timestamp;
25478
25620
  }
@@ -25487,8 +25629,10 @@ var Mediabunny = (() => {
25487
25629
  };
25488
25630
  var trak = (trackData, creationTime) => {
25489
25631
  const trackMetadata = getTrackMetadata(trackData);
25632
+ const needsEditList = trackData.startTimestampOffset !== null && trackData.startTimestampOffset > 0;
25490
25633
  return box("trak", void 0, [
25491
25634
  tkhd(trackData, creationTime),
25635
+ needsEditList ? edts(trackData, trackData.startTimestampOffset) : null,
25492
25636
  mdia(trackData, creationTime),
25493
25637
  trackMetadata.name !== void 0 ? box("udta", void 0, [
25494
25638
  box("name", [
@@ -25499,10 +25643,7 @@ var Mediabunny = (() => {
25499
25643
  ]);
25500
25644
  };
25501
25645
  var tkhd = (trackData, creationTime) => {
25502
- const durationInGlobalTimescale = intoTimescale(
25503
- presentationSpan(trackData),
25504
- GLOBAL_TIMESCALE
25505
- );
25646
+ const durationInGlobalTimescale = intoTimescale(presentationSpan(trackData), GLOBAL_TIMESCALE) + intoTimescale(trackData.startTimestampOffset ?? 0, GLOBAL_TIMESCALE);
25506
25647
  const needsU64 = !isU32(creationTime) || !isU32(durationInGlobalTimescale);
25507
25648
  const u32OrU64 = needsU64 ? u64 : u32;
25508
25649
  let matrix;
@@ -25545,6 +25686,33 @@ var Mediabunny = (() => {
25545
25686
  // Track height
25546
25687
  ]);
25547
25688
  };
25689
+ var edts = (trackData, offset) => {
25690
+ const startOffset = intoTimescale(offset, GLOBAL_TIMESCALE);
25691
+ const mediaDuration = intoTimescale(presentationSpan(trackData), GLOBAL_TIMESCALE);
25692
+ const needs64Bits = !isU32(startOffset) || !isU32(mediaDuration);
25693
+ const u32OrU64 = needs64Bits ? u64 : u32;
25694
+ const i32OrI64 = needs64Bits ? i64 : i32;
25695
+ return box("edts", void 0, [
25696
+ fullBox("elst", needs64Bits ? 1 : 0, 0, [
25697
+ u32(2),
25698
+ // Entry count
25699
+ // #1
25700
+ u32OrU64(startOffset),
25701
+ // Segment duration
25702
+ i32OrI64(-1),
25703
+ // Media time
25704
+ fixed_16_16(1),
25705
+ // Media rate
25706
+ // #2
25707
+ u32OrU64(mediaDuration),
25708
+ // Segment duration
25709
+ i32OrI64(0),
25710
+ // Media time
25711
+ fixed_16_16(1)
25712
+ // Media rate
25713
+ ])
25714
+ ]);
25715
+ };
25548
25716
  var mdia = (trackData, creationTime) => box("mdia", void 0, [
25549
25717
  mdhd(trackData, creationTime),
25550
25718
  hdlr(true, TRACK_TYPE_TO_COMPONENT_SUBTYPE[trackData.type], TRACK_TYPE_TO_HANDLER_NAME[trackData.type]),
@@ -27473,7 +27641,7 @@ var Mediabunny = (() => {
27473
27641
  };
27474
27642
 
27475
27643
  // src/isobmff/isobmff-muxer.ts
27476
- var GLOBAL_TIMESCALE = 1e3;
27644
+ var GLOBAL_TIMESCALE = 57600;
27477
27645
  var TIMESTAMP_OFFSET = 2082844800;
27478
27646
  var getTrackMetadata = (trackData) => {
27479
27647
  const metadata = {};
@@ -27641,7 +27809,10 @@ var Mediabunny = (() => {
27641
27809
  decoderConfig.description = serializeHevcDecoderConfigurationRecord(decoderConfigurationRecord);
27642
27810
  requiresAnnexBTransformation = true;
27643
27811
  }
27644
- const timescale = computeRationalApproximation(1 / (track.metadata.frameRate ?? 57600), 1e6).denominator;
27812
+ const timescale = computeRationalApproximation(
27813
+ 1 / (track.metadata.frameRate ?? GLOBAL_TIMESCALE),
27814
+ 1e6
27815
+ ).denominator;
27645
27816
  const displayAspectWidth = decoderConfig.displayAspectWidth;
27646
27817
  const displayAspectHeight = decoderConfig.displayAspectHeight;
27647
27818
  const pixelAspectRatio = displayAspectWidth === void 0 || displayAspectHeight === void 0 ? { num: 1, den: 1 } : simplifyRational({
@@ -27667,6 +27838,7 @@ var Mediabunny = (() => {
27667
27838
  compositionTimeOffsetTable: [],
27668
27839
  lastTimescaleUnits: null,
27669
27840
  lastSample: null,
27841
+ startTimestampOffset: null,
27670
27842
  finalizedChunks: [],
27671
27843
  currentChunk: null,
27672
27844
  compactlyCodedChunkTable: [],
@@ -27717,6 +27889,7 @@ var Mediabunny = (() => {
27717
27889
  sampleRate: meta.decoderConfig.sampleRate,
27718
27890
  decoderConfig,
27719
27891
  requiresPcmTransformation: !this.isFragmented && PCM_AUDIO_CODECS.includes(track.source._codec),
27892
+ expectedNextPcmPacketTimestamp: null,
27720
27893
  requiresAdtsStripping,
27721
27894
  firstPacket: packet
27722
27895
  },
@@ -27728,6 +27901,7 @@ var Mediabunny = (() => {
27728
27901
  compositionTimeOffsetTable: [],
27729
27902
  lastTimescaleUnits: null,
27730
27903
  lastSample: null,
27904
+ startTimestampOffset: null,
27731
27905
  finalizedChunks: [],
27732
27906
  currentChunk: null,
27733
27907
  compactlyCodedChunkTable: [],
@@ -27764,6 +27938,7 @@ var Mediabunny = (() => {
27764
27938
  compositionTimeOffsetTable: [],
27765
27939
  lastTimescaleUnits: null,
27766
27940
  lastSample: null,
27941
+ startTimestampOffset: null,
27767
27942
  finalizedChunks: [],
27768
27943
  currentChunk: null,
27769
27944
  compactlyCodedChunkTable: [],
@@ -27824,31 +27999,48 @@ var Mediabunny = (() => {
27824
27999
  const headerLength = adtsFrame.crcCheck === null ? MIN_ADTS_FRAME_HEADER_SIZE : MAX_ADTS_FRAME_HEADER_SIZE;
27825
28000
  packetData = packetData.subarray(headerLength);
27826
28001
  }
27827
- const timestamp = this.validateAndNormalizeTimestamp(
28002
+ let timestamp = this.validateAndNormalizeTimestamp(
27828
28003
  trackData.track,
27829
28004
  packet.timestamp,
27830
28005
  packet.type === "key"
27831
28006
  );
28007
+ let duration = packet.duration;
28008
+ if (trackData.info.requiresPcmTransformation) {
28009
+ const pcmInfo = parsePcmCodec(
28010
+ trackData.info.decoderConfig.codec
28011
+ );
28012
+ const frameSize = pcmInfo.sampleSize * trackData.info.numberOfChannels;
28013
+ duration = packetData.byteLength / frameSize / trackData.info.sampleRate;
28014
+ if (trackData.info.expectedNextPcmPacketTimestamp !== null) {
28015
+ const diff = timestamp - trackData.info.expectedNextPcmPacketTimestamp;
28016
+ if (diff < 0.01) {
28017
+ timestamp = trackData.info.expectedNextPcmPacketTimestamp;
28018
+ } else {
28019
+ const paddedDuration = await this.padWithSilence(
28020
+ trackData,
28021
+ trackData.info.expectedNextPcmPacketTimestamp,
28022
+ diff
28023
+ );
28024
+ timestamp = trackData.info.expectedNextPcmPacketTimestamp + paddedDuration;
28025
+ }
28026
+ }
28027
+ trackData.info.expectedNextPcmPacketTimestamp = timestamp + duration;
28028
+ }
27832
28029
  const internalSample = this.createSampleForTrack(
27833
28030
  trackData,
27834
28031
  packetData,
27835
28032
  timestamp,
27836
- packet.duration,
28033
+ duration,
27837
28034
  packet.type
27838
28035
  );
27839
- if (trackData.info.requiresPcmTransformation) {
27840
- await this.maybePadWithSilence(trackData, timestamp);
27841
- }
27842
28036
  await this.registerSample(trackData, internalSample);
27843
28037
  } finally {
27844
28038
  release();
27845
28039
  }
27846
28040
  }
27847
- async maybePadWithSilence(trackData, untilTimestamp) {
27848
- const lastSample = last(trackData.samples);
27849
- const lastEndTimestamp = lastSample ? lastSample.timestamp + lastSample.duration : 0;
27850
- const delta = untilTimestamp - lastEndTimestamp;
27851
- const deltaInTimescale = intoTimescale(delta, trackData.timescale);
28041
+ async padWithSilence(trackData, timestamp, duration) {
28042
+ const deltaInTimescale = intoTimescale(duration, trackData.timescale);
28043
+ duration = deltaInTimescale / trackData.timescale;
27852
28044
  if (deltaInTimescale > 0) {
27853
28045
  const { sampleSize, silentValue } = parsePcmCodec(
27854
28046
  trackData.info.decoderConfig.codec
@@ -27858,12 +28050,13 @@ var Mediabunny = (() => {
27858
28050
  const paddingSample = this.createSampleForTrack(
27859
28051
  trackData,
27860
28052
  new Uint8Array(data.buffer),
27861
- lastEndTimestamp,
27862
- delta,
28053
+ timestamp,
28054
+ duration,
27863
28055
  "key"
27864
28056
  );
27865
28057
  await this.registerSample(trackData, paddingSample);
27866
28058
  }
28059
+ return duration;
27867
28060
  }
27868
28061
  async addSubtitleCue(track, cue, meta) {
27869
28062
  const release = await this.mutex.acquire();
@@ -27964,6 +28157,9 @@ var Mediabunny = (() => {
27964
28157
  return;
27965
28158
  }
27966
28159
  if (trackData.type === "audio" && trackData.info.requiresPcmTransformation) {
28160
+ if (!this.isFragmented) {
28161
+ trackData.startTimestampOffset ??= trackData.timestampProcessingQueue[0].timestamp;
28162
+ }
27967
28163
  let totalDuration = 0;
27968
28164
  for (let i = 0; i < trackData.timestampProcessingQueue.length; i++) {
27969
28165
  const sample = trackData.timestampProcessingQueue[i];
@@ -27983,12 +28179,12 @@ var Mediabunny = (() => {
27983
28179
  return;
27984
28180
  }
27985
28181
  const sortedTimestamps = trackData.timestampProcessingQueue.map((x) => x.timestamp).sort((a, b) => a - b);
28182
+ if (!this.isFragmented) {
28183
+ trackData.startTimestampOffset ??= sortedTimestamps[0];
28184
+ }
27986
28185
  for (let i = 0; i < trackData.timestampProcessingQueue.length; i++) {
27987
28186
  const sample = trackData.timestampProcessingQueue[i];
27988
28187
  sample.decodeTimestamp = sortedTimestamps[i];
27989
- if (!this.isFragmented && trackData.lastTimescaleUnits === null) {
27990
- sample.decodeTimestamp = 0;
27991
- }
27992
28188
  const sampleCompositionTimeOffset = intoTimescale(sample.timestamp - sample.decodeTimestamp, trackData.timescale);
27993
28189
  const durationInTimescale = intoTimescale(sample.duration, trackData.timescale);
27994
28190
  if (trackData.lastTimescaleUnits !== null) {
@@ -28352,6 +28548,12 @@ var Mediabunny = (() => {
28352
28548
  } else {
28353
28549
  for (const trackData of this.trackDatas) {
28354
28550
  await this.finalizeCurrentChunk(trackData);
28551
+ assert(trackData.startTimestampOffset !== null);
28552
+ for (let i = 0; i < trackData.samples.length; i++) {
28553
+ const sample = trackData.samples[i];
28554
+ sample.timestamp -= trackData.startTimestampOffset;
28555
+ sample.decodeTimestamp -= trackData.startTimestampOffset;
28556
+ }
28355
28557
  }
28356
28558
  }
28357
28559
  assert(this.writer);
@@ -32320,6 +32522,14 @@ ${cue.notes ?? ""}`;
32320
32522
  */
32321
32523
  async processAndEncode(audioSample, shouldClose) {
32322
32524
  const config = this.encodingConfig;
32525
+ if (config.transform?.sampleFormat !== void 0 && toInterleavedAudioFormat(audioSample.format) !== config.transform.sampleFormat) {
32526
+ const newSample = audioSampleToInterleavedFormat(audioSample, config.transform.sampleFormat);
32527
+ if (shouldClose) {
32528
+ audioSample.close();
32529
+ }
32530
+ audioSample = newSample;
32531
+ shouldClose = true;
32532
+ }
32323
32533
  if (config.transform?.process) {
32324
32534
  let processed = config.transform.process(audioSample);
32325
32535
  if (processed instanceof Promise) {
@@ -32339,6 +32549,9 @@ ${cue.notes ?? ""}`;
32339
32549
  }
32340
32550
  await this.encodeSample(sample, true);
32341
32551
  }
32552
+ if (shouldClose) {
32553
+ audioSample.close();
32554
+ }
32342
32555
  } else {
32343
32556
  await this.encodeSample(audioSample, shouldClose);
32344
32557
  }
@@ -35418,6 +35631,9 @@ ${cue.notes ?? ""}`;
35418
35631
  if (audioOptions?.sampleRate !== void 0 && (!Number.isInteger(audioOptions.sampleRate) || audioOptions.sampleRate <= 0)) {
35419
35632
  throw new TypeError("options.audio.sampleRate, when provided, must be a positive integer.");
35420
35633
  }
35634
+ if (audioOptions?.sampleFormat !== void 0 && !["u8", "s16", "s32", "f32"].includes(audioOptions.sampleFormat)) {
35635
+ throw new TypeError("options.audio.sampleFormat, when provided, must be one of: u8, s16, s32, f32.");
35636
+ }
35421
35637
  if (audioOptions?.process !== void 0 && typeof audioOptions.process !== "function") {
35422
35638
  throw new TypeError("options.audio.process, when provided, must be a function.");
35423
35639
  }
@@ -36036,7 +36252,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
36036
36252
  timestamp: lastCanvasTimestamp + i / frameRate,
36037
36253
  duration: 1 / frameRate
36038
36254
  });
36039
- await this._registerVideoSample(track, trackOptions, outputTrackId, source, sample);
36255
+ await this._registerVideoSample(trackOptions, outputTrackId, source, sample);
36040
36256
  sample.close();
36041
36257
  }
36042
36258
  };
@@ -36063,7 +36279,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
36063
36279
  timestamp: adjustedSampleTimestamp,
36064
36280
  duration: frameRate !== void 0 ? 1 / frameRate : duration
36065
36281
  });
36066
- await this._registerVideoSample(track, trackOptions, outputTrackId, source, sample);
36282
+ await this._registerVideoSample(trackOptions, outputTrackId, source, sample);
36067
36283
  sample.close();
36068
36284
  if (frameRate !== void 0) {
36069
36285
  lastCanvas = canvas;
@@ -36093,7 +36309,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
36093
36309
  for (let i = 1; i < frameDifference; i++) {
36094
36310
  lastSample.setTimestamp(lastSampleTimestamp + i / frameRate);
36095
36311
  lastSample.setDuration(1 / frameRate);
36096
- await this._registerVideoSample(track, trackOptions, outputTrackId, source, lastSample);
36312
+ await this._registerVideoSample(trackOptions, outputTrackId, source, lastSample);
36097
36313
  }
36098
36314
  lastSample.close();
36099
36315
  };
@@ -36121,7 +36337,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
36121
36337
  sample.setDuration(1 / frameRate);
36122
36338
  }
36123
36339
  sample.setTimestamp(adjustedSampleTimestamp);
36124
- await this._registerVideoSample(track, trackOptions, outputTrackId, source, sample);
36340
+ await this._registerVideoSample(trackOptions, outputTrackId, source, sample);
36125
36341
  if (frameRate !== void 0) {
36126
36342
  lastSample = sample;
36127
36343
  lastSampleTimestamp = adjustedSampleTimestamp;
@@ -36160,7 +36376,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
36160
36376
  this._outputOwnTrackGroups.push(ownGroup);
36161
36377
  }
36162
36378
  /** @internal */
36163
- async _registerVideoSample(track, trackOptions, outputTrackId, source, sample) {
36379
+ async _registerVideoSample(trackOptions, outputTrackId, source, sample) {
36164
36380
  if (this._canceled) {
36165
36381
  return;
36166
36382
  }
@@ -36224,7 +36440,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
36224
36440
  let sampleRate = trackOptions.sampleRate ?? originalSampleRate;
36225
36441
  let needsResample = numberOfChannels !== originalNumberOfChannels || sampleRate !== originalSampleRate || firstTimestamp < this._startTimestamp || firstTimestamp > this._startTimestamp && !this.output.format.supportsTimestampedMediaData;
36226
36442
  let audioCodecs = this.output.format.getSupportedAudioCodecs();
36227
- if (!trackOptions.forceTranscode && !trackOptions.bitrate && !needsResample && audioCodecs.includes(sourceCodec) && (!trackOptions.codec || trackOptions.codec === sourceCodec) && !trackOptions.process) {
36443
+ if (!trackOptions.forceTranscode && !trackOptions.bitrate && !needsResample && audioCodecs.includes(sourceCodec) && (!trackOptions.codec || trackOptions.codec === sourceCodec) && !trackOptions.process && trackOptions.sampleFormat === void 0) {
36228
36444
  const source = new EncodedAudioPacketSource(sourceCodec);
36229
36445
  audioSource = source;
36230
36446
  this._trackPromises.push((async () => {
@@ -36321,7 +36537,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
36321
36537
  return;
36322
36538
  }
36323
36539
  sample.setTimestamp(sample.timestamp - this._startTimestamp);
36324
- await this._registerAudioSample(track, trackOptions, outputTrackId, source, sample);
36540
+ await this._registerAudioSample(trackOptions, outputTrackId, source, sample);
36325
36541
  sample.close();
36326
36542
  }
36327
36543
  source.close();
@@ -36348,10 +36564,14 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
36348
36564
  this._outputOwnTrackGroups.push(ownGroup);
36349
36565
  }
36350
36566
  /** @internal */
36351
- async _registerAudioSample(track, trackOptions, outputTrackId, source, sample) {
36567
+ async _registerAudioSample(trackOptions, outputTrackId, source, inputSample) {
36352
36568
  if (this._canceled) {
36353
36569
  return;
36354
36570
  }
36571
+ let sample = inputSample;
36572
+ if (trackOptions.sampleFormat !== void 0 && toInterleavedAudioFormat(sample.format) !== trackOptions.sampleFormat) {
36573
+ sample = audioSampleToInterleavedFormat(sample, trackOptions.sampleFormat);
36574
+ }
36355
36575
  this._reportProgress(outputTrackId, sample.timestamp + sample.duration);
36356
36576
  let finalSamples;
36357
36577
  if (!trackOptions.process) {
@@ -36380,8 +36600,11 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
36380
36600
  }
36381
36601
  }
36382
36602
  } finally {
36603
+ if (sample !== inputSample) {
36604
+ sample.close();
36605
+ }
36383
36606
  for (const finalSample of finalSamples) {
36384
- if (finalSample !== sample) {
36607
+ if (finalSample !== inputSample) {
36385
36608
  finalSample.close();
36386
36609
  }
36387
36610
  }
@@ -36402,7 +36625,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
36402
36625
  endTime: this._endTimestamp,
36403
36626
  onSample: async (sample) => {
36404
36627
  sample.setTimestamp(sample.timestamp - this._startTimestamp);
36405
- await this._registerAudioSample(track, trackOptions, outputTrackId, source, sample);
36628
+ await this._registerAudioSample(trackOptions, outputTrackId, source, sample);
36406
36629
  sample.close();
36407
36630
  }
36408
36631
  });