mediabunny 1.55.7 → 1.56.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 (82) hide show
  1. package/dist/bundles/mediabunny.cjs +655 -204
  2. package/dist/bundles/mediabunny.min.cjs +13 -13
  3. package/dist/bundles/mediabunny.min.mjs +13 -13
  4. package/dist/bundles/mediabunny.mjs +655 -204
  5. package/dist/bundles/mediabunny.node.cjs +655 -204
  6. package/dist/mediabunny.d.ts +112 -23
  7. package/dist/modules/shared/bitstream.d.ts +1 -0
  8. package/dist/modules/shared/bitstream.d.ts.map +1 -1
  9. package/dist/modules/shared/bitstream.js +10 -1
  10. package/dist/modules/src/codec-data.d.ts +12 -0
  11. package/dist/modules/src/codec-data.d.ts.map +1 -1
  12. package/dist/modules/src/codec-data.js +98 -20
  13. package/dist/modules/src/conversion.d.ts +57 -12
  14. package/dist/modules/src/conversion.d.ts.map +1 -1
  15. package/dist/modules/src/conversion.js +354 -120
  16. package/dist/modules/src/index.d.ts +2 -2
  17. package/dist/modules/src/index.d.ts.map +1 -1
  18. package/dist/modules/src/isobmff/isobmff-boxes.d.ts +1 -1
  19. package/dist/modules/src/isobmff/isobmff-boxes.d.ts.map +1 -1
  20. package/dist/modules/src/isobmff/isobmff-boxes.js +59 -29
  21. package/dist/modules/src/isobmff/isobmff-demuxer.d.ts.map +1 -1
  22. package/dist/modules/src/isobmff/isobmff-demuxer.js +0 -4
  23. package/dist/modules/src/isobmff/isobmff-muxer.d.ts +1 -1
  24. package/dist/modules/src/isobmff/isobmff-muxer.d.ts.map +1 -1
  25. package/dist/modules/src/isobmff/isobmff-muxer.js +14 -6
  26. package/dist/modules/src/matroska/matroska-demuxer.d.ts +1 -0
  27. package/dist/modules/src/matroska/matroska-demuxer.d.ts.map +1 -1
  28. package/dist/modules/src/matroska/matroska-demuxer.js +20 -8
  29. package/dist/modules/src/matroska/matroska-muxer.d.ts +1 -0
  30. package/dist/modules/src/matroska/matroska-muxer.d.ts.map +1 -1
  31. package/dist/modules/src/matroska/matroska-muxer.js +10 -1
  32. package/dist/modules/src/media-sink.d.ts.map +1 -1
  33. package/dist/modules/src/media-sink.js +34 -9
  34. package/dist/modules/src/media-source.d.ts +17 -5
  35. package/dist/modules/src/media-source.d.ts.map +1 -1
  36. package/dist/modules/src/media-source.js +14 -9
  37. package/dist/modules/src/metadata.d.ts +6 -4
  38. package/dist/modules/src/metadata.d.ts.map +1 -1
  39. package/dist/modules/src/metadata.js +2 -1
  40. package/dist/modules/src/misc.d.ts +3 -0
  41. package/dist/modules/src/misc.d.ts.map +1 -1
  42. package/dist/modules/src/misc.js +13 -0
  43. package/dist/modules/src/mpeg-ts/mpeg-ts-demuxer.d.ts +5 -0
  44. package/dist/modules/src/mpeg-ts/mpeg-ts-demuxer.d.ts.map +1 -1
  45. package/dist/modules/src/mpeg-ts/mpeg-ts-demuxer.js +37 -16
  46. package/dist/modules/src/mpeg-ts/mpeg-ts-misc.d.ts +1 -0
  47. package/dist/modules/src/mpeg-ts/mpeg-ts-misc.d.ts.map +1 -1
  48. package/dist/modules/src/mpeg-ts/mpeg-ts-misc.js +1 -0
  49. package/dist/modules/src/mpeg-ts/mpeg-ts-muxer.d.ts.map +1 -1
  50. package/dist/modules/src/mpeg-ts/mpeg-ts-muxer.js +14 -10
  51. package/dist/modules/src/muxer.d.ts.map +1 -1
  52. package/dist/modules/src/muxer.js +0 -3
  53. package/dist/modules/src/output-format.d.ts +17 -0
  54. package/dist/modules/src/output-format.d.ts.map +1 -1
  55. package/dist/modules/src/output-format.js +39 -1
  56. package/dist/modules/src/sample.d.ts +2 -2
  57. package/dist/modules/src/sample.d.ts.map +1 -1
  58. package/dist/modules/src/sample.js +16 -16
  59. package/dist/modules/src/source.d.ts +10 -0
  60. package/dist/modules/src/source.d.ts.map +1 -1
  61. package/dist/modules/src/source.js +38 -3
  62. package/dist/modules/src/tsconfig.tsbuildinfo +1 -1
  63. package/package.json +1 -1
  64. package/src/codec-data.ts +112 -20
  65. package/src/conversion.ts +461 -100
  66. package/src/index.ts +2 -0
  67. package/src/isobmff/isobmff-boxes.ts +76 -32
  68. package/src/isobmff/isobmff-demuxer.ts +0 -5
  69. package/src/isobmff/isobmff-muxer.ts +22 -12
  70. package/src/matroska/matroska-demuxer.ts +24 -11
  71. package/src/matroska/matroska-muxer.ts +14 -1
  72. package/src/media-sink.ts +39 -8
  73. package/src/media-source.ts +28 -8
  74. package/src/metadata.ts +8 -5
  75. package/src/misc.ts +16 -0
  76. package/src/mpeg-ts/mpeg-ts-demuxer.ts +52 -14
  77. package/src/mpeg-ts/mpeg-ts-misc.ts +1 -0
  78. package/src/mpeg-ts/mpeg-ts-muxer.ts +20 -10
  79. package/src/muxer.ts +0 -4
  80. package/src/output-format.ts +58 -1
  81. package/src/sample.ts +16 -16
  82. package/src/source.ts +55 -3
@@ -259,6 +259,9 @@ var Mediabunny = (() => {
259
259
  var isU32 = (value) => {
260
260
  return value >= 0 && value < 2 ** 32;
261
261
  };
262
+ var isI32 = (value) => {
263
+ return value >= -(2 ** 31) && value < 2 ** 31;
264
+ };
262
265
  var readExpGolomb = (bitstream) => {
263
266
  let leadingZeroBits = 0;
264
267
  while (bitstream.readBits(1) === 0 && leadingZeroBits < 32) {
@@ -270,6 +273,13 @@ var Mediabunny = (() => {
270
273
  const result = (1 << leadingZeroBits) - 1 + bitstream.readBits(leadingZeroBits);
271
274
  return result;
272
275
  };
276
+ var writeExpGolomb = (bitstream, value) => {
277
+ const codeNum = value + 1;
278
+ const leadingZeroBits = Math.floor(Math.log2(codeNum));
279
+ bitstream.writeBits(leadingZeroBits, 0);
280
+ bitstream.writeBits(1, 1);
281
+ bitstream.writeBits(leadingZeroBits, codeNum - 2 ** leadingZeroBits);
282
+ };
273
283
  var readSignedExpGolomb = (bitstream) => {
274
284
  const codeNum = readExpGolomb(bitstream);
275
285
  return (codeNum & 1) === 0 ? -(codeNum >> 1) : codeNum + 1 >> 1;
@@ -570,6 +580,9 @@ var Mediabunny = (() => {
570
580
  var lerp = (from, to, t) => {
571
581
  return from + (to - from) * t;
572
582
  };
583
+ var modEuclid = (value, modulus) => {
584
+ return value - Math.floor(value / modulus) * modulus;
585
+ };
573
586
  var UNDETERMINED_LANGUAGE = "und";
574
587
  var roundIfAlmostInteger = (value) => {
575
588
  const rounded = Math.round(value);
@@ -1293,9 +1306,9 @@ var Mediabunny = (() => {
1293
1306
  throw new TypeError("tags.raw, when provided, must be an object.");
1294
1307
  }
1295
1308
  for (const value of Object.values(tags.raw)) {
1296
- if (value !== null && typeof value !== "string" && !(value instanceof Uint8Array) && !(value instanceof RichImageData) && !(value instanceof AttachedFile) && !isRecordStringString(value)) {
1309
+ if (value !== null && typeof value !== "string" && !(Array.isArray(value) && value.every((x) => typeof x === "string")) && !(value instanceof Uint8Array) && !(value instanceof RichImageData) && !(value instanceof AttachedFile) && !isRecordStringString(value)) {
1297
1310
  throw new TypeError(
1298
- "Each value in tags.raw must be a string, Uint8Array, RichImageData, AttachedFile, Record<string, string>, or null."
1311
+ "Each value in tags.raw must be a string, string array, Uint8Array, RichImageData, AttachedFile, Record<string, string>, or null."
1299
1312
  );
1300
1313
  }
1301
1314
  }
@@ -1381,6 +1394,16 @@ var Mediabunny = (() => {
1381
1394
  }
1382
1395
  this.pos = end;
1383
1396
  }
1397
+ copyBits(n, other) {
1398
+ let i = 0;
1399
+ for (i; i < n - 7; i += 8) {
1400
+ this.writeBits(8, other.readBits(8));
1401
+ }
1402
+ const leftover = n - i;
1403
+ if (leftover > 0) {
1404
+ this.writeBits(leftover, other.readBits(leftover));
1405
+ }
1406
+ }
1384
1407
  readAlignedByte() {
1385
1408
  if (this.pos % 8 !== 0) {
1386
1409
  throw new Error("Bitstream is not byte-aligned.");
@@ -1657,6 +1680,19 @@ var Mediabunny = (() => {
1657
1680
  }
1658
1681
  return new Uint8Array(result);
1659
1682
  };
1683
+ var addEmulationPreventionBytes = (data) => {
1684
+ const result = [];
1685
+ let zeroCount = 0;
1686
+ for (const byte of data) {
1687
+ if (zeroCount === 2 && byte <= 3) {
1688
+ result.push(3);
1689
+ zeroCount = 0;
1690
+ }
1691
+ result.push(byte);
1692
+ zeroCount = byte === 0 ? zeroCount + 1 : 0;
1693
+ }
1694
+ return new Uint8Array(result);
1695
+ };
1660
1696
  var ANNEX_B_START_CODE = new Uint8Array([0, 0, 0, 1]);
1661
1697
  var concatNalUnitsInAnnexB = (nalUnits) => {
1662
1698
  const totalLength = nalUnits.reduce((a, b) => a + ANNEX_B_START_CODE.byteLength + b.byteLength, 0);
@@ -1776,8 +1812,7 @@ var Mediabunny = (() => {
1776
1812
  bytes2.push(pps[i]);
1777
1813
  }
1778
1814
  }
1779
- if (record.avcProfileIndication === 100 || record.avcProfileIndication === 110 || record.avcProfileIndication === 122 || record.avcProfileIndication === 144) {
1780
- assert(record.chromaFormat !== null);
1815
+ if ((record.avcProfileIndication === 100 || record.avcProfileIndication === 110 || record.avcProfileIndication === 122 || record.avcProfileIndication === 144) && record.chromaFormat !== null) {
1781
1816
  assert(record.bitDepthLumaMinus8 !== null);
1782
1817
  assert(record.bitDepthChromaMinus8 !== null);
1783
1818
  assert(record.sequenceParameterSetExt !== null);
@@ -1877,7 +1912,8 @@ var Mediabunny = (() => {
1877
1912
  };
1878
1913
  var parseAvcSps = (sps) => {
1879
1914
  try {
1880
- const bitstream = new Bitstream(removeEmulationPreventionBytes(sps));
1915
+ const emulationUnpreventedBytes = removeEmulationPreventionBytes(sps);
1916
+ const bitstream = new Bitstream(emulationUnpreventedBytes);
1881
1917
  bitstream.skipBits(1);
1882
1918
  bitstream.skipBits(2);
1883
1919
  const nalUnitType = bitstream.readBits(5);
@@ -1973,6 +2009,9 @@ var Mediabunny = (() => {
1973
2009
  let pixelAspectRatio = { num: 1, den: 1 };
1974
2010
  let numReorderFrames = null;
1975
2011
  let maxDecFrameBuffering = null;
2012
+ let bitstreamRestrictionFlagBitOffset = null;
2013
+ let bitstreamRestrictionFlag = null;
2014
+ const vuiParametersFlagBitOffset = bitstream.pos;
1976
2015
  const vuiParametersPresentFlag = bitstream.readBits(1);
1977
2016
  if (vuiParametersPresentFlag) {
1978
2017
  const aspectRatioInfoPresentFlag = bitstream.readBits(1);
@@ -2028,7 +2067,8 @@ var Mediabunny = (() => {
2028
2067
  bitstream.skipBits(1);
2029
2068
  }
2030
2069
  bitstream.skipBits(1);
2031
- const bitstreamRestrictionFlag = bitstream.readBits(1);
2070
+ bitstreamRestrictionFlagBitOffset = bitstream.pos;
2071
+ bitstreamRestrictionFlag = bitstream.readBits(1);
2032
2072
  if (bitstreamRestrictionFlag) {
2033
2073
  bitstream.skipBits(1);
2034
2074
  readExpGolomb(bitstream);
@@ -2062,6 +2102,7 @@ var Mediabunny = (() => {
2062
2102
  }
2063
2103
  assert(maxDecFrameBuffering !== null);
2064
2104
  return {
2105
+ emulationUnpreventedBytes,
2065
2106
  profileIdc,
2066
2107
  constraintFlags,
2067
2108
  levelIdc,
@@ -2079,7 +2120,10 @@ var Mediabunny = (() => {
2079
2120
  transferCharacteristics,
2080
2121
  fullRangeFlag,
2081
2122
  numReorderFrames,
2082
- maxDecFrameBuffering
2123
+ maxDecFrameBuffering,
2124
+ vuiParametersFlagBitOffset,
2125
+ bitstreamRestrictionFlagBitOffset,
2126
+ bitstreamRestrictionFlag
2083
2127
  };
2084
2128
  } catch (error) {
2085
2129
  Logging._error("Error parsing AVC SPS:", error);
@@ -2100,6 +2144,40 @@ var Mediabunny = (() => {
2100
2144
  bitstream.skipBits(5);
2101
2145
  bitstream.skipBits(5);
2102
2146
  };
2147
+ var addAvcBitstreamRestriction = (sps) => {
2148
+ assert(sps.bitstreamRestrictionFlag !== 1);
2149
+ const modifiedBytes = new Uint8Array(sps.emulationUnpreventedBytes.byteLength + 64);
2150
+ const oldBitstream = new Bitstream(sps.emulationUnpreventedBytes);
2151
+ const newBitstream = new Bitstream(modifiedBytes);
2152
+ if (sps.bitstreamRestrictionFlag === null) {
2153
+ newBitstream.copyBits(sps.vuiParametersFlagBitOffset, oldBitstream);
2154
+ newBitstream.writeBits(1, 1);
2155
+ newBitstream.writeBits(1, 0);
2156
+ newBitstream.writeBits(1, 0);
2157
+ newBitstream.writeBits(1, 0);
2158
+ newBitstream.writeBits(1, 0);
2159
+ newBitstream.writeBits(1, 0);
2160
+ newBitstream.writeBits(1, 0);
2161
+ newBitstream.writeBits(1, 0);
2162
+ newBitstream.writeBits(1, 0);
2163
+ } else {
2164
+ assert(sps.bitstreamRestrictionFlagBitOffset !== null);
2165
+ newBitstream.copyBits(sps.bitstreamRestrictionFlagBitOffset, oldBitstream);
2166
+ }
2167
+ newBitstream.writeBits(1, 1);
2168
+ newBitstream.writeBits(1, 1);
2169
+ writeExpGolomb(newBitstream, 2);
2170
+ writeExpGolomb(newBitstream, 1);
2171
+ writeExpGolomb(newBitstream, 16);
2172
+ writeExpGolomb(newBitstream, 16);
2173
+ writeExpGolomb(newBitstream, sps.numReorderFrames);
2174
+ writeExpGolomb(newBitstream, sps.maxDecFrameBuffering);
2175
+ newBitstream.writeBits(1, 1);
2176
+ newBitstream.writeBits((8 - newBitstream.pos % 8) % 8, 0);
2177
+ const byteLength = newBitstream.pos / 8;
2178
+ assert(Number.isInteger(byteLength));
2179
+ return addEmulationPreventionBytes(modifiedBytes.subarray(0, byteLength));
2180
+ };
2103
2181
  var concatHevcNalUnits = (nalUnits, decoderConfig) => {
2104
2182
  if (decoderConfig.description) {
2105
2183
  const bytes2 = toUint8Array(decoderConfig.description);
@@ -3416,7 +3494,13 @@ var Mediabunny = (() => {
3416
3494
  const key = string.slice(0, separatorIndex).toUpperCase();
3417
3495
  const value = string.slice(separatorIndex + 1);
3418
3496
  metadataTags.raw ??= {};
3419
- metadataTags.raw[key] ??= value;
3497
+ if (Array.isArray(metadataTags.raw[key])) {
3498
+ metadataTags.raw[key] = [...metadataTags.raw[key], value];
3499
+ } else if (typeof metadataTags.raw[key] === "string") {
3500
+ metadataTags.raw[key] = [metadataTags.raw[key], value];
3501
+ } else {
3502
+ metadataTags.raw[key] ??= value;
3503
+ }
3420
3504
  switch (key) {
3421
3505
  case "TITLE":
3422
3506
  {
@@ -3563,7 +3647,7 @@ var Mediabunny = (() => {
3563
3647
  currentView.setUint32(0, encodedVendorString.length, true);
3564
3648
  currentBuffer.set(encodedVendorString, 4);
3565
3649
  commentHeaderParts.push(currentBuffer);
3566
- const writtenTags = /* @__PURE__ */ new Set();
3650
+ const writtenTags = [];
3567
3651
  const addCommentTag = (key, value) => {
3568
3652
  const joined = `${key}=${value}`;
3569
3653
  const encoded = textEncoder.encode(joined);
@@ -3572,7 +3656,7 @@ var Mediabunny = (() => {
3572
3656
  currentView.setUint32(0, encoded.length, true);
3573
3657
  currentBuffer.set(encoded, 4);
3574
3658
  commentHeaderParts.push(currentBuffer);
3575
- writtenTags.add(key);
3659
+ writtenTags.push(key);
3576
3660
  };
3577
3661
  for (const { key, value } of keyValueIterator(tags)) {
3578
3662
  switch (key) {
@@ -3614,12 +3698,7 @@ var Mediabunny = (() => {
3614
3698
  break;
3615
3699
  case "date":
3616
3700
  {
3617
- const rawVersion = tags.raw?.["DATE"] ?? tags.raw?.["date"];
3618
- if (rawVersion && typeof rawVersion === "string") {
3619
- addCommentTag("DATE", rawVersion);
3620
- } else {
3621
- addCommentTag("DATE", value.toISOString().slice(0, 10));
3622
- }
3701
+ addCommentTag("DATE", value.toISOString().slice(0, 10));
3623
3702
  }
3624
3703
  ;
3625
3704
  break;
@@ -3708,16 +3787,23 @@ var Mediabunny = (() => {
3708
3787
  if (tags.raw) {
3709
3788
  for (const key in tags.raw) {
3710
3789
  const value = tags.raw[key] ?? tags.raw[key.toLowerCase()];
3711
- if (key === "vendor" || value == null || writtenTags.has(key)) {
3790
+ if (key === "vendor" || value == null || writtenTags.includes(key)) {
3712
3791
  continue;
3713
3792
  }
3714
3793
  if (typeof value === "string") {
3715
3794
  addCommentTag(key, value);
3795
+ } else if (Array.isArray(value)) {
3796
+ const isOnlyStrings = value.every((x) => typeof x === "string");
3797
+ if (isOnlyStrings) {
3798
+ for (const elem of value) {
3799
+ addCommentTag(key, elem);
3800
+ }
3801
+ }
3716
3802
  }
3717
3803
  }
3718
3804
  }
3719
3805
  const listLengthBuffer = new Uint8Array(4);
3720
- toDataView(listLengthBuffer).setUint32(0, writtenTags.size, true);
3806
+ toDataView(listLengthBuffer).setUint32(0, writtenTags.length, true);
3721
3807
  commentHeaderParts.splice(2, 0, listLengthBuffer);
3722
3808
  const commentHeaderLength = commentHeaderParts.reduce((a, b) => a + b.length, 0);
3723
3809
  const commentHeader = new Uint8Array(commentHeaderLength);
@@ -6657,9 +6743,6 @@ var Mediabunny = (() => {
6657
6743
  const segmentDuration = version === 1 ? readU64Be(slice) : readU32Be(slice);
6658
6744
  const mediaTime = version === 1 ? readI64Be(slice) : readI32Be(slice);
6659
6745
  const mediaRate = readFixed_16_16(slice);
6660
- if (segmentDuration === 0) {
6661
- continue;
6662
- }
6663
6746
  if (relevantEntryFound) {
6664
6747
  Logging._warn(
6665
6748
  "Unsupported edit list: multiple edits are not currently supported. Only using first edit."
@@ -11321,6 +11404,22 @@ var Mediabunny = (() => {
11321
11404
  }
11322
11405
  }
11323
11406
  }
11407
+ async getDurationFromMetadata(segment) {
11408
+ if (segment.duration <= 0) {
11409
+ return null;
11410
+ }
11411
+ let minTimestamp = null;
11412
+ for (const track of segment.tracks) {
11413
+ assert(track.trackBacking);
11414
+ const firstPacket = await track.trackBacking.getFirstPacket({ metadataOnly: true });
11415
+ if (firstPacket) {
11416
+ minTimestamp = Math.min(minTimestamp ?? Infinity, firstPacket.timestamp);
11417
+ }
11418
+ }
11419
+ let endTimestamp = segment.duration / segment.timestampFactor;
11420
+ endTimestamp += minTimestamp ?? 0;
11421
+ return endTimestamp;
11422
+ }
11324
11423
  };
11325
11424
  var MatroskaTrackBacking = class {
11326
11425
  constructor(internalTrack) {
@@ -11380,14 +11479,7 @@ var Mediabunny = (() => {
11380
11479
  return null;
11381
11480
  }
11382
11481
  async getDurationFromMetadata() {
11383
- const segment = this.internalTrack.segment;
11384
- if (segment.duration <= 0) {
11385
- return null;
11386
- }
11387
- let endTimestamp = segment.duration / segment.timestampFactor;
11388
- const firstPacket = await this.getFirstPacket({ metadataOnly: true });
11389
- endTimestamp += firstPacket?.timestamp ?? 0;
11390
- return endTimestamp;
11482
+ return this.internalTrack.demuxer.getDurationFromMetadata(this.internalTrack.segment);
11391
11483
  }
11392
11484
  async getLiveRefreshInterval() {
11393
11485
  return null;
@@ -14474,6 +14566,7 @@ var Mediabunny = (() => {
14474
14566
 
14475
14567
  // src/mpeg-ts/mpeg-ts-misc.ts
14476
14568
  var TIMESCALE = 9e4;
14569
+ var TIMESTAMP_MODULUS = 2 ** 33;
14477
14570
  var TS_PACKET_SIZE = 188;
14478
14571
  var buildMpegTsMimeType = (codecStrings) => {
14479
14572
  let string = "video/MP2T";
@@ -14508,6 +14601,7 @@ var Mediabunny = (() => {
14508
14601
  this.seekChunkSize = 5 * 1024 * 1024;
14509
14602
  // 5 MiB, picked because most HLS segments are below this size
14510
14603
  this.minReferencePointByteDistance = -1;
14604
+ this.timestampWrapInfo = null;
14511
14605
  this.reader = input._reader;
14512
14606
  }
14513
14607
  async readMetadata() {
@@ -14765,7 +14859,7 @@ var Mediabunny = (() => {
14765
14859
  const elementaryStream = this.elementaryStreams.find((x) => x.pid === section.pid);
14766
14860
  outer:
14767
14861
  if (elementaryStream && !elementaryStream.initialized) {
14768
- const pesPacket = readPesPacket(section, true);
14862
+ const pesPacket = readPesPacket(this, section, true);
14769
14863
  if (!pesPacket) {
14770
14864
  throw new Error(
14771
14865
  `Couldn't read first PES packet for Elementary Stream with PID ${elementaryStream.pid}`
@@ -15160,8 +15254,22 @@ var Mediabunny = (() => {
15160
15254
  body: bytes2.subarray(4)
15161
15255
  };
15162
15256
  }
15257
+ normalizeTimestamp(timestamp) {
15258
+ if (!this.timestampWrapInfo) {
15259
+ const tolerance = 60 * TIMESCALE;
15260
+ this.timestampWrapInfo = {
15261
+ reference: timestamp - tolerance,
15262
+ offset: timestamp >= TIMESTAMP_MODULUS - tolerance ? -TIMESTAMP_MODULUS : TIMESTAMP_MODULUS
15263
+ };
15264
+ }
15265
+ const { reference, offset } = this.timestampWrapInfo;
15266
+ if (offset < 0 && timestamp >= reference || offset > 0 && timestamp < reference) {
15267
+ return timestamp + offset;
15268
+ }
15269
+ return timestamp;
15270
+ }
15163
15271
  };
15164
- var readPesPacketHeader = (section, expectPts) => {
15272
+ var readPesPacketHeader = (demuxer, section, expectPts) => {
15165
15273
  if (section.payload.byteLength < 3) {
15166
15274
  return null;
15167
15275
  }
@@ -15187,6 +15295,7 @@ var Mediabunny = (() => {
15187
15295
  pts += bitstream.readBits(15) * (1 << 15);
15188
15296
  bitstream.skipBits(1);
15189
15297
  pts += bitstream.readBits(15);
15298
+ pts = demuxer.normalizeTimestamp(pts);
15190
15299
  } else {
15191
15300
  if (expectPts) {
15192
15301
  throw new Error(MISSING_PTS_ERROR_MESSAGE);
@@ -15199,9 +15308,9 @@ var Mediabunny = (() => {
15199
15308
  randomAccessIndicator: section.randomAccessIndicator
15200
15309
  };
15201
15310
  };
15202
- var readPesPacket = (section, expectPts) => {
15311
+ var readPesPacket = (demuxer, section, expectPts) => {
15203
15312
  assert(section.endPos !== null);
15204
- const header = readPesPacketHeader(section, expectPts);
15313
+ const header = readPesPacketHeader(demuxer, section, expectPts);
15205
15314
  if (!header) {
15206
15315
  return null;
15207
15316
  }
@@ -15313,7 +15422,7 @@ var Mediabunny = (() => {
15313
15422
  async getFirstPacket(options) {
15314
15423
  const section = this.elementaryStream.firstSection;
15315
15424
  assert(section);
15316
- const pesPacket = readPesPacket(section, true);
15425
+ const pesPacket = readPesPacket(this.elementaryStream.demuxer, section, true);
15317
15426
  assert(pesPacket);
15318
15427
  const context = new PacketReadingContext(this.elementaryStream, pesPacket);
15319
15428
  const buffer = new PacketBuffer(this, context);
@@ -15346,7 +15455,7 @@ var Mediabunny = (() => {
15346
15455
  const demuxer = this.elementaryStream.demuxer;
15347
15456
  const section = await demuxer.readSection(sectionStartPos, true);
15348
15457
  assert(section);
15349
- const pesPacket = readPesPacket(section, true);
15458
+ const pesPacket = readPesPacket(demuxer, section, true);
15350
15459
  assert(pesPacket);
15351
15460
  const context = new PacketReadingContext(this.elementaryStream, pesPacket);
15352
15461
  buffer = new PacketBuffer(this, context);
@@ -15404,7 +15513,7 @@ var Mediabunny = (() => {
15404
15513
  if (!section) {
15405
15514
  return null;
15406
15515
  }
15407
- const pesPacketHeader = readPesPacketHeader(section, false);
15516
+ const pesPacketHeader = readPesPacketHeader(demuxer, section, false);
15408
15517
  if (pesPacketHeader && pesPacketHeader.pts !== null) {
15409
15518
  return {
15410
15519
  pesPacketHeader,
@@ -15418,7 +15527,7 @@ var Mediabunny = (() => {
15418
15527
  };
15419
15528
  const firstSection = this.elementaryStream.firstSection;
15420
15529
  assert(firstSection);
15421
- const firstPesPacketHeader = readPesPacketHeader(firstSection, true);
15530
+ const firstPesPacketHeader = readPesPacketHeader(demuxer, firstSection, true);
15422
15531
  assert(firstPesPacketHeader);
15423
15532
  if (searchPts < firstPesPacketHeader.pts) {
15424
15533
  return null;
@@ -15473,7 +15582,7 @@ var Mediabunny = (() => {
15473
15582
  const retrieveEncodedPacket = async (sectionStartPos, predicate) => {
15474
15583
  const section = await demuxer.readSection(sectionStartPos, true);
15475
15584
  assert(section);
15476
- const pesPacket = readPesPacket(section, true);
15585
+ const pesPacket = readPesPacket(demuxer, section, true);
15477
15586
  assert(pesPacket);
15478
15587
  const context = new PacketReadingContext(this.elementaryStream, pesPacket);
15479
15588
  const buffer = new PacketBuffer(this, context);
@@ -15516,7 +15625,7 @@ var Mediabunny = (() => {
15516
15625
  if (packetHeader.pid === pid && packetHeader.payloadUnitStartIndicator === 1) {
15517
15626
  const section = await demuxer.readSection(currentPos, false);
15518
15627
  if (section) {
15519
- const nextPesHeader = readPesPacketHeader(section, false);
15628
+ const nextPesHeader = readPesPacketHeader(demuxer, section, false);
15520
15629
  if (nextPesHeader && nextPesHeader.pts !== null) {
15521
15630
  if (nextPesHeader.pts > searchPts) {
15522
15631
  break outer;
@@ -15541,7 +15650,7 @@ var Mediabunny = (() => {
15541
15650
  if (packetHeader.pid === pid && packetHeader.payloadUnitStartIndicator === 1) {
15542
15651
  const section = await demuxer.readSection(pos, false);
15543
15652
  if (section) {
15544
- const header = readPesPacketHeader(section, false);
15653
+ const header = readPesPacketHeader(demuxer, section, false);
15545
15654
  if (header && header.pts !== null) {
15546
15655
  currentPesHeader = header;
15547
15656
  break;
@@ -15586,7 +15695,7 @@ var Mediabunny = (() => {
15586
15695
  isKeyPacket = pesHeader.randomAccessIndicator === 1;
15587
15696
  } else {
15588
15697
  assert(pesHeaderSection);
15589
- const pesPacket = readPesPacket(pesHeaderSection, true);
15698
+ const pesPacket = readPesPacket(demuxer, pesHeaderSection, true);
15590
15699
  assert(pesPacket);
15591
15700
  const context = new PacketReadingContext(this.elementaryStream, pesPacket);
15592
15701
  await context.markNextPacket();
@@ -15614,7 +15723,7 @@ var Mediabunny = (() => {
15614
15723
  if (packetHeader.pid === pid && packetHeader.payloadUnitStartIndicator === 1) {
15615
15724
  const section = await demuxer.readSection(currentPos, readSectionsInFull);
15616
15725
  if (section) {
15617
- const nextPesHeader = readPesPacketHeader(section, false);
15726
+ const nextPesHeader = readPesPacketHeader(demuxer, section, false);
15618
15727
  if (nextPesHeader && nextPesHeader.pts !== null) {
15619
15728
  pesHeader = nextPesHeader;
15620
15729
  pesHeaderSection = section;
@@ -15640,7 +15749,7 @@ var Mediabunny = (() => {
15640
15749
  if (packetHeader.pid === pid && packetHeader.payloadUnitStartIndicator === 1) {
15641
15750
  const section = await demuxer.readSection(pos, readSectionsInFull);
15642
15751
  if (section) {
15643
- const header = readPesPacketHeader(section, false);
15752
+ const header = readPesPacketHeader(demuxer, section, false);
15644
15753
  if (header && header.pts !== null) {
15645
15754
  startPesHeader = header;
15646
15755
  break;
@@ -15812,7 +15921,7 @@ var Mediabunny = (() => {
15812
15921
  if (!nextSection) {
15813
15922
  return;
15814
15923
  }
15815
- const nextPesPacket = readPesPacket(nextSection, false);
15924
+ const nextPesPacket = readPesPacket(this.demuxer, nextSection, false);
15816
15925
  if (nextPesPacket) {
15817
15926
  pesPacket = nextPesPacket;
15818
15927
  break;
@@ -17084,6 +17193,9 @@ var Mediabunny = (() => {
17084
17193
  if (options.useStreamReader !== void 0 && typeof options.useStreamReader !== "boolean") {
17085
17194
  throw new TypeError("options.useStreamReader, when provided, must be a boolean.");
17086
17195
  }
17196
+ if (options.handleUnhandledError !== void 0 && typeof options.handleUnhandledError !== "function") {
17197
+ throw new TypeError("options.handleUnhandledError, when provided, must be a function.");
17198
+ }
17087
17199
  super();
17088
17200
  /** @internal */
17089
17201
  this._readers = /* @__PURE__ */ new WeakMap();
@@ -17102,7 +17214,8 @@ var Mediabunny = (() => {
17102
17214
  });
17103
17215
  }
17104
17216
  },
17105
- prefetchProfile: PREFETCH_PROFILES.fileSystem
17217
+ prefetchProfile: PREFETCH_PROFILES.fileSystem,
17218
+ handleUnhandledError: options.handleUnhandledError
17106
17219
  });
17107
17220
  this._orchestrator.fileSize = blob.size;
17108
17221
  }
@@ -17210,6 +17323,9 @@ var Mediabunny = (() => {
17210
17323
  if (options.fetchFn !== void 0 && typeof options.fetchFn !== "function") {
17211
17324
  throw new TypeError("options.fetchFn, when provided, must be a function.");
17212
17325
  }
17326
+ if (options.handleUnhandledError !== void 0 && typeof options.handleUnhandledError !== "function") {
17327
+ throw new TypeError("options.handleUnhandledError, when provided, must be a function.");
17328
+ }
17213
17329
  const urlString = url2 instanceof Request ? url2.url : url2 instanceof URL ? url2.href : url2;
17214
17330
  super(
17215
17331
  urlString,
@@ -17266,7 +17382,8 @@ var Mediabunny = (() => {
17266
17382
  maxCacheSize: options.maxCacheSize ?? 64 * 2 ** 20,
17267
17383
  maxWorkerCount: options.parallelism ?? DEFAULT_PARALLELISM,
17268
17384
  runWorker: this._runWorker.bind(this),
17269
- prefetchProfile: PREFETCH_PROFILES.network
17385
+ prefetchProfile: PREFETCH_PROFILES.network,
17386
+ handleUnhandledError: options.handleUnhandledError
17270
17387
  });
17271
17388
  }
17272
17389
  /** @internal */
@@ -17329,7 +17446,7 @@ var Mediabunny = (() => {
17329
17446
  this.rootPath = response.url;
17330
17447
  }
17331
17448
  outer:
17332
- if (this._orchestrator.fileSize === null) {
17449
+ if (this._orchestrator.fileSize === null && (response.status === 206 || response.type === "basic" && !response.headers.has("Content-Encoding"))) {
17333
17450
  const contentRange = response.headers.get("Content-Range");
17334
17451
  if (contentRange) {
17335
17452
  const match = /\/(\d+)/.exec(contentRange);
@@ -17485,7 +17602,8 @@ var Mediabunny = (() => {
17485
17602
  cancel: () => currentReader.cancel()
17486
17603
  });
17487
17604
  const backing = new ReadableStreamSource(wrappedStream, {
17488
- maxCacheSize: this._orchestrator.options.maxCacheSize
17605
+ maxCacheSize: this._orchestrator.options.maxCacheSize,
17606
+ handleUnhandledError: this._options.handleUnhandledError
17489
17607
  });
17490
17608
  backing._endIndex = this._orchestrator.fileSize;
17491
17609
  backing._cacheMissErrorMessage = "Attempted to read data from an already-evicted part of the cache. Because the HTTP server did not honor the range request, data can only be read sequentially, with old data being evicted from the cache. To fix this issue, either ensure your server responds to range requests with 206 Partial Content, or set maxCacheSize to Infinity in the UrlSource options. Note that the latter will store the entire file in the cache if needed, no matter how large.";
@@ -17585,7 +17703,8 @@ var Mediabunny = (() => {
17585
17703
  return buffer;
17586
17704
  },
17587
17705
  maxCacheSize: options.maxCacheSize,
17588
- prefetchProfile: "fileSystem"
17706
+ prefetchProfile: "fileSystem",
17707
+ handleUnhandledError: options.handleUnhandledError
17589
17708
  });
17590
17709
  }
17591
17710
  /** @internal */
@@ -17621,6 +17740,9 @@ var Mediabunny = (() => {
17621
17740
  if (options.dispose !== void 0 && typeof options.dispose !== "function") {
17622
17741
  throw new TypeError("options.dispose, when provided, must be a function.");
17623
17742
  }
17743
+ if (options.handleUnhandledError !== void 0 && typeof options.handleUnhandledError !== "function") {
17744
+ throw new TypeError("options.handleUnhandledError, when provided, must be a function.");
17745
+ }
17624
17746
  if (options.maxCacheSize !== void 0 && (!isNumber(options.maxCacheSize) || options.maxCacheSize < 0)) {
17625
17747
  throw new TypeError("options.maxCacheSize, when provided, must be a non-negative number.");
17626
17748
  }
@@ -17636,7 +17758,8 @@ var Mediabunny = (() => {
17636
17758
  maxWorkerCount: 2,
17637
17759
  // Fixed for now, *should* be fine
17638
17760
  prefetchProfile: PREFETCH_PROFILES[options.prefetchProfile ?? "none"],
17639
- runWorker: this._runWorker.bind(this)
17761
+ runWorker: this._runWorker.bind(this),
17762
+ handleUnhandledError: options.handleUnhandledError
17640
17763
  });
17641
17764
  }
17642
17765
  /** @internal */
@@ -17728,6 +17851,9 @@ var Mediabunny = (() => {
17728
17851
  if (!options || typeof options !== "object") {
17729
17852
  throw new TypeError("options must be an object.");
17730
17853
  }
17854
+ if (options.handleUnhandledError !== void 0 && typeof options.handleUnhandledError !== "function") {
17855
+ throw new TypeError("options.handleUnhandledError, when provided, must be a function.");
17856
+ }
17731
17857
  if (options.maxCacheSize !== void 0 && (!isNumber(options.maxCacheSize) || options.maxCacheSize < 0)) {
17732
17858
  throw new TypeError("options.maxCacheSize, when provided, must be a non-negative number.");
17733
17859
  }
@@ -17755,6 +17881,7 @@ var Mediabunny = (() => {
17755
17881
  this._cacheMissErrorMessage = "Attempted to read data from an already-evicted part of the cache. With ReadableStreamSource, you must access the data more sequentially or increase the size of its cache.";
17756
17882
  this._stream = stream;
17757
17883
  this._maxCacheSize = options.maxCacheSize ?? 32 * 2 ** 20;
17884
+ this._handleUnhandledError = options.handleUnhandledError;
17758
17885
  }
17759
17886
  /** @internal */
17760
17887
  _getFileSize() {
@@ -17823,6 +17950,8 @@ var Mediabunny = (() => {
17823
17950
  if (this._pendingSlices.length > 0) {
17824
17951
  this._pendingSlices.forEach((x) => x.reject(error));
17825
17952
  this._pendingSlices.length = 0;
17953
+ } else if (this._handleUnhandledError) {
17954
+ this._handleUnhandledError(error);
17826
17955
  } else {
17827
17956
  throw error;
17828
17957
  }
@@ -18106,7 +18235,11 @@ var Mediabunny = (() => {
18106
18235
  if (this.disposed) {
18107
18236
  return;
18108
18237
  }
18109
- throw error;
18238
+ if (this.options.handleUnhandledError) {
18239
+ this.options.handleUnhandledError(error);
18240
+ } else {
18241
+ throw error;
18242
+ }
18110
18243
  });
18111
18244
  }
18112
18245
  return result;
@@ -18194,7 +18327,11 @@ var Mediabunny = (() => {
18194
18327
  worker.pendingSlices.forEach((x) => x.reject(error));
18195
18328
  worker.pendingSlices.length = 0;
18196
18329
  } else if (!worker.aborted && !this.disposed) {
18197
- throw error;
18330
+ if (this.options.handleUnhandledError) {
18331
+ this.options.handleUnhandledError(error);
18332
+ } else {
18333
+ throw error;
18334
+ }
18198
18335
  }
18199
18336
  }).finally(() => {
18200
18337
  if (worker.running) {
@@ -22098,30 +22235,30 @@ var Mediabunny = (() => {
22098
22235
  }
22099
22236
  }
22100
22237
  /**
22101
- * Returns a new {@link AudioSample} containing only the frames in the range [startSample, endSample). Both bounds
22238
+ * Returns a new {@link AudioSample} containing only the frames in the range [startFrame, endFrame). Both bounds
22102
22239
  * must lie within this sample's range of frames. The returned sample's timestamp is shifted to match the start of
22103
22240
  * the trimmed section.
22104
22241
  */
22105
- trim(startSample, endSample = this.numberOfFrames) {
22106
- if (!Number.isInteger(startSample) || startSample < 0) {
22107
- throw new TypeError("startSample must be a non-negative integer.");
22242
+ trim(startFrame, endFrame = this.numberOfFrames) {
22243
+ if (!Number.isInteger(startFrame) || startFrame < 0) {
22244
+ throw new TypeError("startFrame must be a non-negative integer.");
22108
22245
  }
22109
- if (!Number.isInteger(endSample) || endSample < 0) {
22110
- throw new TypeError("endSample must be a non-negative integer.");
22246
+ if (!Number.isInteger(endFrame) || endFrame < 0) {
22247
+ throw new TypeError("endFrame must be a non-negative integer.");
22111
22248
  }
22112
- if (startSample > this.numberOfFrames) {
22113
- throw new RangeError("startSample out of range.");
22249
+ if (startFrame > this.numberOfFrames) {
22250
+ throw new RangeError("startFrame out of range.");
22114
22251
  }
22115
- if (endSample > this.numberOfFrames) {
22116
- throw new RangeError("endSample out of range.");
22252
+ if (endFrame > this.numberOfFrames) {
22253
+ throw new RangeError("endFrame out of range.");
22117
22254
  }
22118
- if (endSample < startSample) {
22119
- throw new RangeError("endSample must not be less than startSample.");
22255
+ if (endFrame < startFrame) {
22256
+ throw new RangeError("endFrame must not be less than startFrame.");
22120
22257
  }
22121
22258
  if (this._closed) {
22122
22259
  throw new Error("AudioSample is closed.");
22123
22260
  }
22124
- const frameCount = endSample - startSample;
22261
+ const frameCount = endFrame - startFrame;
22125
22262
  const bytesPerSample = getBytesPerSample(this.format);
22126
22263
  let data;
22127
22264
  if (formatIsPlanar(this.format)) {
@@ -22132,7 +22269,7 @@ var Mediabunny = (() => {
22132
22269
  this.copyTo(data.subarray(i * planeSize, (i + 1) * planeSize), {
22133
22270
  planeIndex: i,
22134
22271
  format: this.format,
22135
- frameOffset: startSample,
22272
+ frameOffset: startFrame,
22136
22273
  frameCount
22137
22274
  });
22138
22275
  }
@@ -22143,7 +22280,7 @@ var Mediabunny = (() => {
22143
22280
  this.copyTo(data, {
22144
22281
  planeIndex: 0,
22145
22282
  format: this.format,
22146
- frameOffset: startSample,
22283
+ frameOffset: startFrame,
22147
22284
  frameCount
22148
22285
  });
22149
22286
  }
@@ -22153,7 +22290,7 @@ var Mediabunny = (() => {
22153
22290
  format: this.format,
22154
22291
  sampleRate: this.sampleRate,
22155
22292
  numberOfChannels: this.numberOfChannels,
22156
- timestamp: this.timestamp + startSample / this.sampleRate
22293
+ timestamp: this.timestamp + startFrame / this.sampleRate
22157
22294
  });
22158
22295
  }
22159
22296
  /**
@@ -24013,11 +24150,20 @@ var Mediabunny = (() => {
24013
24150
  );
24014
24151
  if (record && record.sequenceParameterSets.length > 0) {
24015
24152
  const sps = parseAvcSps(record.sequenceParameterSets[0]);
24016
- if (sps && sps.frameMbsOnlyFlag === 0) {
24017
- this.decoderConfig = {
24018
- ...this.decoderConfig,
24019
- hardwareAcceleration: "prefer-software"
24020
- };
24153
+ if (sps) {
24154
+ if (sps.frameMbsOnlyFlag === 0) {
24155
+ this.decoderConfig = {
24156
+ ...this.decoderConfig,
24157
+ hardwareAcceleration: "prefer-software"
24158
+ };
24159
+ }
24160
+ if (sps.maxDecFrameBuffering !== 0 && sps.bitstreamRestrictionFlag !== 1) {
24161
+ record.sequenceParameterSets[0] = addAvcBitstreamRestriction(sps);
24162
+ this.decoderConfig = {
24163
+ ...this.decoderConfig,
24164
+ description: serializeAvcDecoderConfigurationRecord(record)
24165
+ };
24166
+ }
24021
24167
  }
24022
24168
  }
24023
24169
  }
@@ -24094,6 +24240,19 @@ var Mediabunny = (() => {
24094
24240
  filteredNalUnits.push(packet.data.subarray(loc.offset, loc.offset + loc.length));
24095
24241
  }
24096
24242
  }
24243
+ if (!this.decoderConfig.description) {
24244
+ for (let i = 0; i < filteredNalUnits.length; i++) {
24245
+ const nalUnit = filteredNalUnits[i];
24246
+ if (extractNalUnitTypeForAvc(nalUnit[0]) !== 7 /* SPS */) {
24247
+ continue;
24248
+ }
24249
+ const sps = parseAvcSps(nalUnit);
24250
+ if (sps && sps.maxDecFrameBuffering !== 0 && sps.bitstreamRestrictionFlag !== 1) {
24251
+ filteredNalUnits[i] = addAvcBitstreamRestriction(sps);
24252
+ }
24253
+ break;
24254
+ }
24255
+ }
24097
24256
  const newData = concatAvcNalUnits(filteredNalUnits, this.decoderConfig);
24098
24257
  packet = new EncodedPacket(newData, packet.type, packet.timestamp, packet.duration);
24099
24258
  } else if (this.codec === "hevc") {
@@ -27886,9 +28045,6 @@ var Mediabunny = (() => {
27886
28045
  onTrackClose(track) {
27887
28046
  }
27888
28047
  validateTimestamp(track, timestampInSeconds, isKeyPacket) {
27889
- if (timestampInSeconds < 0) {
27890
- throw new Error(`Timestamps must be non-negative (got ${timestampInSeconds}s).`);
27891
- }
27892
28048
  let timestampInfo = this.trackTimestampInfo.get(track);
27893
28049
  if (!timestampInfo) {
27894
28050
  if (!isKeyPacket) {
@@ -28564,7 +28720,8 @@ var Mediabunny = (() => {
28564
28720
  ascii("dash")
28565
28721
  ]);
28566
28722
  var sidx = (muxer, referencedSize) => {
28567
- let duration = muxer.maxWrittenEndTimestamp - muxer.minWrittenTimestamp;
28723
+ const earliestPresentationTime = Math.max(0, muxer.minWrittenTimestamp);
28724
+ let duration = Math.max(0, muxer.maxWrittenEndTimestamp - earliestPresentationTime);
28568
28725
  if (!Number.isFinite(duration)) {
28569
28726
  duration = 0;
28570
28727
  }
@@ -28573,7 +28730,7 @@ var Mediabunny = (() => {
28573
28730
  // Reference ID
28574
28731
  u32(GLOBAL_TIMESCALE),
28575
28732
  // Timescale
28576
- u64(intoTimescale(muxer.minWrittenTimestamp, GLOBAL_TIMESCALE)),
28733
+ u64(intoTimescale(earliestPresentationTime, GLOBAL_TIMESCALE)),
28577
28734
  // Earliest presentation time
28578
28735
  u64(0),
28579
28736
  // First offset
@@ -28602,7 +28759,13 @@ var Mediabunny = (() => {
28602
28759
  var mvhd = (creationTime, trackDatas) => {
28603
28760
  const duration = Math.max(
28604
28761
  0,
28605
- ...trackDatas.map((trackData) => intoTimescale(presentationSpan(trackData), GLOBAL_TIMESCALE) + intoTimescale(trackData.startTimestampOffset ?? 0, GLOBAL_TIMESCALE))
28762
+ ...trackDatas.map((trackData) => (
28763
+ // Round separately to match the edit list
28764
+ Math.max(
28765
+ 0,
28766
+ intoTimescale(presentationSpan(trackData), GLOBAL_TIMESCALE) + intoTimescale(trackData.startTimestampOffset ?? 0, GLOBAL_TIMESCALE)
28767
+ )
28768
+ ))
28606
28769
  );
28607
28770
  const nextTrackId = Math.max(0, ...trackDatas.map((x) => x.track.id)) + 1;
28608
28771
  const needsU64 = !isU32(creationTime) || !isU32(duration);
@@ -28652,10 +28815,10 @@ var Mediabunny = (() => {
28652
28815
  };
28653
28816
  var trak = (trackData, creationTime) => {
28654
28817
  const trackMetadata = getTrackMetadata(trackData);
28655
- const needsEditList = trackData.startTimestampOffset !== null && trackData.startTimestampOffset > 0;
28818
+ const needsEditList = trackData.startTimestampOffset !== null && trackData.startTimestampOffset !== 0;
28656
28819
  return box("trak", void 0, [
28657
28820
  tkhd(trackData, creationTime),
28658
- needsEditList ? edts(trackData, trackData.startTimestampOffset) : null,
28821
+ needsEditList ? edts(trackData) : null,
28659
28822
  mdia(trackData, creationTime),
28660
28823
  trackMetadata.name !== void 0 ? box("udta", void 0, [
28661
28824
  box("name", [
@@ -28666,7 +28829,10 @@ var Mediabunny = (() => {
28666
28829
  ]);
28667
28830
  };
28668
28831
  var tkhd = (trackData, creationTime) => {
28669
- const durationInGlobalTimescale = intoTimescale(presentationSpan(trackData), GLOBAL_TIMESCALE) + intoTimescale(trackData.startTimestampOffset ?? 0, GLOBAL_TIMESCALE);
28832
+ const durationInGlobalTimescale = Math.max(
28833
+ 0,
28834
+ intoTimescale(presentationSpan(trackData), GLOBAL_TIMESCALE) + intoTimescale(trackData.startTimestampOffset ?? 0, GLOBAL_TIMESCALE)
28835
+ );
28670
28836
  const needsU64 = !isU32(creationTime) || !isU32(durationInGlobalTimescale);
28671
28837
  const u32OrU64 = needsU64 ? u64 : u32;
28672
28838
  let matrix;
@@ -28710,32 +28876,58 @@ var Mediabunny = (() => {
28710
28876
  // Track height
28711
28877
  ]);
28712
28878
  };
28713
- var edts = (trackData, offset) => {
28714
- const startOffset = intoTimescale(offset, GLOBAL_TIMESCALE);
28715
- const mediaDuration = intoTimescale(presentationSpan(trackData), GLOBAL_TIMESCALE);
28716
- const needs64Bits = !isU32(startOffset) || !isU32(mediaDuration);
28717
- const u32OrU64 = needs64Bits ? u64 : u32;
28718
- const i32OrI64 = needs64Bits ? i64 : i32;
28719
- return box("edts", void 0, [
28720
- fullBox("elst", needs64Bits ? 1 : 0, 0, [
28721
- u32(2),
28722
- // Entry count
28723
- // #1
28724
- u32OrU64(startOffset),
28725
- // Segment duration
28726
- i32OrI64(-1),
28727
- // Media time
28728
- fixed_16_16(1),
28729
- // Media rate
28730
- // #2
28731
- u32OrU64(mediaDuration),
28732
- // Segment duration
28733
- i32OrI64(0),
28734
- // Media time
28735
- fixed_16_16(1)
28736
- // Media rate
28737
- ])
28738
- ]);
28879
+ var edts = (trackData) => {
28880
+ const offset = trackData.startTimestampOffset;
28881
+ assert(offset !== null);
28882
+ if (offset > 0) {
28883
+ const startOffset = intoTimescale(offset, GLOBAL_TIMESCALE);
28884
+ const mediaDuration = intoTimescale(presentationSpan(trackData), GLOBAL_TIMESCALE);
28885
+ const needs64Bits = !isU32(startOffset) || !isU32(mediaDuration);
28886
+ const u32OrU64 = needs64Bits ? u64 : u32;
28887
+ const i32OrI64 = needs64Bits ? i64 : i32;
28888
+ return box("edts", void 0, [
28889
+ fullBox("elst", needs64Bits ? 1 : 0, 0, [
28890
+ u32(2),
28891
+ // Entry count
28892
+ // #1
28893
+ u32OrU64(startOffset),
28894
+ // Segment duration
28895
+ i32OrI64(-1),
28896
+ // Media time
28897
+ fixed_16_16(1),
28898
+ // Media rate
28899
+ // #2
28900
+ u32OrU64(mediaDuration),
28901
+ // Segment duration
28902
+ i32OrI64(0),
28903
+ // Media time
28904
+ fixed_16_16(1)
28905
+ // Media rate
28906
+ ])
28907
+ ]);
28908
+ } else {
28909
+ const mediaTime = intoTimescale(-offset, trackData.timescale);
28910
+ const mediaDuration = Math.max(
28911
+ 0,
28912
+ intoTimescale(presentationSpan(trackData), GLOBAL_TIMESCALE) + intoTimescale(offset, GLOBAL_TIMESCALE)
28913
+ );
28914
+ const needs64Bits = !isI32(mediaTime) || !isU32(mediaDuration);
28915
+ const u32OrU64 = needs64Bits ? u64 : u32;
28916
+ const i32OrI64 = needs64Bits ? i64 : i32;
28917
+ return box("edts", void 0, [
28918
+ fullBox("elst", needs64Bits ? 1 : 0, 0, [
28919
+ u32(1),
28920
+ // Entry count
28921
+ // #1
28922
+ u32OrU64(mediaDuration),
28923
+ // Segment duration
28924
+ i32OrI64(mediaTime),
28925
+ // Media time
28926
+ fixed_16_16(1)
28927
+ // Media rate
28928
+ ])
28929
+ ]);
28930
+ }
28739
28931
  };
28740
28932
  var mdia = (trackData, creationTime) => box("mdia", void 0, [
28741
28933
  mdhd(trackData, creationTime),
@@ -31044,7 +31236,7 @@ var Mediabunny = (() => {
31044
31236
  currentChunk: null,
31045
31237
  compactlyCodedChunkTable: [],
31046
31238
  closed: false,
31047
- lastCueEndTimestamp: 0,
31239
+ lastCueEndTimestamp: null,
31048
31240
  cueQueue: [],
31049
31241
  nextSourceId: 0,
31050
31242
  cueToSourceId: /* @__PURE__ */ new WeakMap()
@@ -31176,6 +31368,7 @@ var Mediabunny = (() => {
31176
31368
  }
31177
31369
  async processWebVTTCues(trackData, until) {
31178
31370
  while (trackData.cueQueue.length > 0) {
31371
+ trackData.lastCueEndTimestamp ??= Math.min(0, trackData.cueQueue[0].timestamp);
31179
31372
  const timestamps = /* @__PURE__ */ new Set([]);
31180
31373
  for (const cue of trackData.cueQueue) {
31181
31374
  assert(cue.timestamp <= until);
@@ -31259,9 +31452,8 @@ var Mediabunny = (() => {
31259
31452
  return;
31260
31453
  }
31261
31454
  if (trackData.type === "audio" && trackData.info.requiresPcmTransformation) {
31262
- if (!this.isFragmented) {
31263
- trackData.startTimestampOffset ??= trackData.timestampProcessingQueue[0].timestamp;
31264
- }
31455
+ assert(!this.isFragmented);
31456
+ trackData.startTimestampOffset ??= trackData.timestampProcessingQueue[0].timestamp;
31265
31457
  let totalDuration = 0;
31266
31458
  for (let i = 0; i < trackData.timestampProcessingQueue.length; i++) {
31267
31459
  const sample = trackData.timestampProcessingQueue[i];
@@ -31281,7 +31473,9 @@ var Mediabunny = (() => {
31281
31473
  return;
31282
31474
  }
31283
31475
  const sortedTimestamps = trackData.timestampProcessingQueue.map((x) => x.timestamp).sort((a, b) => a - b);
31284
- if (!this.isFragmented) {
31476
+ if (this.isFragmented) {
31477
+ trackData.startTimestampOffset ??= Math.min(sortedTimestamps[0], 0);
31478
+ } else {
31285
31479
  trackData.startTimestampOffset ??= sortedTimestamps[0];
31286
31480
  }
31287
31481
  for (let i = 0; i < trackData.timestampProcessingQueue.length; i++) {
@@ -31532,11 +31726,16 @@ var Mediabunny = (() => {
31532
31726
  let fragmentStartTimestamp = Infinity;
31533
31727
  for (let i = 0; i < tracksInFragment.length; i++) {
31534
31728
  const trackData = tracksInFragment[i];
31729
+ assert(trackData.currentChunk);
31730
+ assert(trackData.startTimestampOffset !== null);
31535
31731
  trackData.currentChunk.offset = currentPos;
31536
31732
  trackData.currentChunk.moofOffset = moofOffset;
31537
31733
  trackData.currentChunk.trafIndex = i;
31734
+ trackData.currentChunk.startTimestamp -= trackData.startTimestampOffset;
31538
31735
  for (const sample of trackData.currentChunk.samples) {
31539
31736
  currentPos += sample.size;
31737
+ sample.timestamp -= trackData.startTimestampOffset;
31738
+ sample.decodeTimestamp -= trackData.startTimestampOffset;
31540
31739
  }
31541
31740
  fragmentStartTimestamp = Math.min(fragmentStartTimestamp, trackData.currentChunk.startTimestamp);
31542
31741
  }
@@ -31838,6 +32037,7 @@ var Mediabunny = (() => {
31838
32037
  this.trackDatasInCurrentCluster = /* @__PURE__ */ new Map();
31839
32038
  this.startTimestamp = Infinity;
31840
32039
  this.endTimestamp = -Infinity;
32040
+ this.warnedAboutTooNegativeTimestamp = false;
31841
32041
  this.format = format;
31842
32042
  }
31843
32043
  async start() {
@@ -32635,6 +32835,13 @@ ${cue.notes ?? ""}`;
32635
32835
  }
32636
32836
  const relativeTimestamp = msTimestamp - this.currentClusterStartMsTimestamp;
32637
32837
  if (relativeTimestamp < MIN_CLUSTER_TIMESTAMP_MS) {
32838
+ if (!this.warnedAboutTooNegativeTimestamp) {
32839
+ const formatName = this.format instanceof WebMOutputFormat ? "WebM" : "Matroska";
32840
+ Logging._warn(
32841
+ `Packets had to be discarded because their timestamp is too negative to represent in ${formatName}.`
32842
+ );
32843
+ this.warnedAboutTooNegativeTimestamp = true;
32844
+ }
32638
32845
  return;
32639
32846
  }
32640
32847
  const prelude = new Uint8Array(4);
@@ -32683,6 +32890,7 @@ ${cue.notes ?? ""}`;
32683
32890
  }
32684
32891
  /** Creates a new Cluster element to contain media chunks. */
32685
32892
  createNewCluster(msTimestamp) {
32893
+ msTimestamp = Math.max(0, msTimestamp);
32686
32894
  if (this.currentCluster) {
32687
32895
  this.finalizeCurrentCluster();
32688
32896
  }
@@ -32727,7 +32935,8 @@ ${cue.notes ?? ""}`;
32727
32935
  for (const [msTimestamp, trackDatas] of groupedAndSortedByTimestamp) {
32728
32936
  assert(this.cues);
32729
32937
  this.cues.data.push({ id: 187 /* CuePoint */, data: [
32730
- { id: 179 /* CueTime */, data: msTimestamp },
32938
+ { id: 179 /* CueTime */, data: Math.max(0, msTimestamp) },
32939
+ // CueTime is unsigned
32731
32940
  // Create CueTrackPositions for each track that starts at this timestamp
32732
32941
  ...trackDatas.map((trackData) => {
32733
32942
  return { id: 183 /* CueTrackPositions */, data: [
@@ -33786,24 +33995,26 @@ ${cue.notes ?? ""}`;
33786
33995
  pesView.setUint8(6, 132);
33787
33996
  pesView.setUint8(7, includeDts ? 192 : 128);
33788
33997
  pesView.setUint8(8, headerDataLength);
33789
- const pts = Math.round(queuedPacket.presentationTimestamp * TIMESCALE);
33998
+ const unwrappedPts = Math.round(queuedPacket.presentationTimestamp * TIMESCALE);
33999
+ const pts = modEuclid(unwrappedPts, TIMESTAMP_MODULUS);
33790
34000
  ptsDtsBitstream.pos = 0;
33791
34001
  ptsDtsBitstream.writeBits(4, includeDts ? 3 : 2);
33792
- ptsDtsBitstream.writeBits(3, pts >>> 30 & 7);
34002
+ ptsDtsBitstream.writeBits(3, Math.floor(pts / 2 ** 30));
33793
34003
  ptsDtsBitstream.writeBits(1, 1);
33794
- ptsDtsBitstream.writeBits(15, pts >>> 15 & 32767);
34004
+ ptsDtsBitstream.writeBits(15, Math.floor(pts / 2 ** 15) % 2 ** 15);
33795
34005
  ptsDtsBitstream.writeBits(1, 1);
33796
- ptsDtsBitstream.writeBits(15, pts & 32767);
34006
+ ptsDtsBitstream.writeBits(15, pts % 2 ** 15);
33797
34007
  ptsDtsBitstream.writeBits(1, 1);
33798
34008
  if (includeDts) {
33799
34009
  assert(queuedPacket.decodeTimestamp !== null);
33800
- const dts = Math.round(queuedPacket.decodeTimestamp * TIMESCALE);
34010
+ const unwrappedDts = Math.round(queuedPacket.decodeTimestamp * TIMESCALE);
34011
+ const dts = modEuclid(unwrappedDts, TIMESTAMP_MODULUS);
33801
34012
  ptsDtsBitstream.writeBits(4, 1);
33802
- ptsDtsBitstream.writeBits(3, dts >>> 30 & 7);
34013
+ ptsDtsBitstream.writeBits(3, Math.floor(dts / 2 ** 30));
33803
34014
  ptsDtsBitstream.writeBits(1, 1);
33804
- ptsDtsBitstream.writeBits(15, dts >>> 15 & 32767);
34015
+ ptsDtsBitstream.writeBits(15, Math.floor(dts / 2 ** 15) % 2 ** 15);
33805
34016
  ptsDtsBitstream.writeBits(1, 1);
33806
- ptsDtsBitstream.writeBits(15, dts & 32767);
34017
+ ptsDtsBitstream.writeBits(15, dts % 2 ** 15);
33807
34018
  ptsDtsBitstream.writeBits(1, 1);
33808
34019
  }
33809
34020
  const totalLength = pesHeaderBuffer.length + queuedPacket.data.length;
@@ -35390,8 +35601,8 @@ ${cue.notes ?? ""}`;
35390
35601
  * to respect writer and encoder backpressure.
35391
35602
  */
35392
35603
  add(timestamp, duration = 0, encodeOptions) {
35393
- if (!Number.isFinite(timestamp) || timestamp < 0) {
35394
- throw new TypeError("timestamp must be a non-negative number.");
35604
+ if (!Number.isFinite(timestamp)) {
35605
+ throw new TypeError("timestamp must be a finite number.");
35395
35606
  }
35396
35607
  if (!Number.isFinite(duration) || duration < 0) {
35397
35608
  throw new TypeError("duration must be a non-negative number.");
@@ -36236,19 +36447,24 @@ ${cue.notes ?? ""}`;
36236
36447
  var AudioBufferSource = class extends AudioSource {
36237
36448
  /**
36238
36449
  * Creates a new {@link AudioBufferSource} whose `AudioBuffer` instances are encoded according to the specified
36239
- * {@link AudioEncodingConfig}.
36450
+ * {@link AudioEncodingConfig} and {@link AudioBufferSourceOptions}.
36240
36451
  */
36241
- constructor(encodingConfig) {
36452
+ constructor(encodingConfig, options = {}) {
36242
36453
  validateAudioEncodingConfig(encodingConfig);
36454
+ if (typeof options !== "object" || !options) {
36455
+ throw new TypeError("options must be an object.");
36456
+ }
36457
+ if (options.startTimestamp !== void 0 && !Number.isFinite(options.startTimestamp)) {
36458
+ throw new TypeError("options.startTimestamp, when provided, must be a finite number.");
36459
+ }
36243
36460
  super(encodingConfig.codec);
36244
- /** @internal */
36245
- this._accumulatedTime = 0;
36246
36461
  this._encoder = new AudioEncoderWrapper(this, encodingConfig);
36462
+ this._accumulatedTime = options.startTimestamp ?? 0;
36247
36463
  }
36248
36464
  /**
36249
- * Converts an AudioBuffer to audio samples, encodes them and adds them to the output. The first AudioBuffer will
36250
- * be played at timestamp 0, and any subsequent AudioBuffer will have a timestamp equal to the total duration of
36251
- * all previous AudioBuffers.
36465
+ * Converts an AudioBuffer to audio samples, encodes them and adds them to the output. The first `AudioBuffer` will
36466
+ * be played at the configured start timestamp (the default is 0), and each subsequent `AudioBuffer` will be placed
36467
+ * directly after the previous one.
36252
36468
  *
36253
36469
  * @returns A Promise that resolves once the output is ready to receive more samples. You should await this Promise
36254
36470
  * to respect writer and encoder backpressure.
@@ -37830,6 +38046,9 @@ ${cue.notes ?? ""}`;
37830
38046
  get supportsTimestampedMediaData() {
37831
38047
  return true;
37832
38048
  }
38049
+ get negativeTimestampSupport() {
38050
+ return "full";
38051
+ }
37833
38052
  /** @internal */
37834
38053
  _createMuxer(output) {
37835
38054
  return new IsobmffMuxer2(output, this);
@@ -38004,6 +38223,9 @@ ${cue.notes ?? ""}`;
38004
38223
  get supportsTimestampedMediaData() {
38005
38224
  return true;
38006
38225
  }
38226
+ get negativeTimestampSupport() {
38227
+ return "prefer-non-negative";
38228
+ }
38007
38229
  };
38008
38230
  var WebMOutputFormat = class extends MkvOutputFormat2 {
38009
38231
  /** Creates a new {@link WebMOutputFormat} configured with the specified `options`. */
@@ -38081,6 +38303,9 @@ ${cue.notes ?? ""}`;
38081
38303
  get supportsTimestampedMediaData() {
38082
38304
  return false;
38083
38305
  }
38306
+ get negativeTimestampSupport() {
38307
+ return null;
38308
+ }
38084
38309
  };
38085
38310
  var WavOutputFormat = class extends OutputFormat {
38086
38311
  /** Creates a new {@link WavOutputFormat} configured with the specified `options`. */
@@ -38135,6 +38360,9 @@ ${cue.notes ?? ""}`;
38135
38360
  get supportsTimestampedMediaData() {
38136
38361
  return false;
38137
38362
  }
38363
+ get negativeTimestampSupport() {
38364
+ return null;
38365
+ }
38138
38366
  };
38139
38367
  var OggOutputFormat = class extends OutputFormat {
38140
38368
  /** Creates a new {@link OggOutputFormat} configured with the specified `options`. */
@@ -38185,6 +38413,9 @@ ${cue.notes ?? ""}`;
38185
38413
  get supportsTimestampedMediaData() {
38186
38414
  return false;
38187
38415
  }
38416
+ get negativeTimestampSupport() {
38417
+ return null;
38418
+ }
38188
38419
  };
38189
38420
  var AdtsOutputFormat = class extends OutputFormat {
38190
38421
  /** Creates a new {@link AdtsOutputFormat} configured with the specified `options`. */
@@ -38229,6 +38460,9 @@ ${cue.notes ?? ""}`;
38229
38460
  get supportsTimestampedMediaData() {
38230
38461
  return false;
38231
38462
  }
38463
+ get negativeTimestampSupport() {
38464
+ return null;
38465
+ }
38232
38466
  };
38233
38467
  var FlacOutputFormat = class extends OutputFormat {
38234
38468
  /** Creates a new {@link FlacOutputFormat} configured with the specified `options`. */
@@ -38273,6 +38507,9 @@ ${cue.notes ?? ""}`;
38273
38507
  get supportsTimestampedMediaData() {
38274
38508
  return false;
38275
38509
  }
38510
+ get negativeTimestampSupport() {
38511
+ return null;
38512
+ }
38276
38513
  };
38277
38514
  var MpegTsOutputFormat = class extends OutputFormat {
38278
38515
  /** Creates a new {@link MpegTsOutputFormat} configured with the specified `options`. */
@@ -38323,6 +38560,9 @@ ${cue.notes ?? ""}`;
38323
38560
  get supportsTimestampedMediaData() {
38324
38561
  return true;
38325
38562
  }
38563
+ get negativeTimestampSupport() {
38564
+ return "prefer-non-negative";
38565
+ }
38326
38566
  };
38327
38567
  var HlsOutputFormat = class extends OutputFormat {
38328
38568
  /** Creates a new {@link HlsOutputFormat} configured with the specified `options`. */
@@ -38416,6 +38656,19 @@ ${cue.notes ?? ""}`;
38416
38656
  get supportsTimestampedMediaData() {
38417
38657
  return true;
38418
38658
  }
38659
+ get negativeTimestampSupport() {
38660
+ const formats = toArray(this._options.segmentFormat);
38661
+ if (formats.some((format) => format.negativeTimestampSupport === "none")) {
38662
+ return "none";
38663
+ }
38664
+ if (formats.some((format) => format.negativeTimestampSupport === "prefer-non-negative")) {
38665
+ return "prefer-non-negative";
38666
+ }
38667
+ if (formats.some((format) => format.negativeTimestampSupport === "full")) {
38668
+ return "full";
38669
+ }
38670
+ return null;
38671
+ }
38419
38672
  /** @internal */
38420
38673
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
38421
38674
  _codecUnsupportedHint(codec) {
@@ -39154,15 +39407,16 @@ ${cue.notes ?? ""}`;
39154
39407
  var Conversion = class _Conversion {
39155
39408
  /** Creates a new Conversion instance (duh). */
39156
39409
  constructor(options) {
39157
- /**
39158
- * The current state of the conversion.
39159
- *
39160
- * - `'idle'`: The conversion is not currently executing and isn't done; `execute` can be called.
39161
- * - `'executing'`: A call to `execute` is currently running.
39162
- * - `'canceled'`: The conversion has been canceled and can no longer be executed.
39163
- * - `'done'`: The conversion has run to completion. Subsequent calls to `execute` do nothing.
39164
- */
39165
- this.state = "idle";
39410
+ /** @internal */
39411
+ this._state = "idle";
39412
+ /** @internal */
39413
+ this._timestampOffset = 0;
39414
+ /** @internal */
39415
+ this._timestampOffsetAdjusted = false;
39416
+ /** @internal */
39417
+ this._copyTimestampPossible = /* @__PURE__ */ new Map();
39418
+ /** @internal */
39419
+ this._copyStartPackets = /* @__PURE__ */ new Map();
39166
39420
  /** @internal */
39167
39421
  this._nextOutputTrackId = 0;
39168
39422
  /** @internal */
@@ -39233,6 +39487,22 @@ ${cue.notes ?? ""}`;
39233
39487
  if (options.composable !== void 0 && typeof options.composable !== "boolean") {
39234
39488
  throw new TypeError("options.composable, when provided, must be a boolean.");
39235
39489
  }
39490
+ if (options.copy !== void 0 && options.copy !== false) {
39491
+ if (!options.copy || typeof options.copy !== "object") {
39492
+ throw new TypeError("options.copy, when provided, must be an object or false.");
39493
+ }
39494
+ if (options.copy.mode !== void 0 && !["forced", "preferred"].includes(options.copy.mode)) {
39495
+ throw new TypeError("options.copy.mode, when provided, must be 'forced' or 'preferred'.");
39496
+ }
39497
+ if (options.copy.shiftTolerance !== void 0 && (!isNumber(options.copy.shiftTolerance) || options.copy.shiftTolerance < 0)) {
39498
+ throw new TypeError("options.copy.shiftTolerance, when provided, must be a non-negative number.");
39499
+ }
39500
+ if (options.copy.boundaryPolicy !== void 0 && !["expand", "shrink"].includes(options.copy.boundaryPolicy)) {
39501
+ throw new TypeError(
39502
+ "options.copy.boundaryPolicy, when provided, must be 'expand' or 'shrink'."
39503
+ );
39504
+ }
39505
+ }
39236
39506
  const composable = options.composable ?? false;
39237
39507
  if (!composable) {
39238
39508
  if (options.output.tracks.length > 0 || Object.keys(options.output._metadataTags).length > 0 || options.output.state !== "pending") {
@@ -39274,8 +39544,8 @@ ${cue.notes ?? ""}`;
39274
39544
  if (options.trim?.start !== void 0 && !Number.isFinite(options.trim.start)) {
39275
39545
  throw new TypeError("options.trim.start, when provided, must be a finite number.");
39276
39546
  }
39277
- if (options.trim?.end !== void 0 && !Number.isFinite(options.trim.end)) {
39278
- throw new TypeError("options.trim.end, when provided, must be a finite number.");
39547
+ if (options.trim?.end !== void 0 && !isNumber(options.trim.end)) {
39548
+ throw new TypeError("options.trim.end, when provided, must be a number.");
39279
39549
  }
39280
39550
  if (options.trim?.start !== void 0 && options.trim.end !== void 0 && options.trim.start >= options.trim.end) {
39281
39551
  throw new TypeError("options.trim.start must be less than options.trim.end.");
@@ -39290,10 +39560,24 @@ ${cue.notes ?? ""}`;
39290
39560
  throw new TypeError("options.showWarnings, when provided, must be a boolean.");
39291
39561
  }
39292
39562
  this._options = options;
39563
+ this._copyMode = options.copy === false ? false : options.copy?.mode ?? "preferred";
39564
+ this._copyTimestampShiftTolerance = options.copy === false ? 0 : options.copy?.shiftTolerance ?? 0;
39565
+ this._copyBoundaryPolicy = options.copy === false ? "expand" : options.copy?.boundaryPolicy ?? "expand";
39293
39566
  this._composable = composable;
39294
39567
  this.input = options.input;
39295
39568
  this.output = options.output;
39296
39569
  }
39570
+ /**
39571
+ * The current state of the conversion.
39572
+ *
39573
+ * - `'idle'`: The conversion is not currently executing and isn't done; `execute` can be called.
39574
+ * - `'executing'`: A call to `execute` is currently running.
39575
+ * - `'canceled'`: The conversion has been canceled and can no longer be executed.
39576
+ * - `'done'`: The conversion has run to completion. Subsequent calls to `execute` do nothing.
39577
+ */
39578
+ get state() {
39579
+ return this._state;
39580
+ }
39297
39581
  /** Initializes a new conversion process without starting the conversion. */
39298
39582
  static async init(options) {
39299
39583
  const conversion = new _Conversion(options);
@@ -39400,6 +39684,7 @@ ${cue.notes ?? ""}`;
39400
39684
  );
39401
39685
  }
39402
39686
  this._endTimestamp = Math.max(this._options.trim?.end ?? Infinity, this._startTimestamp);
39687
+ this._timestampOffset = -this._startTimestamp;
39403
39688
  for (let i = 0; i < filteredTracks.length; i++) {
39404
39689
  const track = filteredTracks[i];
39405
39690
  const options = filteredTrackOptions[i];
@@ -39459,7 +39744,7 @@ ${cue.notes ?? ""}`;
39459
39744
  }
39460
39745
  const inputAndOutputFormatMatch = inputFormat.mimeType === this.output.format.mimeType;
39461
39746
  const rawTagsAreUnchanged = inputTags.raw === outputTags.raw;
39462
- if (inputTags.raw && rawTagsAreUnchanged && !inputAndOutputFormatMatch) {
39747
+ if (rawTagsAreUnchanged && !inputAndOutputFormatMatch) {
39463
39748
  delete outputTags.raw;
39464
39749
  }
39465
39750
  this.output.setMetadataTags(outputTags);
@@ -39578,13 +39863,13 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
39578
39863
  "Cannot execute this conversion because its output configuration is invalid. Make sure to always check the isValid field before executing a conversion.\n" + this._getInvalidityExplanation().join("")
39579
39864
  );
39580
39865
  }
39581
- if (this.state === "executing") {
39866
+ if (this._state === "executing") {
39582
39867
  throw new Error("Cannot call execute() while a previous call to execute() is still running.");
39583
39868
  }
39584
- if (this.state === "canceled") {
39869
+ if (this._state === "canceled") {
39585
39870
  throw new ConversionCanceledError();
39586
39871
  }
39587
- if (this.state === "done") {
39872
+ if (this._state === "done") {
39588
39873
  return;
39589
39874
  }
39590
39875
  if (this._composable && this.output.state === "pending") {
@@ -39592,11 +39877,11 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
39592
39877
  "A composable conversion requires the output to be started. Call start() on the output before executing the conversion."
39593
39878
  );
39594
39879
  }
39595
- this.state = "executing";
39880
+ this._state = "executing";
39596
39881
  this._executionUntil = options.until ?? Infinity;
39597
39882
  this._pauseRequested = options.pauseSignal?.aborted ?? false;
39598
39883
  const onPause = () => {
39599
- if (this.state !== "executing") {
39884
+ if (this._state !== "executing") {
39600
39885
  return;
39601
39886
  }
39602
39887
  this._pauseRequested = true;
@@ -39646,18 +39931,18 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
39646
39931
  try {
39647
39932
  await Promise.all(this._trackPumps.map((x) => x.resolvers.promise));
39648
39933
  } catch (error) {
39649
- if (this.state !== "canceled") {
39934
+ if (this._state !== "canceled") {
39650
39935
  void this.cancel();
39651
39936
  }
39652
39937
  throw error;
39653
39938
  } finally {
39654
39939
  options.pauseSignal?.removeEventListener("abort", onPause);
39655
39940
  }
39656
- if (this.state === "canceled") {
39941
+ if (this._state === "canceled") {
39657
39942
  throw new ConversionCanceledError();
39658
39943
  }
39659
39944
  const isDone = this._trackPumps.every((x) => x.done);
39660
- this.state = isDone ? "done" : "idle";
39945
+ this._state = isDone ? "done" : "idle";
39661
39946
  if (isDone) {
39662
39947
  if (!this._composable) {
39663
39948
  await this.output.finalize();
@@ -39673,14 +39958,14 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
39673
39958
  * Does nothing if the conversion is already complete.
39674
39959
  */
39675
39960
  async cancel() {
39676
- if (this.state === "done") {
39961
+ if (this._state === "done") {
39677
39962
  return;
39678
39963
  }
39679
- if (this.state === "canceled") {
39964
+ if (this._state === "canceled") {
39680
39965
  Logging._warn("Conversion already canceled.");
39681
39966
  return;
39682
39967
  }
39683
- this.state = "canceled";
39968
+ this._state = "canceled";
39684
39969
  for (const pump of this._trackPumps) {
39685
39970
  pump.wake?.();
39686
39971
  }
@@ -39726,10 +40011,63 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
39726
40011
  width = ceilToMultipleOfTwo(trackOptions.width);
39727
40012
  height = ceilToMultipleOfTwo(trackOptions.height);
39728
40013
  }
39729
- const firstTimestamp = await track.getFirstTimestamp();
39730
40014
  let videoCodecs = this.output.format.getSupportedVideoCodecs();
39731
- const needsTranscode = !!trackOptions.forceTranscode || firstTimestamp < this._startTimestamp || !!trackOptions.frameRate || trackOptions.keyFrameInterval !== void 0 || trackOptions.process !== void 0 || trackOptions.quality !== void 0 || trackOptions.bitrate !== void 0 || !videoCodecs.includes(sourceCodec) || trackOptions.codec && trackOptions.codec !== sourceCodec || width !== originalWidth || height !== originalHeight || totalRotation !== 0 && !canUseRotationMetadata || !!crop;
39732
40015
  const alpha = trackOptions.alpha ?? "discard";
40016
+ let needsTranscode = !this._copyMode || !!trackOptions.forceTranscode || !!trackOptions.frameRate || trackOptions.keyFrameInterval !== void 0 || trackOptions.process !== void 0 || trackOptions.quality !== void 0 || trackOptions.bitrate !== void 0 || !videoCodecs.includes(sourceCodec) || trackOptions.codec && trackOptions.codec !== sourceCodec || width !== originalWidth || height !== originalHeight || totalRotation !== 0 && !canUseRotationMetadata || !!crop;
40017
+ let copyStartPacket = null;
40018
+ if (!needsTranscode) {
40019
+ const sink = new EncodedPacketSink(track);
40020
+ let startPacket = await sink.getKeyPacket(this._startTimestamp, { verifyKeyPackets: true }) ?? await sink.getFirstKeyPacket({ verifyKeyPackets: true });
40021
+ if (startPacket && startPacket.timestamp < this._startTimestamp && startPacket.timestamp + startPacket.duration <= this._startTimestamp && this._copyBoundaryPolicy === "shrink") {
40022
+ startPacket = await sink.getNextKeyPacket(startPacket, { verifyKeyPackets: true });
40023
+ }
40024
+ copyStartPacket = startPacket;
40025
+ if (startPacket) {
40026
+ const effectiveStartTimestamp = this._copyBoundaryPolicy === "shrink" ? Math.max(startPacket.timestamp, this._startTimestamp) : startPacket.timestamp;
40027
+ if (!this.output.format.supportsTimestampedMediaData) {
40028
+ if (this._timestampOffsetAdjusted) {
40029
+ const isValid = effectiveStartTimestamp + this._timestampOffset === 0;
40030
+ if (!isValid) {
40031
+ needsTranscode = true;
40032
+ }
40033
+ } else {
40034
+ const correction = clamp(
40035
+ this._startTimestamp - effectiveStartTimestamp,
40036
+ -this._copyTimestampShiftTolerance,
40037
+ this._copyTimestampShiftTolerance
40038
+ );
40039
+ const shiftedStartTimestamp = effectiveStartTimestamp + correction;
40040
+ const isValid = shiftedStartTimestamp === this._startTimestamp;
40041
+ if (isValid) {
40042
+ this._timestampOffset = -this._startTimestamp + correction;
40043
+ this._timestampOffsetAdjusted = true;
40044
+ } else {
40045
+ needsTranscode = true;
40046
+ }
40047
+ }
40048
+ } else if (this.output.format.negativeTimestampSupport !== "full" && effectiveStartTimestamp < this._startTimestamp) {
40049
+ const correction = Math.min(
40050
+ this._startTimestamp - effectiveStartTimestamp,
40051
+ this._copyTimestampShiftTolerance
40052
+ );
40053
+ const shiftedStartTimestamp = effectiveStartTimestamp + correction;
40054
+ const isValid = shiftedStartTimestamp >= this._startTimestamp || this.output.format.negativeTimestampSupport === "prefer-non-negative" && this._copyMode === "forced";
40055
+ if (isValid) {
40056
+ this._timestampOffset = Math.max(this._timestampOffset, -this._startTimestamp + correction);
40057
+ } else {
40058
+ needsTranscode = true;
40059
+ }
40060
+ }
40061
+ }
40062
+ }
40063
+ if (needsTranscode && this._copyMode === "forced") {
40064
+ this.discardedTracks.push({
40065
+ track,
40066
+ reason: "cannot_copy",
40067
+ trackOptions
40068
+ });
40069
+ return;
40070
+ }
39733
40071
  if (!needsTranscode) {
39734
40072
  const source = new EncodedVideoPacketSource(sourceCodec);
39735
40073
  videoSource = source;
@@ -39737,18 +40075,51 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
39737
40075
  const sink = new EncodedPacketSink(track);
39738
40076
  const decoderConfig = await track.getDecoderConfig();
39739
40077
  const meta = { decoderConfig: decoderConfig ?? void 0 };
39740
- for await (const packet of sink.packets(void 0, void 0, { verifyKeyPackets: true })) {
39741
- if (this.state === "canceled") {
40078
+ if (copyStartPacket) for await (const packet of sink.packets(
40079
+ copyStartPacket,
40080
+ void 0,
40081
+ { verifyKeyPackets: true }
40082
+ )) {
40083
+ if (this._state === "canceled") {
39742
40084
  break;
39743
40085
  }
39744
40086
  if (packet.timestamp >= this._endTimestamp) {
39745
- break;
40087
+ if (this._copyBoundaryPolicy === "shrink") {
40088
+ break;
40089
+ } else {
40090
+ let current = packet;
40091
+ let found = false;
40092
+ const lookahead = 6;
40093
+ for (let i = 0; i < lookahead; i++) {
40094
+ const next = await sink.getNextPacket(current, { metadataOnly: true });
40095
+ if (!next) {
40096
+ break;
40097
+ }
40098
+ if (next.timestamp < this._endTimestamp) {
40099
+ found = true;
40100
+ break;
40101
+ }
40102
+ current = next;
40103
+ }
40104
+ if (!found) {
40105
+ break;
40106
+ }
40107
+ }
39746
40108
  }
40109
+ let packetStartTimestamp = packet.timestamp;
40110
+ let packetEndTimestamp = packet.timestamp + packet.duration;
40111
+ if (this._copyBoundaryPolicy === "shrink") {
40112
+ packetStartTimestamp = Math.max(packetStartTimestamp, this._startTimestamp);
40113
+ packetEndTimestamp = Math.min(packetEndTimestamp, this._endTimestamp);
40114
+ packetEndTimestamp = Math.max(packetEndTimestamp, packetStartTimestamp);
40115
+ }
40116
+ packetStartTimestamp += this._timestampOffset;
40117
+ packetEndTimestamp += this._timestampOffset;
39747
40118
  const modifiedPacket = packet.clone({
39748
- timestamp: packet.timestamp - this._startTimestamp,
40119
+ timestamp: packetStartTimestamp,
40120
+ duration: packetEndTimestamp - packetStartTimestamp,
39749
40121
  sideData: alpha === "discard" ? {} : packet.sideData
39750
40122
  });
39751
- assert(modifiedPacket.timestamp >= 0);
39752
40123
  this._reportProgress(outputTrackId, modifiedPacket.timestamp + modifiedPacket.duration);
39753
40124
  await source.add(modifiedPacket, meta);
39754
40125
  if (this._synchronizer.shouldWait(outputTrackId, modifiedPacket.timestamp)) {
@@ -39809,7 +40180,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
39809
40180
  tempOutput.addVideoTrack(tempSource);
39810
40181
  await tempOutput.start();
39811
40182
  const sink = new VideoSampleSink(track);
39812
- const firstSample = __using(_stack, await sink.getSample(firstTimestamp));
40183
+ const firstSample = __using(_stack, await sink.getSample(await track.getFirstTimestamp()));
39813
40184
  if (firstSample) {
39814
40185
  try {
39815
40186
  await tempSource.add(firstSample);
@@ -39860,11 +40231,16 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
39860
40231
  var _stack2 = [];
39861
40232
  try {
39862
40233
  const sample = __using(_stack2, _sample);
39863
- if (this.state === "canceled") {
40234
+ if (this._state === "canceled") {
39864
40235
  break;
39865
40236
  }
39866
- const adjustedSampleTimestamp = Math.max(sample.timestamp - this._startTimestamp, 0);
39867
- sample.setTimestamp(adjustedSampleTimestamp);
40237
+ const clampedStartTimestamp = Math.max(this._startTimestamp, sample.timestamp);
40238
+ const clampedEndTimestamp = Math.min(this._endTimestamp, sample.timestamp + sample.duration);
40239
+ if (clampedStartTimestamp >= clampedEndTimestamp) {
40240
+ continue;
40241
+ }
40242
+ sample.setTimestamp(clampedStartTimestamp + this._timestampOffset);
40243
+ sample.setDuration(clampedEndTimestamp - clampedStartTimestamp);
39868
40244
  this._reportProgress(outputTrackId, sample.timestamp + sample.duration);
39869
40245
  await source.add(sample);
39870
40246
  sample.close();
@@ -39889,12 +40265,14 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
39889
40265
  ownGroup = new OutputTrackGroup();
39890
40266
  }
39891
40267
  const videoTrackLanguageCode = await track.getLanguageCode();
40268
+ const trackName = await track.getName();
40269
+ const trackDisposition = await track.getDisposition();
39892
40270
  this.output.addVideoTrack(videoSource, {
39893
40271
  frameRate: trackOptions.frameRate,
39894
40272
  // TODO: This condition can be removed when all demuxers properly homogenize to BCP47 in v2
39895
40273
  languageCode: isIso639Dash2LanguageCode(videoTrackLanguageCode) ? videoTrackLanguageCode : void 0,
39896
- name: await track.getName() ?? void 0,
39897
- disposition: await track.getDisposition(),
40274
+ name: trackName ?? void 0,
40275
+ disposition: trackDisposition,
39898
40276
  rotation: outputTrackRotation,
39899
40277
  group: ownGroup ?? trackOptions.group
39900
40278
  });
@@ -39916,30 +40294,93 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
39916
40294
  let audioSource;
39917
40295
  const originalNumberOfChannels = await track.getNumberOfChannels();
39918
40296
  const originalSampleRate = await track.getSampleRate();
39919
- const firstTimestamp = await track.getFirstTimestamp();
39920
40297
  let numberOfChannels = trackOptions.numberOfChannels ?? originalNumberOfChannels;
39921
40298
  let sampleRate = trackOptions.sampleRate ?? originalSampleRate;
39922
- const needsTrimming = firstTimestamp < this._startTimestamp;
39923
- let needsPadding = firstTimestamp > this._startTimestamp && !this.output.format.supportsTimestampedMediaData;
39924
40299
  let audioCodecs = this.output.format.getSupportedAudioCodecs();
39925
- if (!trackOptions.forceTranscode && !trackOptions.quality && !trackOptions.bitrate && numberOfChannels === originalNumberOfChannels && sampleRate === originalSampleRate && !needsTrimming && !needsPadding && audioCodecs.includes(sourceCodec) && (!trackOptions.codec || trackOptions.codec === sourceCodec) && !trackOptions.process && trackOptions.sampleFormat === void 0) {
40300
+ let needsTranscode = !this._copyMode || !!trackOptions.forceTranscode || !!trackOptions.quality || !!trackOptions.bitrate || numberOfChannels !== originalNumberOfChannels || sampleRate !== originalSampleRate || !audioCodecs.includes(sourceCodec) || !!trackOptions.codec && trackOptions.codec !== sourceCodec || trackOptions.process !== void 0 || trackOptions.sampleFormat !== void 0;
40301
+ let copyStartPacket = null;
40302
+ if (!needsTranscode) {
40303
+ const sink = new EncodedPacketSink(track);
40304
+ let startPacket = await sink.getKeyPacket(this._startTimestamp) ?? await sink.getFirstKeyPacket();
40305
+ if (startPacket && (this._copyBoundaryPolicy === "shrink" && startPacket.timestamp < this._startTimestamp || this._copyBoundaryPolicy === "expand" && startPacket.timestamp + startPacket.duration <= this._startTimestamp)) {
40306
+ startPacket = await sink.getNextKeyPacket(startPacket);
40307
+ }
40308
+ const hasDecoderWarmup = NON_PCM_AUDIO_CODECS.includes(sourceCodec) && sourceCodec !== "flac";
40309
+ if (startPacket && this._copyBoundaryPolicy === "expand" && hasDecoderWarmup) {
40310
+ const previousPacket = await sink.getKeyPacket(
40311
+ startPacket.timestamp - 1 / await track.getTimeResolution()
40312
+ );
40313
+ if (previousPacket) {
40314
+ startPacket = previousPacket;
40315
+ }
40316
+ }
40317
+ copyStartPacket = startPacket;
40318
+ if (startPacket) {
40319
+ if (!this.output.format.supportsTimestampedMediaData) {
40320
+ if (this._timestampOffsetAdjusted) {
40321
+ const isValid = startPacket.timestamp + this._timestampOffset === 0;
40322
+ if (!isValid) {
40323
+ needsTranscode = true;
40324
+ }
40325
+ } else {
40326
+ const correction = clamp(
40327
+ this._startTimestamp - startPacket.timestamp,
40328
+ -this._copyTimestampShiftTolerance,
40329
+ this._copyTimestampShiftTolerance
40330
+ );
40331
+ const shiftedStartTimestamp = startPacket.timestamp + correction;
40332
+ const isValid = shiftedStartTimestamp === this._startTimestamp;
40333
+ if (isValid) {
40334
+ this._timestampOffset = -this._startTimestamp + correction;
40335
+ this._timestampOffsetAdjusted = true;
40336
+ } else {
40337
+ needsTranscode = true;
40338
+ }
40339
+ }
40340
+ } else if (this.output.format.negativeTimestampSupport !== "full" && startPacket.timestamp < this._startTimestamp) {
40341
+ const correction = Math.min(
40342
+ this._startTimestamp - startPacket.timestamp,
40343
+ this._copyTimestampShiftTolerance
40344
+ );
40345
+ const shiftedStartTimestamp = startPacket.timestamp + correction;
40346
+ const isValid = shiftedStartTimestamp >= this._startTimestamp || this.output.format.negativeTimestampSupport === "prefer-non-negative" && this._copyMode === "forced";
40347
+ if (isValid) {
40348
+ this._timestampOffset = Math.max(this._timestampOffset, -this._startTimestamp + correction);
40349
+ } else {
40350
+ needsTranscode = true;
40351
+ }
40352
+ }
40353
+ }
40354
+ }
40355
+ if (needsTranscode && this._copyMode === "forced") {
40356
+ this.discardedTracks.push({
40357
+ track,
40358
+ reason: "cannot_copy",
40359
+ trackOptions
40360
+ });
40361
+ return;
40362
+ }
40363
+ if (!needsTranscode) {
39926
40364
  const source = new EncodedAudioPacketSource(sourceCodec);
39927
40365
  audioSource = source;
39928
40366
  this._registerTrackPump(async (pump) => {
39929
40367
  const sink = new EncodedPacketSink(track);
39930
40368
  const decoderConfig = await track.getDecoderConfig();
39931
40369
  const meta = { decoderConfig: decoderConfig ?? void 0 };
39932
- for await (const packet of sink.packets()) {
39933
- if (this.state === "canceled") {
40370
+ if (copyStartPacket) for await (const packet of sink.packets(copyStartPacket)) {
40371
+ if (this._state === "canceled") {
39934
40372
  break;
39935
40373
  }
39936
40374
  if (packet.timestamp >= this._endTimestamp) {
39937
40375
  break;
39938
40376
  }
40377
+ if (this._copyBoundaryPolicy === "shrink" && packet.timestamp + packet.duration > this._endTimestamp) {
40378
+ break;
40379
+ }
39939
40380
  const modifiedPacket = packet.clone({
39940
- timestamp: packet.timestamp - this._startTimestamp
40381
+ timestamp: packet.timestamp + this._timestampOffset,
40382
+ duration: packet.duration
39941
40383
  });
39942
- assert(modifiedPacket.timestamp >= 0);
39943
40384
  this._reportProgress(outputTrackId, modifiedPacket.timestamp + modifiedPacket.duration);
39944
40385
  await source.add(modifiedPacket, meta);
39945
40386
  if (this._synchronizer.shouldWait(outputTrackId, modifiedPacket.timestamp)) {
@@ -40015,6 +40456,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
40015
40456
  const source = new AudioSampleSource(encodingConfig);
40016
40457
  audioSource = source;
40017
40458
  this._registerTrackPump(async (pump) => {
40459
+ let needsPadding = null;
40018
40460
  const sink = new AudioSampleSink(track);
40019
40461
  for await (var _sample of sink.samples(this._startTimestamp, this._endTimestamp)) {
40020
40462
  var _stack3 = [];
@@ -40022,13 +40464,42 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
40022
40464
  const sample = __using(_stack3, _sample);
40023
40465
  var _stack2 = [];
40024
40466
  try {
40025
- if (this.state === "canceled") {
40467
+ if (this._state === "canceled") {
40026
40468
  break;
40027
40469
  }
40470
+ let startFrame = 0;
40471
+ let endFrame = sample.numberOfFrames;
40472
+ if (sample.timestamp < this._startTimestamp) {
40473
+ startFrame = Math.round((this._startTimestamp - sample.timestamp) * sample.sampleRate);
40474
+ }
40475
+ if (sample.timestamp + sample.duration > this._endTimestamp) {
40476
+ endFrame = Math.round((this._endTimestamp - sample.timestamp) * sample.sampleRate);
40477
+ }
40478
+ if (startFrame >= endFrame) {
40479
+ sample.close();
40480
+ continue;
40481
+ }
40482
+ let finalSampleLet;
40483
+ if (startFrame > 0 || endFrame < sample.numberOfFrames) {
40484
+ const trimmedSample = sample.trim(startFrame, endFrame);
40485
+ sample.close();
40486
+ finalSampleLet = trimmedSample;
40487
+ if (trimmedSample.numberOfFrames === 0) {
40488
+ trimmedSample.close();
40489
+ continue;
40490
+ }
40491
+ } else {
40492
+ finalSampleLet = sample;
40493
+ }
40494
+ const finalSample = __using(_stack2, finalSampleLet);
40495
+ finalSample.setTimestamp(finalSample.timestamp + this._timestampOffset);
40496
+ if (needsPadding === null) {
40497
+ needsPadding = finalSample.timestamp > 0 && !this.output.format.supportsTimestampedMediaData;
40498
+ }
40028
40499
  if (needsPadding) {
40029
40500
  var _stack = [];
40030
40501
  try {
40031
- const paddingLength = firstTimestamp - this._startTimestamp;
40502
+ const paddingLength = finalSample.timestamp;
40032
40503
  const paddingLengthSamples = Math.round(paddingLength * originalSampleRate);
40033
40504
  const bytesPerSample = getBytesPerSample(sample.format);
40034
40505
  const data = new Uint8Array(bytesPerSample * paddingLengthSamples * originalNumberOfChannels);
@@ -40058,28 +40529,6 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
40058
40529
  __callDispose(_stack, _error, _hasError);
40059
40530
  }
40060
40531
  }
40061
- let startFrame = 0;
40062
- let endFrame = sample.numberOfFrames;
40063
- if (sample.timestamp < this._startTimestamp) {
40064
- startFrame = Math.round((this._startTimestamp - sample.timestamp) * sample.sampleRate);
40065
- }
40066
- if (sample.timestamp + sample.duration > this._endTimestamp) {
40067
- endFrame = Math.round((this._endTimestamp - sample.timestamp) * sample.sampleRate);
40068
- }
40069
- let finalSampleLet;
40070
- if (startFrame > 0 || endFrame < sample.numberOfFrames) {
40071
- const trimmedSample = sample.trim(startFrame, endFrame);
40072
- sample.close();
40073
- finalSampleLet = trimmedSample;
40074
- if (trimmedSample.numberOfFrames === 0) {
40075
- trimmedSample.close();
40076
- continue;
40077
- }
40078
- } else {
40079
- finalSampleLet = sample;
40080
- }
40081
- const finalSample = __using(_stack2, finalSampleLet);
40082
- finalSample.setTimestamp(finalSample.timestamp - this._startTimestamp);
40083
40532
  await this._registerAudioSample(
40084
40533
  pump,
40085
40534
  finalSample,
@@ -40107,11 +40556,13 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
40107
40556
  ownGroup = new OutputTrackGroup();
40108
40557
  }
40109
40558
  const audioTrackLanguageCode = await track.getLanguageCode();
40559
+ const trackName = await track.getName();
40560
+ const trackDisposition = await track.getDisposition();
40110
40561
  this.output.addAudioTrack(audioSource, {
40111
40562
  // TODO: This condition can be removed when all demuxers properly homogenize to BCP47 in v2
40112
40563
  languageCode: isIso639Dash2LanguageCode(audioTrackLanguageCode) ? audioTrackLanguageCode : void 0,
40113
- name: await track.getName() ?? void 0,
40114
- disposition: await track.getDisposition(),
40564
+ name: trackName ?? void 0,
40565
+ disposition: trackDisposition,
40115
40566
  group: ownGroup ?? trackOptions.group
40116
40567
  });
40117
40568
  this.utilizedTracks.push(track);
@@ -40150,7 +40601,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
40150
40601
  }
40151
40602
  /** @internal */
40152
40603
  async _checkpoint(pump, timestamp) {
40153
- while (this.state !== "canceled" && (timestamp >= this._executionUntil || this._pauseRequested)) {
40604
+ while (this._state !== "canceled" && (timestamp >= this._executionUntil || this._pauseRequested)) {
40154
40605
  pump.resolvers.resolve();
40155
40606
  const { promise, resolve } = promiseWithResolvers();
40156
40607
  pump.wake = resolve;
@@ -40191,14 +40642,14 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
40191
40642
  this.conversion = conversion;
40192
40643
  }
40193
40644
  declareTrack(trackId) {
40194
- this.maxTimestamps.set(trackId, 0);
40645
+ this.maxTimestamps.set(trackId, -Infinity);
40195
40646
  }
40196
40647
  shouldWait(trackId, timestamp) {
40197
40648
  const currentValue = this.maxTimestamps.get(trackId);
40198
40649
  assert(currentValue !== void 0);
40199
40650
  this.maxTimestamps.set(trackId, Math.max(timestamp, currentValue));
40200
40651
  const newMin = this.computeMinAndMaybeResolve();
40201
- if (this.conversion.state === "canceled" || this.conversion._pauseRequested || timestamp >= this.conversion._executionUntil) {
40652
+ if (this.conversion._state === "canceled" || this.conversion._pauseRequested || timestamp >= this.conversion._executionUntil) {
40202
40653
  return false;
40203
40654
  }
40204
40655
  return timestamp - newMin > MAX_TIMESTAMP_GAP;