mediabunny 1.55.7 → 1.56.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/bundles/mediabunny.cjs +529 -180
  2. package/dist/bundles/mediabunny.min.cjs +12 -12
  3. package/dist/bundles/mediabunny.min.mjs +12 -12
  4. package/dist/bundles/mediabunny.mjs +529 -180
  5. package/dist/bundles/mediabunny.node.cjs +529 -180
  6. package/dist/mediabunny.d.ts +106 -19
  7. package/dist/modules/src/conversion.d.ts +57 -12
  8. package/dist/modules/src/conversion.d.ts.map +1 -1
  9. package/dist/modules/src/conversion.js +353 -119
  10. package/dist/modules/src/index.d.ts +2 -2
  11. package/dist/modules/src/index.d.ts.map +1 -1
  12. package/dist/modules/src/isobmff/isobmff-boxes.d.ts +1 -1
  13. package/dist/modules/src/isobmff/isobmff-boxes.d.ts.map +1 -1
  14. package/dist/modules/src/isobmff/isobmff-boxes.js +59 -29
  15. package/dist/modules/src/isobmff/isobmff-demuxer.d.ts.map +1 -1
  16. package/dist/modules/src/isobmff/isobmff-demuxer.js +0 -4
  17. package/dist/modules/src/isobmff/isobmff-muxer.d.ts +1 -1
  18. package/dist/modules/src/isobmff/isobmff-muxer.d.ts.map +1 -1
  19. package/dist/modules/src/isobmff/isobmff-muxer.js +14 -6
  20. package/dist/modules/src/matroska/matroska-demuxer.d.ts +1 -0
  21. package/dist/modules/src/matroska/matroska-demuxer.d.ts.map +1 -1
  22. package/dist/modules/src/matroska/matroska-demuxer.js +20 -8
  23. package/dist/modules/src/matroska/matroska-muxer.d.ts +1 -0
  24. package/dist/modules/src/matroska/matroska-muxer.d.ts.map +1 -1
  25. package/dist/modules/src/matroska/matroska-muxer.js +10 -1
  26. package/dist/modules/src/media-source.d.ts +17 -5
  27. package/dist/modules/src/media-source.d.ts.map +1 -1
  28. package/dist/modules/src/media-source.js +14 -9
  29. package/dist/modules/src/misc.d.ts +2 -0
  30. package/dist/modules/src/misc.d.ts.map +1 -1
  31. package/dist/modules/src/misc.js +6 -0
  32. package/dist/modules/src/mpeg-ts/mpeg-ts-demuxer.d.ts +5 -0
  33. package/dist/modules/src/mpeg-ts/mpeg-ts-demuxer.d.ts.map +1 -1
  34. package/dist/modules/src/mpeg-ts/mpeg-ts-demuxer.js +37 -16
  35. package/dist/modules/src/mpeg-ts/mpeg-ts-misc.d.ts +1 -0
  36. package/dist/modules/src/mpeg-ts/mpeg-ts-misc.d.ts.map +1 -1
  37. package/dist/modules/src/mpeg-ts/mpeg-ts-misc.js +1 -0
  38. package/dist/modules/src/mpeg-ts/mpeg-ts-muxer.d.ts.map +1 -1
  39. package/dist/modules/src/mpeg-ts/mpeg-ts-muxer.js +14 -10
  40. package/dist/modules/src/muxer.d.ts.map +1 -1
  41. package/dist/modules/src/muxer.js +0 -3
  42. package/dist/modules/src/output-format.d.ts +17 -0
  43. package/dist/modules/src/output-format.d.ts.map +1 -1
  44. package/dist/modules/src/output-format.js +39 -1
  45. package/dist/modules/src/sample.d.ts +2 -2
  46. package/dist/modules/src/sample.d.ts.map +1 -1
  47. package/dist/modules/src/sample.js +16 -16
  48. package/dist/modules/src/source.d.ts +10 -0
  49. package/dist/modules/src/source.d.ts.map +1 -1
  50. package/dist/modules/src/source.js +38 -3
  51. package/dist/modules/src/tsconfig.tsbuildinfo +1 -1
  52. package/package.json +1 -1
  53. package/src/conversion.ts +460 -99
  54. package/src/index.ts +2 -0
  55. package/src/isobmff/isobmff-boxes.ts +76 -32
  56. package/src/isobmff/isobmff-demuxer.ts +0 -5
  57. package/src/isobmff/isobmff-muxer.ts +22 -12
  58. package/src/matroska/matroska-demuxer.ts +24 -11
  59. package/src/matroska/matroska-muxer.ts +14 -1
  60. package/src/media-source.ts +28 -8
  61. package/src/misc.ts +8 -0
  62. package/src/mpeg-ts/mpeg-ts-demuxer.ts +52 -14
  63. package/src/mpeg-ts/mpeg-ts-misc.ts +1 -0
  64. package/src/mpeg-ts/mpeg-ts-muxer.ts +20 -10
  65. package/src/muxer.ts +0 -4
  66. package/src/output-format.ts +58 -1
  67. package/src/sample.ts +16 -16
  68. 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) {
@@ -570,6 +573,9 @@ var Mediabunny = (() => {
570
573
  var lerp = (from, to, t) => {
571
574
  return from + (to - from) * t;
572
575
  };
576
+ var modEuclid = (value, modulus) => {
577
+ return value - Math.floor(value / modulus) * modulus;
578
+ };
573
579
  var UNDETERMINED_LANGUAGE = "und";
574
580
  var roundIfAlmostInteger = (value) => {
575
581
  const rounded = Math.round(value);
@@ -6657,9 +6663,6 @@ var Mediabunny = (() => {
6657
6663
  const segmentDuration = version === 1 ? readU64Be(slice) : readU32Be(slice);
6658
6664
  const mediaTime = version === 1 ? readI64Be(slice) : readI32Be(slice);
6659
6665
  const mediaRate = readFixed_16_16(slice);
6660
- if (segmentDuration === 0) {
6661
- continue;
6662
- }
6663
6666
  if (relevantEntryFound) {
6664
6667
  Logging._warn(
6665
6668
  "Unsupported edit list: multiple edits are not currently supported. Only using first edit."
@@ -11321,6 +11324,22 @@ var Mediabunny = (() => {
11321
11324
  }
11322
11325
  }
11323
11326
  }
11327
+ async getDurationFromMetadata(segment) {
11328
+ if (segment.duration <= 0) {
11329
+ return null;
11330
+ }
11331
+ let minTimestamp = null;
11332
+ for (const track of segment.tracks) {
11333
+ assert(track.trackBacking);
11334
+ const firstPacket = await track.trackBacking.getFirstPacket({ metadataOnly: true });
11335
+ if (firstPacket) {
11336
+ minTimestamp = Math.min(minTimestamp ?? Infinity, firstPacket.timestamp);
11337
+ }
11338
+ }
11339
+ let endTimestamp = segment.duration / segment.timestampFactor;
11340
+ endTimestamp += minTimestamp ?? 0;
11341
+ return endTimestamp;
11342
+ }
11324
11343
  };
11325
11344
  var MatroskaTrackBacking = class {
11326
11345
  constructor(internalTrack) {
@@ -11380,14 +11399,7 @@ var Mediabunny = (() => {
11380
11399
  return null;
11381
11400
  }
11382
11401
  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;
11402
+ return this.internalTrack.demuxer.getDurationFromMetadata(this.internalTrack.segment);
11391
11403
  }
11392
11404
  async getLiveRefreshInterval() {
11393
11405
  return null;
@@ -14474,6 +14486,7 @@ var Mediabunny = (() => {
14474
14486
 
14475
14487
  // src/mpeg-ts/mpeg-ts-misc.ts
14476
14488
  var TIMESCALE = 9e4;
14489
+ var TIMESTAMP_MODULUS = 2 ** 33;
14477
14490
  var TS_PACKET_SIZE = 188;
14478
14491
  var buildMpegTsMimeType = (codecStrings) => {
14479
14492
  let string = "video/MP2T";
@@ -14508,6 +14521,7 @@ var Mediabunny = (() => {
14508
14521
  this.seekChunkSize = 5 * 1024 * 1024;
14509
14522
  // 5 MiB, picked because most HLS segments are below this size
14510
14523
  this.minReferencePointByteDistance = -1;
14524
+ this.timestampWrapInfo = null;
14511
14525
  this.reader = input._reader;
14512
14526
  }
14513
14527
  async readMetadata() {
@@ -14765,7 +14779,7 @@ var Mediabunny = (() => {
14765
14779
  const elementaryStream = this.elementaryStreams.find((x) => x.pid === section.pid);
14766
14780
  outer:
14767
14781
  if (elementaryStream && !elementaryStream.initialized) {
14768
- const pesPacket = readPesPacket(section, true);
14782
+ const pesPacket = readPesPacket(this, section, true);
14769
14783
  if (!pesPacket) {
14770
14784
  throw new Error(
14771
14785
  `Couldn't read first PES packet for Elementary Stream with PID ${elementaryStream.pid}`
@@ -15160,8 +15174,22 @@ var Mediabunny = (() => {
15160
15174
  body: bytes2.subarray(4)
15161
15175
  };
15162
15176
  }
15177
+ normalizeTimestamp(timestamp) {
15178
+ if (!this.timestampWrapInfo) {
15179
+ const tolerance = 60 * TIMESCALE;
15180
+ this.timestampWrapInfo = {
15181
+ reference: timestamp - tolerance,
15182
+ offset: timestamp >= TIMESTAMP_MODULUS - tolerance ? -TIMESTAMP_MODULUS : TIMESTAMP_MODULUS
15183
+ };
15184
+ }
15185
+ const { reference, offset } = this.timestampWrapInfo;
15186
+ if (offset < 0 && timestamp >= reference || offset > 0 && timestamp < reference) {
15187
+ return timestamp + offset;
15188
+ }
15189
+ return timestamp;
15190
+ }
15163
15191
  };
15164
- var readPesPacketHeader = (section, expectPts) => {
15192
+ var readPesPacketHeader = (demuxer, section, expectPts) => {
15165
15193
  if (section.payload.byteLength < 3) {
15166
15194
  return null;
15167
15195
  }
@@ -15187,6 +15215,7 @@ var Mediabunny = (() => {
15187
15215
  pts += bitstream.readBits(15) * (1 << 15);
15188
15216
  bitstream.skipBits(1);
15189
15217
  pts += bitstream.readBits(15);
15218
+ pts = demuxer.normalizeTimestamp(pts);
15190
15219
  } else {
15191
15220
  if (expectPts) {
15192
15221
  throw new Error(MISSING_PTS_ERROR_MESSAGE);
@@ -15199,9 +15228,9 @@ var Mediabunny = (() => {
15199
15228
  randomAccessIndicator: section.randomAccessIndicator
15200
15229
  };
15201
15230
  };
15202
- var readPesPacket = (section, expectPts) => {
15231
+ var readPesPacket = (demuxer, section, expectPts) => {
15203
15232
  assert(section.endPos !== null);
15204
- const header = readPesPacketHeader(section, expectPts);
15233
+ const header = readPesPacketHeader(demuxer, section, expectPts);
15205
15234
  if (!header) {
15206
15235
  return null;
15207
15236
  }
@@ -15313,7 +15342,7 @@ var Mediabunny = (() => {
15313
15342
  async getFirstPacket(options) {
15314
15343
  const section = this.elementaryStream.firstSection;
15315
15344
  assert(section);
15316
- const pesPacket = readPesPacket(section, true);
15345
+ const pesPacket = readPesPacket(this.elementaryStream.demuxer, section, true);
15317
15346
  assert(pesPacket);
15318
15347
  const context = new PacketReadingContext(this.elementaryStream, pesPacket);
15319
15348
  const buffer = new PacketBuffer(this, context);
@@ -15346,7 +15375,7 @@ var Mediabunny = (() => {
15346
15375
  const demuxer = this.elementaryStream.demuxer;
15347
15376
  const section = await demuxer.readSection(sectionStartPos, true);
15348
15377
  assert(section);
15349
- const pesPacket = readPesPacket(section, true);
15378
+ const pesPacket = readPesPacket(demuxer, section, true);
15350
15379
  assert(pesPacket);
15351
15380
  const context = new PacketReadingContext(this.elementaryStream, pesPacket);
15352
15381
  buffer = new PacketBuffer(this, context);
@@ -15404,7 +15433,7 @@ var Mediabunny = (() => {
15404
15433
  if (!section) {
15405
15434
  return null;
15406
15435
  }
15407
- const pesPacketHeader = readPesPacketHeader(section, false);
15436
+ const pesPacketHeader = readPesPacketHeader(demuxer, section, false);
15408
15437
  if (pesPacketHeader && pesPacketHeader.pts !== null) {
15409
15438
  return {
15410
15439
  pesPacketHeader,
@@ -15418,7 +15447,7 @@ var Mediabunny = (() => {
15418
15447
  };
15419
15448
  const firstSection = this.elementaryStream.firstSection;
15420
15449
  assert(firstSection);
15421
- const firstPesPacketHeader = readPesPacketHeader(firstSection, true);
15450
+ const firstPesPacketHeader = readPesPacketHeader(demuxer, firstSection, true);
15422
15451
  assert(firstPesPacketHeader);
15423
15452
  if (searchPts < firstPesPacketHeader.pts) {
15424
15453
  return null;
@@ -15473,7 +15502,7 @@ var Mediabunny = (() => {
15473
15502
  const retrieveEncodedPacket = async (sectionStartPos, predicate) => {
15474
15503
  const section = await demuxer.readSection(sectionStartPos, true);
15475
15504
  assert(section);
15476
- const pesPacket = readPesPacket(section, true);
15505
+ const pesPacket = readPesPacket(demuxer, section, true);
15477
15506
  assert(pesPacket);
15478
15507
  const context = new PacketReadingContext(this.elementaryStream, pesPacket);
15479
15508
  const buffer = new PacketBuffer(this, context);
@@ -15516,7 +15545,7 @@ var Mediabunny = (() => {
15516
15545
  if (packetHeader.pid === pid && packetHeader.payloadUnitStartIndicator === 1) {
15517
15546
  const section = await demuxer.readSection(currentPos, false);
15518
15547
  if (section) {
15519
- const nextPesHeader = readPesPacketHeader(section, false);
15548
+ const nextPesHeader = readPesPacketHeader(demuxer, section, false);
15520
15549
  if (nextPesHeader && nextPesHeader.pts !== null) {
15521
15550
  if (nextPesHeader.pts > searchPts) {
15522
15551
  break outer;
@@ -15541,7 +15570,7 @@ var Mediabunny = (() => {
15541
15570
  if (packetHeader.pid === pid && packetHeader.payloadUnitStartIndicator === 1) {
15542
15571
  const section = await demuxer.readSection(pos, false);
15543
15572
  if (section) {
15544
- const header = readPesPacketHeader(section, false);
15573
+ const header = readPesPacketHeader(demuxer, section, false);
15545
15574
  if (header && header.pts !== null) {
15546
15575
  currentPesHeader = header;
15547
15576
  break;
@@ -15586,7 +15615,7 @@ var Mediabunny = (() => {
15586
15615
  isKeyPacket = pesHeader.randomAccessIndicator === 1;
15587
15616
  } else {
15588
15617
  assert(pesHeaderSection);
15589
- const pesPacket = readPesPacket(pesHeaderSection, true);
15618
+ const pesPacket = readPesPacket(demuxer, pesHeaderSection, true);
15590
15619
  assert(pesPacket);
15591
15620
  const context = new PacketReadingContext(this.elementaryStream, pesPacket);
15592
15621
  await context.markNextPacket();
@@ -15614,7 +15643,7 @@ var Mediabunny = (() => {
15614
15643
  if (packetHeader.pid === pid && packetHeader.payloadUnitStartIndicator === 1) {
15615
15644
  const section = await demuxer.readSection(currentPos, readSectionsInFull);
15616
15645
  if (section) {
15617
- const nextPesHeader = readPesPacketHeader(section, false);
15646
+ const nextPesHeader = readPesPacketHeader(demuxer, section, false);
15618
15647
  if (nextPesHeader && nextPesHeader.pts !== null) {
15619
15648
  pesHeader = nextPesHeader;
15620
15649
  pesHeaderSection = section;
@@ -15640,7 +15669,7 @@ var Mediabunny = (() => {
15640
15669
  if (packetHeader.pid === pid && packetHeader.payloadUnitStartIndicator === 1) {
15641
15670
  const section = await demuxer.readSection(pos, readSectionsInFull);
15642
15671
  if (section) {
15643
- const header = readPesPacketHeader(section, false);
15672
+ const header = readPesPacketHeader(demuxer, section, false);
15644
15673
  if (header && header.pts !== null) {
15645
15674
  startPesHeader = header;
15646
15675
  break;
@@ -15812,7 +15841,7 @@ var Mediabunny = (() => {
15812
15841
  if (!nextSection) {
15813
15842
  return;
15814
15843
  }
15815
- const nextPesPacket = readPesPacket(nextSection, false);
15844
+ const nextPesPacket = readPesPacket(this.demuxer, nextSection, false);
15816
15845
  if (nextPesPacket) {
15817
15846
  pesPacket = nextPesPacket;
15818
15847
  break;
@@ -17084,6 +17113,9 @@ var Mediabunny = (() => {
17084
17113
  if (options.useStreamReader !== void 0 && typeof options.useStreamReader !== "boolean") {
17085
17114
  throw new TypeError("options.useStreamReader, when provided, must be a boolean.");
17086
17115
  }
17116
+ if (options.handleUnhandledError !== void 0 && typeof options.handleUnhandledError !== "function") {
17117
+ throw new TypeError("options.handleUnhandledError, when provided, must be a function.");
17118
+ }
17087
17119
  super();
17088
17120
  /** @internal */
17089
17121
  this._readers = /* @__PURE__ */ new WeakMap();
@@ -17102,7 +17134,8 @@ var Mediabunny = (() => {
17102
17134
  });
17103
17135
  }
17104
17136
  },
17105
- prefetchProfile: PREFETCH_PROFILES.fileSystem
17137
+ prefetchProfile: PREFETCH_PROFILES.fileSystem,
17138
+ handleUnhandledError: options.handleUnhandledError
17106
17139
  });
17107
17140
  this._orchestrator.fileSize = blob.size;
17108
17141
  }
@@ -17210,6 +17243,9 @@ var Mediabunny = (() => {
17210
17243
  if (options.fetchFn !== void 0 && typeof options.fetchFn !== "function") {
17211
17244
  throw new TypeError("options.fetchFn, when provided, must be a function.");
17212
17245
  }
17246
+ if (options.handleUnhandledError !== void 0 && typeof options.handleUnhandledError !== "function") {
17247
+ throw new TypeError("options.handleUnhandledError, when provided, must be a function.");
17248
+ }
17213
17249
  const urlString = url2 instanceof Request ? url2.url : url2 instanceof URL ? url2.href : url2;
17214
17250
  super(
17215
17251
  urlString,
@@ -17266,7 +17302,8 @@ var Mediabunny = (() => {
17266
17302
  maxCacheSize: options.maxCacheSize ?? 64 * 2 ** 20,
17267
17303
  maxWorkerCount: options.parallelism ?? DEFAULT_PARALLELISM,
17268
17304
  runWorker: this._runWorker.bind(this),
17269
- prefetchProfile: PREFETCH_PROFILES.network
17305
+ prefetchProfile: PREFETCH_PROFILES.network,
17306
+ handleUnhandledError: options.handleUnhandledError
17270
17307
  });
17271
17308
  }
17272
17309
  /** @internal */
@@ -17329,7 +17366,7 @@ var Mediabunny = (() => {
17329
17366
  this.rootPath = response.url;
17330
17367
  }
17331
17368
  outer:
17332
- if (this._orchestrator.fileSize === null) {
17369
+ if (this._orchestrator.fileSize === null && (response.status === 206 || response.type === "basic" && !response.headers.has("Content-Encoding"))) {
17333
17370
  const contentRange = response.headers.get("Content-Range");
17334
17371
  if (contentRange) {
17335
17372
  const match = /\/(\d+)/.exec(contentRange);
@@ -17485,7 +17522,8 @@ var Mediabunny = (() => {
17485
17522
  cancel: () => currentReader.cancel()
17486
17523
  });
17487
17524
  const backing = new ReadableStreamSource(wrappedStream, {
17488
- maxCacheSize: this._orchestrator.options.maxCacheSize
17525
+ maxCacheSize: this._orchestrator.options.maxCacheSize,
17526
+ handleUnhandledError: this._options.handleUnhandledError
17489
17527
  });
17490
17528
  backing._endIndex = this._orchestrator.fileSize;
17491
17529
  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 +17623,8 @@ var Mediabunny = (() => {
17585
17623
  return buffer;
17586
17624
  },
17587
17625
  maxCacheSize: options.maxCacheSize,
17588
- prefetchProfile: "fileSystem"
17626
+ prefetchProfile: "fileSystem",
17627
+ handleUnhandledError: options.handleUnhandledError
17589
17628
  });
17590
17629
  }
17591
17630
  /** @internal */
@@ -17621,6 +17660,9 @@ var Mediabunny = (() => {
17621
17660
  if (options.dispose !== void 0 && typeof options.dispose !== "function") {
17622
17661
  throw new TypeError("options.dispose, when provided, must be a function.");
17623
17662
  }
17663
+ if (options.handleUnhandledError !== void 0 && typeof options.handleUnhandledError !== "function") {
17664
+ throw new TypeError("options.handleUnhandledError, when provided, must be a function.");
17665
+ }
17624
17666
  if (options.maxCacheSize !== void 0 && (!isNumber(options.maxCacheSize) || options.maxCacheSize < 0)) {
17625
17667
  throw new TypeError("options.maxCacheSize, when provided, must be a non-negative number.");
17626
17668
  }
@@ -17636,7 +17678,8 @@ var Mediabunny = (() => {
17636
17678
  maxWorkerCount: 2,
17637
17679
  // Fixed for now, *should* be fine
17638
17680
  prefetchProfile: PREFETCH_PROFILES[options.prefetchProfile ?? "none"],
17639
- runWorker: this._runWorker.bind(this)
17681
+ runWorker: this._runWorker.bind(this),
17682
+ handleUnhandledError: options.handleUnhandledError
17640
17683
  });
17641
17684
  }
17642
17685
  /** @internal */
@@ -17728,6 +17771,9 @@ var Mediabunny = (() => {
17728
17771
  if (!options || typeof options !== "object") {
17729
17772
  throw new TypeError("options must be an object.");
17730
17773
  }
17774
+ if (options.handleUnhandledError !== void 0 && typeof options.handleUnhandledError !== "function") {
17775
+ throw new TypeError("options.handleUnhandledError, when provided, must be a function.");
17776
+ }
17731
17777
  if (options.maxCacheSize !== void 0 && (!isNumber(options.maxCacheSize) || options.maxCacheSize < 0)) {
17732
17778
  throw new TypeError("options.maxCacheSize, when provided, must be a non-negative number.");
17733
17779
  }
@@ -17755,6 +17801,7 @@ var Mediabunny = (() => {
17755
17801
  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
17802
  this._stream = stream;
17757
17803
  this._maxCacheSize = options.maxCacheSize ?? 32 * 2 ** 20;
17804
+ this._handleUnhandledError = options.handleUnhandledError;
17758
17805
  }
17759
17806
  /** @internal */
17760
17807
  _getFileSize() {
@@ -17823,6 +17870,8 @@ var Mediabunny = (() => {
17823
17870
  if (this._pendingSlices.length > 0) {
17824
17871
  this._pendingSlices.forEach((x) => x.reject(error));
17825
17872
  this._pendingSlices.length = 0;
17873
+ } else if (this._handleUnhandledError) {
17874
+ this._handleUnhandledError(error);
17826
17875
  } else {
17827
17876
  throw error;
17828
17877
  }
@@ -18106,7 +18155,11 @@ var Mediabunny = (() => {
18106
18155
  if (this.disposed) {
18107
18156
  return;
18108
18157
  }
18109
- throw error;
18158
+ if (this.options.handleUnhandledError) {
18159
+ this.options.handleUnhandledError(error);
18160
+ } else {
18161
+ throw error;
18162
+ }
18110
18163
  });
18111
18164
  }
18112
18165
  return result;
@@ -18194,7 +18247,11 @@ var Mediabunny = (() => {
18194
18247
  worker.pendingSlices.forEach((x) => x.reject(error));
18195
18248
  worker.pendingSlices.length = 0;
18196
18249
  } else if (!worker.aborted && !this.disposed) {
18197
- throw error;
18250
+ if (this.options.handleUnhandledError) {
18251
+ this.options.handleUnhandledError(error);
18252
+ } else {
18253
+ throw error;
18254
+ }
18198
18255
  }
18199
18256
  }).finally(() => {
18200
18257
  if (worker.running) {
@@ -22098,30 +22155,30 @@ var Mediabunny = (() => {
22098
22155
  }
22099
22156
  }
22100
22157
  /**
22101
- * Returns a new {@link AudioSample} containing only the frames in the range [startSample, endSample). Both bounds
22158
+ * Returns a new {@link AudioSample} containing only the frames in the range [startFrame, endFrame). Both bounds
22102
22159
  * must lie within this sample's range of frames. The returned sample's timestamp is shifted to match the start of
22103
22160
  * the trimmed section.
22104
22161
  */
22105
- trim(startSample, endSample = this.numberOfFrames) {
22106
- if (!Number.isInteger(startSample) || startSample < 0) {
22107
- throw new TypeError("startSample must be a non-negative integer.");
22162
+ trim(startFrame, endFrame = this.numberOfFrames) {
22163
+ if (!Number.isInteger(startFrame) || startFrame < 0) {
22164
+ throw new TypeError("startFrame must be a non-negative integer.");
22108
22165
  }
22109
- if (!Number.isInteger(endSample) || endSample < 0) {
22110
- throw new TypeError("endSample must be a non-negative integer.");
22166
+ if (!Number.isInteger(endFrame) || endFrame < 0) {
22167
+ throw new TypeError("endFrame must be a non-negative integer.");
22111
22168
  }
22112
- if (startSample > this.numberOfFrames) {
22113
- throw new RangeError("startSample out of range.");
22169
+ if (startFrame > this.numberOfFrames) {
22170
+ throw new RangeError("startFrame out of range.");
22114
22171
  }
22115
- if (endSample > this.numberOfFrames) {
22116
- throw new RangeError("endSample out of range.");
22172
+ if (endFrame > this.numberOfFrames) {
22173
+ throw new RangeError("endFrame out of range.");
22117
22174
  }
22118
- if (endSample < startSample) {
22119
- throw new RangeError("endSample must not be less than startSample.");
22175
+ if (endFrame < startFrame) {
22176
+ throw new RangeError("endFrame must not be less than startFrame.");
22120
22177
  }
22121
22178
  if (this._closed) {
22122
22179
  throw new Error("AudioSample is closed.");
22123
22180
  }
22124
- const frameCount = endSample - startSample;
22181
+ const frameCount = endFrame - startFrame;
22125
22182
  const bytesPerSample = getBytesPerSample(this.format);
22126
22183
  let data;
22127
22184
  if (formatIsPlanar(this.format)) {
@@ -22132,7 +22189,7 @@ var Mediabunny = (() => {
22132
22189
  this.copyTo(data.subarray(i * planeSize, (i + 1) * planeSize), {
22133
22190
  planeIndex: i,
22134
22191
  format: this.format,
22135
- frameOffset: startSample,
22192
+ frameOffset: startFrame,
22136
22193
  frameCount
22137
22194
  });
22138
22195
  }
@@ -22143,7 +22200,7 @@ var Mediabunny = (() => {
22143
22200
  this.copyTo(data, {
22144
22201
  planeIndex: 0,
22145
22202
  format: this.format,
22146
- frameOffset: startSample,
22203
+ frameOffset: startFrame,
22147
22204
  frameCount
22148
22205
  });
22149
22206
  }
@@ -22153,7 +22210,7 @@ var Mediabunny = (() => {
22153
22210
  format: this.format,
22154
22211
  sampleRate: this.sampleRate,
22155
22212
  numberOfChannels: this.numberOfChannels,
22156
- timestamp: this.timestamp + startSample / this.sampleRate
22213
+ timestamp: this.timestamp + startFrame / this.sampleRate
22157
22214
  });
22158
22215
  }
22159
22216
  /**
@@ -27886,9 +27943,6 @@ var Mediabunny = (() => {
27886
27943
  onTrackClose(track) {
27887
27944
  }
27888
27945
  validateTimestamp(track, timestampInSeconds, isKeyPacket) {
27889
- if (timestampInSeconds < 0) {
27890
- throw new Error(`Timestamps must be non-negative (got ${timestampInSeconds}s).`);
27891
- }
27892
27946
  let timestampInfo = this.trackTimestampInfo.get(track);
27893
27947
  if (!timestampInfo) {
27894
27948
  if (!isKeyPacket) {
@@ -28564,7 +28618,8 @@ var Mediabunny = (() => {
28564
28618
  ascii("dash")
28565
28619
  ]);
28566
28620
  var sidx = (muxer, referencedSize) => {
28567
- let duration = muxer.maxWrittenEndTimestamp - muxer.minWrittenTimestamp;
28621
+ const earliestPresentationTime = Math.max(0, muxer.minWrittenTimestamp);
28622
+ let duration = Math.max(0, muxer.maxWrittenEndTimestamp - earliestPresentationTime);
28568
28623
  if (!Number.isFinite(duration)) {
28569
28624
  duration = 0;
28570
28625
  }
@@ -28573,7 +28628,7 @@ var Mediabunny = (() => {
28573
28628
  // Reference ID
28574
28629
  u32(GLOBAL_TIMESCALE),
28575
28630
  // Timescale
28576
- u64(intoTimescale(muxer.minWrittenTimestamp, GLOBAL_TIMESCALE)),
28631
+ u64(intoTimescale(earliestPresentationTime, GLOBAL_TIMESCALE)),
28577
28632
  // Earliest presentation time
28578
28633
  u64(0),
28579
28634
  // First offset
@@ -28602,7 +28657,13 @@ var Mediabunny = (() => {
28602
28657
  var mvhd = (creationTime, trackDatas) => {
28603
28658
  const duration = Math.max(
28604
28659
  0,
28605
- ...trackDatas.map((trackData) => intoTimescale(presentationSpan(trackData), GLOBAL_TIMESCALE) + intoTimescale(trackData.startTimestampOffset ?? 0, GLOBAL_TIMESCALE))
28660
+ ...trackDatas.map((trackData) => (
28661
+ // Round separately to match the edit list
28662
+ Math.max(
28663
+ 0,
28664
+ intoTimescale(presentationSpan(trackData), GLOBAL_TIMESCALE) + intoTimescale(trackData.startTimestampOffset ?? 0, GLOBAL_TIMESCALE)
28665
+ )
28666
+ ))
28606
28667
  );
28607
28668
  const nextTrackId = Math.max(0, ...trackDatas.map((x) => x.track.id)) + 1;
28608
28669
  const needsU64 = !isU32(creationTime) || !isU32(duration);
@@ -28652,10 +28713,10 @@ var Mediabunny = (() => {
28652
28713
  };
28653
28714
  var trak = (trackData, creationTime) => {
28654
28715
  const trackMetadata = getTrackMetadata(trackData);
28655
- const needsEditList = trackData.startTimestampOffset !== null && trackData.startTimestampOffset > 0;
28716
+ const needsEditList = trackData.startTimestampOffset !== null && trackData.startTimestampOffset !== 0;
28656
28717
  return box("trak", void 0, [
28657
28718
  tkhd(trackData, creationTime),
28658
- needsEditList ? edts(trackData, trackData.startTimestampOffset) : null,
28719
+ needsEditList ? edts(trackData) : null,
28659
28720
  mdia(trackData, creationTime),
28660
28721
  trackMetadata.name !== void 0 ? box("udta", void 0, [
28661
28722
  box("name", [
@@ -28666,7 +28727,10 @@ var Mediabunny = (() => {
28666
28727
  ]);
28667
28728
  };
28668
28729
  var tkhd = (trackData, creationTime) => {
28669
- const durationInGlobalTimescale = intoTimescale(presentationSpan(trackData), GLOBAL_TIMESCALE) + intoTimescale(trackData.startTimestampOffset ?? 0, GLOBAL_TIMESCALE);
28730
+ const durationInGlobalTimescale = Math.max(
28731
+ 0,
28732
+ intoTimescale(presentationSpan(trackData), GLOBAL_TIMESCALE) + intoTimescale(trackData.startTimestampOffset ?? 0, GLOBAL_TIMESCALE)
28733
+ );
28670
28734
  const needsU64 = !isU32(creationTime) || !isU32(durationInGlobalTimescale);
28671
28735
  const u32OrU64 = needsU64 ? u64 : u32;
28672
28736
  let matrix;
@@ -28710,32 +28774,58 @@ var Mediabunny = (() => {
28710
28774
  // Track height
28711
28775
  ]);
28712
28776
  };
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
- ]);
28777
+ var edts = (trackData) => {
28778
+ const offset = trackData.startTimestampOffset;
28779
+ assert(offset !== null);
28780
+ if (offset > 0) {
28781
+ const startOffset = intoTimescale(offset, GLOBAL_TIMESCALE);
28782
+ const mediaDuration = intoTimescale(presentationSpan(trackData), GLOBAL_TIMESCALE);
28783
+ const needs64Bits = !isU32(startOffset) || !isU32(mediaDuration);
28784
+ const u32OrU64 = needs64Bits ? u64 : u32;
28785
+ const i32OrI64 = needs64Bits ? i64 : i32;
28786
+ return box("edts", void 0, [
28787
+ fullBox("elst", needs64Bits ? 1 : 0, 0, [
28788
+ u32(2),
28789
+ // Entry count
28790
+ // #1
28791
+ u32OrU64(startOffset),
28792
+ // Segment duration
28793
+ i32OrI64(-1),
28794
+ // Media time
28795
+ fixed_16_16(1),
28796
+ // Media rate
28797
+ // #2
28798
+ u32OrU64(mediaDuration),
28799
+ // Segment duration
28800
+ i32OrI64(0),
28801
+ // Media time
28802
+ fixed_16_16(1)
28803
+ // Media rate
28804
+ ])
28805
+ ]);
28806
+ } else {
28807
+ const mediaTime = intoTimescale(-offset, trackData.timescale);
28808
+ const mediaDuration = Math.max(
28809
+ 0,
28810
+ intoTimescale(presentationSpan(trackData), GLOBAL_TIMESCALE) + intoTimescale(offset, GLOBAL_TIMESCALE)
28811
+ );
28812
+ const needs64Bits = !isI32(mediaTime) || !isU32(mediaDuration);
28813
+ const u32OrU64 = needs64Bits ? u64 : u32;
28814
+ const i32OrI64 = needs64Bits ? i64 : i32;
28815
+ return box("edts", void 0, [
28816
+ fullBox("elst", needs64Bits ? 1 : 0, 0, [
28817
+ u32(1),
28818
+ // Entry count
28819
+ // #1
28820
+ u32OrU64(mediaDuration),
28821
+ // Segment duration
28822
+ i32OrI64(mediaTime),
28823
+ // Media time
28824
+ fixed_16_16(1)
28825
+ // Media rate
28826
+ ])
28827
+ ]);
28828
+ }
28739
28829
  };
28740
28830
  var mdia = (trackData, creationTime) => box("mdia", void 0, [
28741
28831
  mdhd(trackData, creationTime),
@@ -31044,7 +31134,7 @@ var Mediabunny = (() => {
31044
31134
  currentChunk: null,
31045
31135
  compactlyCodedChunkTable: [],
31046
31136
  closed: false,
31047
- lastCueEndTimestamp: 0,
31137
+ lastCueEndTimestamp: null,
31048
31138
  cueQueue: [],
31049
31139
  nextSourceId: 0,
31050
31140
  cueToSourceId: /* @__PURE__ */ new WeakMap()
@@ -31176,6 +31266,7 @@ var Mediabunny = (() => {
31176
31266
  }
31177
31267
  async processWebVTTCues(trackData, until) {
31178
31268
  while (trackData.cueQueue.length > 0) {
31269
+ trackData.lastCueEndTimestamp ??= Math.min(0, trackData.cueQueue[0].timestamp);
31179
31270
  const timestamps = /* @__PURE__ */ new Set([]);
31180
31271
  for (const cue of trackData.cueQueue) {
31181
31272
  assert(cue.timestamp <= until);
@@ -31259,9 +31350,8 @@ var Mediabunny = (() => {
31259
31350
  return;
31260
31351
  }
31261
31352
  if (trackData.type === "audio" && trackData.info.requiresPcmTransformation) {
31262
- if (!this.isFragmented) {
31263
- trackData.startTimestampOffset ??= trackData.timestampProcessingQueue[0].timestamp;
31264
- }
31353
+ assert(!this.isFragmented);
31354
+ trackData.startTimestampOffset ??= trackData.timestampProcessingQueue[0].timestamp;
31265
31355
  let totalDuration = 0;
31266
31356
  for (let i = 0; i < trackData.timestampProcessingQueue.length; i++) {
31267
31357
  const sample = trackData.timestampProcessingQueue[i];
@@ -31281,7 +31371,9 @@ var Mediabunny = (() => {
31281
31371
  return;
31282
31372
  }
31283
31373
  const sortedTimestamps = trackData.timestampProcessingQueue.map((x) => x.timestamp).sort((a, b) => a - b);
31284
- if (!this.isFragmented) {
31374
+ if (this.isFragmented) {
31375
+ trackData.startTimestampOffset ??= Math.min(sortedTimestamps[0], 0);
31376
+ } else {
31285
31377
  trackData.startTimestampOffset ??= sortedTimestamps[0];
31286
31378
  }
31287
31379
  for (let i = 0; i < trackData.timestampProcessingQueue.length; i++) {
@@ -31532,11 +31624,16 @@ var Mediabunny = (() => {
31532
31624
  let fragmentStartTimestamp = Infinity;
31533
31625
  for (let i = 0; i < tracksInFragment.length; i++) {
31534
31626
  const trackData = tracksInFragment[i];
31627
+ assert(trackData.currentChunk);
31628
+ assert(trackData.startTimestampOffset !== null);
31535
31629
  trackData.currentChunk.offset = currentPos;
31536
31630
  trackData.currentChunk.moofOffset = moofOffset;
31537
31631
  trackData.currentChunk.trafIndex = i;
31632
+ trackData.currentChunk.startTimestamp -= trackData.startTimestampOffset;
31538
31633
  for (const sample of trackData.currentChunk.samples) {
31539
31634
  currentPos += sample.size;
31635
+ sample.timestamp -= trackData.startTimestampOffset;
31636
+ sample.decodeTimestamp -= trackData.startTimestampOffset;
31540
31637
  }
31541
31638
  fragmentStartTimestamp = Math.min(fragmentStartTimestamp, trackData.currentChunk.startTimestamp);
31542
31639
  }
@@ -31838,6 +31935,7 @@ var Mediabunny = (() => {
31838
31935
  this.trackDatasInCurrentCluster = /* @__PURE__ */ new Map();
31839
31936
  this.startTimestamp = Infinity;
31840
31937
  this.endTimestamp = -Infinity;
31938
+ this.warnedAboutTooNegativeTimestamp = false;
31841
31939
  this.format = format;
31842
31940
  }
31843
31941
  async start() {
@@ -32635,6 +32733,13 @@ ${cue.notes ?? ""}`;
32635
32733
  }
32636
32734
  const relativeTimestamp = msTimestamp - this.currentClusterStartMsTimestamp;
32637
32735
  if (relativeTimestamp < MIN_CLUSTER_TIMESTAMP_MS) {
32736
+ if (!this.warnedAboutTooNegativeTimestamp) {
32737
+ const formatName = this.format instanceof WebMOutputFormat ? "WebM" : "Matroska";
32738
+ Logging._warn(
32739
+ `Packets had to be discarded because their timestamp is too negative to represent in ${formatName}.`
32740
+ );
32741
+ this.warnedAboutTooNegativeTimestamp = true;
32742
+ }
32638
32743
  return;
32639
32744
  }
32640
32745
  const prelude = new Uint8Array(4);
@@ -32683,6 +32788,7 @@ ${cue.notes ?? ""}`;
32683
32788
  }
32684
32789
  /** Creates a new Cluster element to contain media chunks. */
32685
32790
  createNewCluster(msTimestamp) {
32791
+ msTimestamp = Math.max(0, msTimestamp);
32686
32792
  if (this.currentCluster) {
32687
32793
  this.finalizeCurrentCluster();
32688
32794
  }
@@ -32727,7 +32833,8 @@ ${cue.notes ?? ""}`;
32727
32833
  for (const [msTimestamp, trackDatas] of groupedAndSortedByTimestamp) {
32728
32834
  assert(this.cues);
32729
32835
  this.cues.data.push({ id: 187 /* CuePoint */, data: [
32730
- { id: 179 /* CueTime */, data: msTimestamp },
32836
+ { id: 179 /* CueTime */, data: Math.max(0, msTimestamp) },
32837
+ // CueTime is unsigned
32731
32838
  // Create CueTrackPositions for each track that starts at this timestamp
32732
32839
  ...trackDatas.map((trackData) => {
32733
32840
  return { id: 183 /* CueTrackPositions */, data: [
@@ -33786,24 +33893,26 @@ ${cue.notes ?? ""}`;
33786
33893
  pesView.setUint8(6, 132);
33787
33894
  pesView.setUint8(7, includeDts ? 192 : 128);
33788
33895
  pesView.setUint8(8, headerDataLength);
33789
- const pts = Math.round(queuedPacket.presentationTimestamp * TIMESCALE);
33896
+ const unwrappedPts = Math.round(queuedPacket.presentationTimestamp * TIMESCALE);
33897
+ const pts = modEuclid(unwrappedPts, TIMESTAMP_MODULUS);
33790
33898
  ptsDtsBitstream.pos = 0;
33791
33899
  ptsDtsBitstream.writeBits(4, includeDts ? 3 : 2);
33792
- ptsDtsBitstream.writeBits(3, pts >>> 30 & 7);
33900
+ ptsDtsBitstream.writeBits(3, Math.floor(pts / 2 ** 30));
33793
33901
  ptsDtsBitstream.writeBits(1, 1);
33794
- ptsDtsBitstream.writeBits(15, pts >>> 15 & 32767);
33902
+ ptsDtsBitstream.writeBits(15, Math.floor(pts / 2 ** 15) % 2 ** 15);
33795
33903
  ptsDtsBitstream.writeBits(1, 1);
33796
- ptsDtsBitstream.writeBits(15, pts & 32767);
33904
+ ptsDtsBitstream.writeBits(15, pts % 2 ** 15);
33797
33905
  ptsDtsBitstream.writeBits(1, 1);
33798
33906
  if (includeDts) {
33799
33907
  assert(queuedPacket.decodeTimestamp !== null);
33800
- const dts = Math.round(queuedPacket.decodeTimestamp * TIMESCALE);
33908
+ const unwrappedDts = Math.round(queuedPacket.decodeTimestamp * TIMESCALE);
33909
+ const dts = modEuclid(unwrappedDts, TIMESTAMP_MODULUS);
33801
33910
  ptsDtsBitstream.writeBits(4, 1);
33802
- ptsDtsBitstream.writeBits(3, dts >>> 30 & 7);
33911
+ ptsDtsBitstream.writeBits(3, Math.floor(dts / 2 ** 30));
33803
33912
  ptsDtsBitstream.writeBits(1, 1);
33804
- ptsDtsBitstream.writeBits(15, dts >>> 15 & 32767);
33913
+ ptsDtsBitstream.writeBits(15, Math.floor(dts / 2 ** 15) % 2 ** 15);
33805
33914
  ptsDtsBitstream.writeBits(1, 1);
33806
- ptsDtsBitstream.writeBits(15, dts & 32767);
33915
+ ptsDtsBitstream.writeBits(15, dts % 2 ** 15);
33807
33916
  ptsDtsBitstream.writeBits(1, 1);
33808
33917
  }
33809
33918
  const totalLength = pesHeaderBuffer.length + queuedPacket.data.length;
@@ -35390,8 +35499,8 @@ ${cue.notes ?? ""}`;
35390
35499
  * to respect writer and encoder backpressure.
35391
35500
  */
35392
35501
  add(timestamp, duration = 0, encodeOptions) {
35393
- if (!Number.isFinite(timestamp) || timestamp < 0) {
35394
- throw new TypeError("timestamp must be a non-negative number.");
35502
+ if (!Number.isFinite(timestamp)) {
35503
+ throw new TypeError("timestamp must be a finite number.");
35395
35504
  }
35396
35505
  if (!Number.isFinite(duration) || duration < 0) {
35397
35506
  throw new TypeError("duration must be a non-negative number.");
@@ -36236,19 +36345,24 @@ ${cue.notes ?? ""}`;
36236
36345
  var AudioBufferSource = class extends AudioSource {
36237
36346
  /**
36238
36347
  * Creates a new {@link AudioBufferSource} whose `AudioBuffer` instances are encoded according to the specified
36239
- * {@link AudioEncodingConfig}.
36348
+ * {@link AudioEncodingConfig} and {@link AudioBufferSourceOptions}.
36240
36349
  */
36241
- constructor(encodingConfig) {
36350
+ constructor(encodingConfig, options = {}) {
36242
36351
  validateAudioEncodingConfig(encodingConfig);
36352
+ if (typeof options !== "object" || !options) {
36353
+ throw new TypeError("options must be an object.");
36354
+ }
36355
+ if (options.startTimestamp !== void 0 && !Number.isFinite(options.startTimestamp)) {
36356
+ throw new TypeError("options.startTimestamp, when provided, must be a finite number.");
36357
+ }
36243
36358
  super(encodingConfig.codec);
36244
- /** @internal */
36245
- this._accumulatedTime = 0;
36246
36359
  this._encoder = new AudioEncoderWrapper(this, encodingConfig);
36360
+ this._accumulatedTime = options.startTimestamp ?? 0;
36247
36361
  }
36248
36362
  /**
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.
36363
+ * Converts an AudioBuffer to audio samples, encodes them and adds them to the output. The first `AudioBuffer` will
36364
+ * be played at the configured start timestamp (the default is 0), and each subsequent `AudioBuffer` will be placed
36365
+ * directly after the previous one.
36252
36366
  *
36253
36367
  * @returns A Promise that resolves once the output is ready to receive more samples. You should await this Promise
36254
36368
  * to respect writer and encoder backpressure.
@@ -37830,6 +37944,9 @@ ${cue.notes ?? ""}`;
37830
37944
  get supportsTimestampedMediaData() {
37831
37945
  return true;
37832
37946
  }
37947
+ get negativeTimestampSupport() {
37948
+ return "full";
37949
+ }
37833
37950
  /** @internal */
37834
37951
  _createMuxer(output) {
37835
37952
  return new IsobmffMuxer2(output, this);
@@ -38004,6 +38121,9 @@ ${cue.notes ?? ""}`;
38004
38121
  get supportsTimestampedMediaData() {
38005
38122
  return true;
38006
38123
  }
38124
+ get negativeTimestampSupport() {
38125
+ return "prefer-non-negative";
38126
+ }
38007
38127
  };
38008
38128
  var WebMOutputFormat = class extends MkvOutputFormat2 {
38009
38129
  /** Creates a new {@link WebMOutputFormat} configured with the specified `options`. */
@@ -38081,6 +38201,9 @@ ${cue.notes ?? ""}`;
38081
38201
  get supportsTimestampedMediaData() {
38082
38202
  return false;
38083
38203
  }
38204
+ get negativeTimestampSupport() {
38205
+ return null;
38206
+ }
38084
38207
  };
38085
38208
  var WavOutputFormat = class extends OutputFormat {
38086
38209
  /** Creates a new {@link WavOutputFormat} configured with the specified `options`. */
@@ -38135,6 +38258,9 @@ ${cue.notes ?? ""}`;
38135
38258
  get supportsTimestampedMediaData() {
38136
38259
  return false;
38137
38260
  }
38261
+ get negativeTimestampSupport() {
38262
+ return null;
38263
+ }
38138
38264
  };
38139
38265
  var OggOutputFormat = class extends OutputFormat {
38140
38266
  /** Creates a new {@link OggOutputFormat} configured with the specified `options`. */
@@ -38185,6 +38311,9 @@ ${cue.notes ?? ""}`;
38185
38311
  get supportsTimestampedMediaData() {
38186
38312
  return false;
38187
38313
  }
38314
+ get negativeTimestampSupport() {
38315
+ return null;
38316
+ }
38188
38317
  };
38189
38318
  var AdtsOutputFormat = class extends OutputFormat {
38190
38319
  /** Creates a new {@link AdtsOutputFormat} configured with the specified `options`. */
@@ -38229,6 +38358,9 @@ ${cue.notes ?? ""}`;
38229
38358
  get supportsTimestampedMediaData() {
38230
38359
  return false;
38231
38360
  }
38361
+ get negativeTimestampSupport() {
38362
+ return null;
38363
+ }
38232
38364
  };
38233
38365
  var FlacOutputFormat = class extends OutputFormat {
38234
38366
  /** Creates a new {@link FlacOutputFormat} configured with the specified `options`. */
@@ -38273,6 +38405,9 @@ ${cue.notes ?? ""}`;
38273
38405
  get supportsTimestampedMediaData() {
38274
38406
  return false;
38275
38407
  }
38408
+ get negativeTimestampSupport() {
38409
+ return null;
38410
+ }
38276
38411
  };
38277
38412
  var MpegTsOutputFormat = class extends OutputFormat {
38278
38413
  /** Creates a new {@link MpegTsOutputFormat} configured with the specified `options`. */
@@ -38323,6 +38458,9 @@ ${cue.notes ?? ""}`;
38323
38458
  get supportsTimestampedMediaData() {
38324
38459
  return true;
38325
38460
  }
38461
+ get negativeTimestampSupport() {
38462
+ return "prefer-non-negative";
38463
+ }
38326
38464
  };
38327
38465
  var HlsOutputFormat = class extends OutputFormat {
38328
38466
  /** Creates a new {@link HlsOutputFormat} configured with the specified `options`. */
@@ -38416,6 +38554,19 @@ ${cue.notes ?? ""}`;
38416
38554
  get supportsTimestampedMediaData() {
38417
38555
  return true;
38418
38556
  }
38557
+ get negativeTimestampSupport() {
38558
+ const formats = toArray(this._options.segmentFormat);
38559
+ if (formats.some((format) => format.negativeTimestampSupport === "none")) {
38560
+ return "none";
38561
+ }
38562
+ if (formats.some((format) => format.negativeTimestampSupport === "prefer-non-negative")) {
38563
+ return "prefer-non-negative";
38564
+ }
38565
+ if (formats.some((format) => format.negativeTimestampSupport === "full")) {
38566
+ return "full";
38567
+ }
38568
+ return null;
38569
+ }
38419
38570
  /** @internal */
38420
38571
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
38421
38572
  _codecUnsupportedHint(codec) {
@@ -39154,15 +39305,16 @@ ${cue.notes ?? ""}`;
39154
39305
  var Conversion = class _Conversion {
39155
39306
  /** Creates a new Conversion instance (duh). */
39156
39307
  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";
39308
+ /** @internal */
39309
+ this._state = "idle";
39310
+ /** @internal */
39311
+ this._timestampOffset = 0;
39312
+ /** @internal */
39313
+ this._timestampOffsetAdjusted = false;
39314
+ /** @internal */
39315
+ this._copyTimestampPossible = /* @__PURE__ */ new Map();
39316
+ /** @internal */
39317
+ this._copyStartPackets = /* @__PURE__ */ new Map();
39166
39318
  /** @internal */
39167
39319
  this._nextOutputTrackId = 0;
39168
39320
  /** @internal */
@@ -39233,6 +39385,22 @@ ${cue.notes ?? ""}`;
39233
39385
  if (options.composable !== void 0 && typeof options.composable !== "boolean") {
39234
39386
  throw new TypeError("options.composable, when provided, must be a boolean.");
39235
39387
  }
39388
+ if (options.copy !== void 0 && options.copy !== false) {
39389
+ if (!options.copy || typeof options.copy !== "object") {
39390
+ throw new TypeError("options.copy, when provided, must be an object or false.");
39391
+ }
39392
+ if (options.copy.mode !== void 0 && !["forced", "preferred"].includes(options.copy.mode)) {
39393
+ throw new TypeError("options.copy.mode, when provided, must be 'forced' or 'preferred'.");
39394
+ }
39395
+ if (options.copy.shiftTolerance !== void 0 && (!isNumber(options.copy.shiftTolerance) || options.copy.shiftTolerance < 0)) {
39396
+ throw new TypeError("options.copy.shiftTolerance, when provided, must be a non-negative number.");
39397
+ }
39398
+ if (options.copy.boundaryPolicy !== void 0 && !["expand", "shrink"].includes(options.copy.boundaryPolicy)) {
39399
+ throw new TypeError(
39400
+ "options.copy.boundaryPolicy, when provided, must be 'expand' or 'shrink'."
39401
+ );
39402
+ }
39403
+ }
39236
39404
  const composable = options.composable ?? false;
39237
39405
  if (!composable) {
39238
39406
  if (options.output.tracks.length > 0 || Object.keys(options.output._metadataTags).length > 0 || options.output.state !== "pending") {
@@ -39274,8 +39442,8 @@ ${cue.notes ?? ""}`;
39274
39442
  if (options.trim?.start !== void 0 && !Number.isFinite(options.trim.start)) {
39275
39443
  throw new TypeError("options.trim.start, when provided, must be a finite number.");
39276
39444
  }
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.");
39445
+ if (options.trim?.end !== void 0 && !isNumber(options.trim.end)) {
39446
+ throw new TypeError("options.trim.end, when provided, must be a number.");
39279
39447
  }
39280
39448
  if (options.trim?.start !== void 0 && options.trim.end !== void 0 && options.trim.start >= options.trim.end) {
39281
39449
  throw new TypeError("options.trim.start must be less than options.trim.end.");
@@ -39290,10 +39458,24 @@ ${cue.notes ?? ""}`;
39290
39458
  throw new TypeError("options.showWarnings, when provided, must be a boolean.");
39291
39459
  }
39292
39460
  this._options = options;
39461
+ this._copyMode = options.copy === false ? false : options.copy?.mode ?? "preferred";
39462
+ this._copyTimestampShiftTolerance = options.copy === false ? 0 : options.copy?.shiftTolerance ?? 0;
39463
+ this._copyBoundaryPolicy = options.copy === false ? "expand" : options.copy?.boundaryPolicy ?? "expand";
39293
39464
  this._composable = composable;
39294
39465
  this.input = options.input;
39295
39466
  this.output = options.output;
39296
39467
  }
39468
+ /**
39469
+ * The current state of the conversion.
39470
+ *
39471
+ * - `'idle'`: The conversion is not currently executing and isn't done; `execute` can be called.
39472
+ * - `'executing'`: A call to `execute` is currently running.
39473
+ * - `'canceled'`: The conversion has been canceled and can no longer be executed.
39474
+ * - `'done'`: The conversion has run to completion. Subsequent calls to `execute` do nothing.
39475
+ */
39476
+ get state() {
39477
+ return this._state;
39478
+ }
39297
39479
  /** Initializes a new conversion process without starting the conversion. */
39298
39480
  static async init(options) {
39299
39481
  const conversion = new _Conversion(options);
@@ -39400,6 +39582,7 @@ ${cue.notes ?? ""}`;
39400
39582
  );
39401
39583
  }
39402
39584
  this._endTimestamp = Math.max(this._options.trim?.end ?? Infinity, this._startTimestamp);
39585
+ this._timestampOffset = -this._startTimestamp;
39403
39586
  for (let i = 0; i < filteredTracks.length; i++) {
39404
39587
  const track = filteredTracks[i];
39405
39588
  const options = filteredTrackOptions[i];
@@ -39578,13 +39761,13 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
39578
39761
  "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
39762
  );
39580
39763
  }
39581
- if (this.state === "executing") {
39764
+ if (this._state === "executing") {
39582
39765
  throw new Error("Cannot call execute() while a previous call to execute() is still running.");
39583
39766
  }
39584
- if (this.state === "canceled") {
39767
+ if (this._state === "canceled") {
39585
39768
  throw new ConversionCanceledError();
39586
39769
  }
39587
- if (this.state === "done") {
39770
+ if (this._state === "done") {
39588
39771
  return;
39589
39772
  }
39590
39773
  if (this._composable && this.output.state === "pending") {
@@ -39592,11 +39775,11 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
39592
39775
  "A composable conversion requires the output to be started. Call start() on the output before executing the conversion."
39593
39776
  );
39594
39777
  }
39595
- this.state = "executing";
39778
+ this._state = "executing";
39596
39779
  this._executionUntil = options.until ?? Infinity;
39597
39780
  this._pauseRequested = options.pauseSignal?.aborted ?? false;
39598
39781
  const onPause = () => {
39599
- if (this.state !== "executing") {
39782
+ if (this._state !== "executing") {
39600
39783
  return;
39601
39784
  }
39602
39785
  this._pauseRequested = true;
@@ -39646,18 +39829,18 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
39646
39829
  try {
39647
39830
  await Promise.all(this._trackPumps.map((x) => x.resolvers.promise));
39648
39831
  } catch (error) {
39649
- if (this.state !== "canceled") {
39832
+ if (this._state !== "canceled") {
39650
39833
  void this.cancel();
39651
39834
  }
39652
39835
  throw error;
39653
39836
  } finally {
39654
39837
  options.pauseSignal?.removeEventListener("abort", onPause);
39655
39838
  }
39656
- if (this.state === "canceled") {
39839
+ if (this._state === "canceled") {
39657
39840
  throw new ConversionCanceledError();
39658
39841
  }
39659
39842
  const isDone = this._trackPumps.every((x) => x.done);
39660
- this.state = isDone ? "done" : "idle";
39843
+ this._state = isDone ? "done" : "idle";
39661
39844
  if (isDone) {
39662
39845
  if (!this._composable) {
39663
39846
  await this.output.finalize();
@@ -39673,14 +39856,14 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
39673
39856
  * Does nothing if the conversion is already complete.
39674
39857
  */
39675
39858
  async cancel() {
39676
- if (this.state === "done") {
39859
+ if (this._state === "done") {
39677
39860
  return;
39678
39861
  }
39679
- if (this.state === "canceled") {
39862
+ if (this._state === "canceled") {
39680
39863
  Logging._warn("Conversion already canceled.");
39681
39864
  return;
39682
39865
  }
39683
- this.state = "canceled";
39866
+ this._state = "canceled";
39684
39867
  for (const pump of this._trackPumps) {
39685
39868
  pump.wake?.();
39686
39869
  }
@@ -39726,10 +39909,63 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
39726
39909
  width = ceilToMultipleOfTwo(trackOptions.width);
39727
39910
  height = ceilToMultipleOfTwo(trackOptions.height);
39728
39911
  }
39729
- const firstTimestamp = await track.getFirstTimestamp();
39730
39912
  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
39913
  const alpha = trackOptions.alpha ?? "discard";
39914
+ 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;
39915
+ let copyStartPacket = null;
39916
+ if (!needsTranscode) {
39917
+ const sink = new EncodedPacketSink(track);
39918
+ let startPacket = await sink.getKeyPacket(this._startTimestamp, { verifyKeyPackets: true }) ?? await sink.getFirstKeyPacket({ verifyKeyPackets: true });
39919
+ if (startPacket && startPacket.timestamp < this._startTimestamp && startPacket.timestamp + startPacket.duration <= this._startTimestamp && this._copyBoundaryPolicy === "shrink") {
39920
+ startPacket = await sink.getNextKeyPacket(startPacket, { verifyKeyPackets: true });
39921
+ }
39922
+ copyStartPacket = startPacket;
39923
+ if (startPacket) {
39924
+ const effectiveStartTimestamp = this._copyBoundaryPolicy === "shrink" ? Math.max(startPacket.timestamp, this._startTimestamp) : startPacket.timestamp;
39925
+ if (!this.output.format.supportsTimestampedMediaData) {
39926
+ if (this._timestampOffsetAdjusted) {
39927
+ const isValid = effectiveStartTimestamp + this._timestampOffset === 0;
39928
+ if (!isValid) {
39929
+ needsTranscode = true;
39930
+ }
39931
+ } else {
39932
+ const correction = clamp(
39933
+ this._startTimestamp - effectiveStartTimestamp,
39934
+ -this._copyTimestampShiftTolerance,
39935
+ this._copyTimestampShiftTolerance
39936
+ );
39937
+ const shiftedStartTimestamp = effectiveStartTimestamp + correction;
39938
+ const isValid = shiftedStartTimestamp === this._startTimestamp;
39939
+ if (isValid) {
39940
+ this._timestampOffset = -this._startTimestamp + correction;
39941
+ this._timestampOffsetAdjusted = true;
39942
+ } else {
39943
+ needsTranscode = true;
39944
+ }
39945
+ }
39946
+ } else if (this.output.format.negativeTimestampSupport !== "full" && effectiveStartTimestamp < this._startTimestamp) {
39947
+ const correction = Math.min(
39948
+ this._startTimestamp - effectiveStartTimestamp,
39949
+ this._copyTimestampShiftTolerance
39950
+ );
39951
+ const shiftedStartTimestamp = effectiveStartTimestamp + correction;
39952
+ const isValid = shiftedStartTimestamp >= this._startTimestamp || this.output.format.negativeTimestampSupport === "prefer-non-negative" && this._copyMode === "forced";
39953
+ if (isValid) {
39954
+ this._timestampOffset = Math.max(this._timestampOffset, -this._startTimestamp + correction);
39955
+ } else {
39956
+ needsTranscode = true;
39957
+ }
39958
+ }
39959
+ }
39960
+ }
39961
+ if (needsTranscode && this._copyMode === "forced") {
39962
+ this.discardedTracks.push({
39963
+ track,
39964
+ reason: "cannot_copy",
39965
+ trackOptions
39966
+ });
39967
+ return;
39968
+ }
39733
39969
  if (!needsTranscode) {
39734
39970
  const source = new EncodedVideoPacketSource(sourceCodec);
39735
39971
  videoSource = source;
@@ -39737,18 +39973,51 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
39737
39973
  const sink = new EncodedPacketSink(track);
39738
39974
  const decoderConfig = await track.getDecoderConfig();
39739
39975
  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") {
39976
+ if (copyStartPacket) for await (const packet of sink.packets(
39977
+ copyStartPacket,
39978
+ void 0,
39979
+ { verifyKeyPackets: true }
39980
+ )) {
39981
+ if (this._state === "canceled") {
39742
39982
  break;
39743
39983
  }
39744
39984
  if (packet.timestamp >= this._endTimestamp) {
39745
- break;
39985
+ if (this._copyBoundaryPolicy === "shrink") {
39986
+ break;
39987
+ } else {
39988
+ let current = packet;
39989
+ let found = false;
39990
+ const lookahead = 6;
39991
+ for (let i = 0; i < lookahead; i++) {
39992
+ const next = await sink.getNextPacket(current, { metadataOnly: true });
39993
+ if (!next) {
39994
+ break;
39995
+ }
39996
+ if (next.timestamp < this._endTimestamp) {
39997
+ found = true;
39998
+ break;
39999
+ }
40000
+ current = next;
40001
+ }
40002
+ if (!found) {
40003
+ break;
40004
+ }
40005
+ }
39746
40006
  }
40007
+ let packetStartTimestamp = packet.timestamp;
40008
+ let packetEndTimestamp = packet.timestamp + packet.duration;
40009
+ if (this._copyBoundaryPolicy === "shrink") {
40010
+ packetStartTimestamp = Math.max(packetStartTimestamp, this._startTimestamp);
40011
+ packetEndTimestamp = Math.min(packetEndTimestamp, this._endTimestamp);
40012
+ packetEndTimestamp = Math.max(packetEndTimestamp, packetStartTimestamp);
40013
+ }
40014
+ packetStartTimestamp += this._timestampOffset;
40015
+ packetEndTimestamp += this._timestampOffset;
39747
40016
  const modifiedPacket = packet.clone({
39748
- timestamp: packet.timestamp - this._startTimestamp,
40017
+ timestamp: packetStartTimestamp,
40018
+ duration: packetEndTimestamp - packetStartTimestamp,
39749
40019
  sideData: alpha === "discard" ? {} : packet.sideData
39750
40020
  });
39751
- assert(modifiedPacket.timestamp >= 0);
39752
40021
  this._reportProgress(outputTrackId, modifiedPacket.timestamp + modifiedPacket.duration);
39753
40022
  await source.add(modifiedPacket, meta);
39754
40023
  if (this._synchronizer.shouldWait(outputTrackId, modifiedPacket.timestamp)) {
@@ -39809,7 +40078,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
39809
40078
  tempOutput.addVideoTrack(tempSource);
39810
40079
  await tempOutput.start();
39811
40080
  const sink = new VideoSampleSink(track);
39812
- const firstSample = __using(_stack, await sink.getSample(firstTimestamp));
40081
+ const firstSample = __using(_stack, await sink.getSample(await track.getFirstTimestamp()));
39813
40082
  if (firstSample) {
39814
40083
  try {
39815
40084
  await tempSource.add(firstSample);
@@ -39860,11 +40129,16 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
39860
40129
  var _stack2 = [];
39861
40130
  try {
39862
40131
  const sample = __using(_stack2, _sample);
39863
- if (this.state === "canceled") {
40132
+ if (this._state === "canceled") {
39864
40133
  break;
39865
40134
  }
39866
- const adjustedSampleTimestamp = Math.max(sample.timestamp - this._startTimestamp, 0);
39867
- sample.setTimestamp(adjustedSampleTimestamp);
40135
+ const clampedStartTimestamp = Math.max(this._startTimestamp, sample.timestamp);
40136
+ const clampedEndTimestamp = Math.min(this._endTimestamp, sample.timestamp + sample.duration);
40137
+ if (clampedStartTimestamp >= clampedEndTimestamp) {
40138
+ continue;
40139
+ }
40140
+ sample.setTimestamp(clampedStartTimestamp + this._timestampOffset);
40141
+ sample.setDuration(clampedEndTimestamp - clampedStartTimestamp);
39868
40142
  this._reportProgress(outputTrackId, sample.timestamp + sample.duration);
39869
40143
  await source.add(sample);
39870
40144
  sample.close();
@@ -39889,12 +40163,14 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
39889
40163
  ownGroup = new OutputTrackGroup();
39890
40164
  }
39891
40165
  const videoTrackLanguageCode = await track.getLanguageCode();
40166
+ const trackName = await track.getName();
40167
+ const trackDisposition = await track.getDisposition();
39892
40168
  this.output.addVideoTrack(videoSource, {
39893
40169
  frameRate: trackOptions.frameRate,
39894
40170
  // TODO: This condition can be removed when all demuxers properly homogenize to BCP47 in v2
39895
40171
  languageCode: isIso639Dash2LanguageCode(videoTrackLanguageCode) ? videoTrackLanguageCode : void 0,
39896
- name: await track.getName() ?? void 0,
39897
- disposition: await track.getDisposition(),
40172
+ name: trackName ?? void 0,
40173
+ disposition: trackDisposition,
39898
40174
  rotation: outputTrackRotation,
39899
40175
  group: ownGroup ?? trackOptions.group
39900
40176
  });
@@ -39916,30 +40192,93 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
39916
40192
  let audioSource;
39917
40193
  const originalNumberOfChannels = await track.getNumberOfChannels();
39918
40194
  const originalSampleRate = await track.getSampleRate();
39919
- const firstTimestamp = await track.getFirstTimestamp();
39920
40195
  let numberOfChannels = trackOptions.numberOfChannels ?? originalNumberOfChannels;
39921
40196
  let sampleRate = trackOptions.sampleRate ?? originalSampleRate;
39922
- const needsTrimming = firstTimestamp < this._startTimestamp;
39923
- let needsPadding = firstTimestamp > this._startTimestamp && !this.output.format.supportsTimestampedMediaData;
39924
40197
  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) {
40198
+ 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;
40199
+ let copyStartPacket = null;
40200
+ if (!needsTranscode) {
40201
+ const sink = new EncodedPacketSink(track);
40202
+ let startPacket = await sink.getKeyPacket(this._startTimestamp) ?? await sink.getFirstKeyPacket();
40203
+ if (startPacket && (this._copyBoundaryPolicy === "shrink" && startPacket.timestamp < this._startTimestamp || this._copyBoundaryPolicy === "expand" && startPacket.timestamp + startPacket.duration <= this._startTimestamp)) {
40204
+ startPacket = await sink.getNextKeyPacket(startPacket);
40205
+ }
40206
+ const hasDecoderWarmup = NON_PCM_AUDIO_CODECS.includes(sourceCodec) && sourceCodec !== "flac";
40207
+ if (startPacket && this._copyBoundaryPolicy === "expand" && hasDecoderWarmup) {
40208
+ const previousPacket = await sink.getKeyPacket(
40209
+ startPacket.timestamp - 1 / await track.getTimeResolution()
40210
+ );
40211
+ if (previousPacket) {
40212
+ startPacket = previousPacket;
40213
+ }
40214
+ }
40215
+ copyStartPacket = startPacket;
40216
+ if (startPacket) {
40217
+ if (!this.output.format.supportsTimestampedMediaData) {
40218
+ if (this._timestampOffsetAdjusted) {
40219
+ const isValid = startPacket.timestamp + this._timestampOffset === 0;
40220
+ if (!isValid) {
40221
+ needsTranscode = true;
40222
+ }
40223
+ } else {
40224
+ const correction = clamp(
40225
+ this._startTimestamp - startPacket.timestamp,
40226
+ -this._copyTimestampShiftTolerance,
40227
+ this._copyTimestampShiftTolerance
40228
+ );
40229
+ const shiftedStartTimestamp = startPacket.timestamp + correction;
40230
+ const isValid = shiftedStartTimestamp === this._startTimestamp;
40231
+ if (isValid) {
40232
+ this._timestampOffset = -this._startTimestamp + correction;
40233
+ this._timestampOffsetAdjusted = true;
40234
+ } else {
40235
+ needsTranscode = true;
40236
+ }
40237
+ }
40238
+ } else if (this.output.format.negativeTimestampSupport !== "full" && startPacket.timestamp < this._startTimestamp) {
40239
+ const correction = Math.min(
40240
+ this._startTimestamp - startPacket.timestamp,
40241
+ this._copyTimestampShiftTolerance
40242
+ );
40243
+ const shiftedStartTimestamp = startPacket.timestamp + correction;
40244
+ const isValid = shiftedStartTimestamp >= this._startTimestamp || this.output.format.negativeTimestampSupport === "prefer-non-negative" && this._copyMode === "forced";
40245
+ if (isValid) {
40246
+ this._timestampOffset = Math.max(this._timestampOffset, -this._startTimestamp + correction);
40247
+ } else {
40248
+ needsTranscode = true;
40249
+ }
40250
+ }
40251
+ }
40252
+ }
40253
+ if (needsTranscode && this._copyMode === "forced") {
40254
+ this.discardedTracks.push({
40255
+ track,
40256
+ reason: "cannot_copy",
40257
+ trackOptions
40258
+ });
40259
+ return;
40260
+ }
40261
+ if (!needsTranscode) {
39926
40262
  const source = new EncodedAudioPacketSource(sourceCodec);
39927
40263
  audioSource = source;
39928
40264
  this._registerTrackPump(async (pump) => {
39929
40265
  const sink = new EncodedPacketSink(track);
39930
40266
  const decoderConfig = await track.getDecoderConfig();
39931
40267
  const meta = { decoderConfig: decoderConfig ?? void 0 };
39932
- for await (const packet of sink.packets()) {
39933
- if (this.state === "canceled") {
40268
+ if (copyStartPacket) for await (const packet of sink.packets(copyStartPacket)) {
40269
+ if (this._state === "canceled") {
39934
40270
  break;
39935
40271
  }
39936
40272
  if (packet.timestamp >= this._endTimestamp) {
39937
40273
  break;
39938
40274
  }
40275
+ if (this._copyBoundaryPolicy === "shrink" && packet.timestamp + packet.duration > this._endTimestamp) {
40276
+ break;
40277
+ }
39939
40278
  const modifiedPacket = packet.clone({
39940
- timestamp: packet.timestamp - this._startTimestamp
40279
+ timestamp: packet.timestamp + this._timestampOffset,
40280
+ duration: packet.duration
39941
40281
  });
39942
- assert(modifiedPacket.timestamp >= 0);
39943
40282
  this._reportProgress(outputTrackId, modifiedPacket.timestamp + modifiedPacket.duration);
39944
40283
  await source.add(modifiedPacket, meta);
39945
40284
  if (this._synchronizer.shouldWait(outputTrackId, modifiedPacket.timestamp)) {
@@ -40015,6 +40354,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
40015
40354
  const source = new AudioSampleSource(encodingConfig);
40016
40355
  audioSource = source;
40017
40356
  this._registerTrackPump(async (pump) => {
40357
+ let needsPadding = null;
40018
40358
  const sink = new AudioSampleSink(track);
40019
40359
  for await (var _sample of sink.samples(this._startTimestamp, this._endTimestamp)) {
40020
40360
  var _stack3 = [];
@@ -40022,13 +40362,42 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
40022
40362
  const sample = __using(_stack3, _sample);
40023
40363
  var _stack2 = [];
40024
40364
  try {
40025
- if (this.state === "canceled") {
40365
+ if (this._state === "canceled") {
40026
40366
  break;
40027
40367
  }
40368
+ let startFrame = 0;
40369
+ let endFrame = sample.numberOfFrames;
40370
+ if (sample.timestamp < this._startTimestamp) {
40371
+ startFrame = Math.round((this._startTimestamp - sample.timestamp) * sample.sampleRate);
40372
+ }
40373
+ if (sample.timestamp + sample.duration > this._endTimestamp) {
40374
+ endFrame = Math.round((this._endTimestamp - sample.timestamp) * sample.sampleRate);
40375
+ }
40376
+ if (startFrame >= endFrame) {
40377
+ sample.close();
40378
+ continue;
40379
+ }
40380
+ let finalSampleLet;
40381
+ if (startFrame > 0 || endFrame < sample.numberOfFrames) {
40382
+ const trimmedSample = sample.trim(startFrame, endFrame);
40383
+ sample.close();
40384
+ finalSampleLet = trimmedSample;
40385
+ if (trimmedSample.numberOfFrames === 0) {
40386
+ trimmedSample.close();
40387
+ continue;
40388
+ }
40389
+ } else {
40390
+ finalSampleLet = sample;
40391
+ }
40392
+ const finalSample = __using(_stack2, finalSampleLet);
40393
+ finalSample.setTimestamp(finalSample.timestamp + this._timestampOffset);
40394
+ if (needsPadding === null) {
40395
+ needsPadding = finalSample.timestamp > 0 && !this.output.format.supportsTimestampedMediaData;
40396
+ }
40028
40397
  if (needsPadding) {
40029
40398
  var _stack = [];
40030
40399
  try {
40031
- const paddingLength = firstTimestamp - this._startTimestamp;
40400
+ const paddingLength = finalSample.timestamp;
40032
40401
  const paddingLengthSamples = Math.round(paddingLength * originalSampleRate);
40033
40402
  const bytesPerSample = getBytesPerSample(sample.format);
40034
40403
  const data = new Uint8Array(bytesPerSample * paddingLengthSamples * originalNumberOfChannels);
@@ -40058,28 +40427,6 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
40058
40427
  __callDispose(_stack, _error, _hasError);
40059
40428
  }
40060
40429
  }
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
40430
  await this._registerAudioSample(
40084
40431
  pump,
40085
40432
  finalSample,
@@ -40107,11 +40454,13 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
40107
40454
  ownGroup = new OutputTrackGroup();
40108
40455
  }
40109
40456
  const audioTrackLanguageCode = await track.getLanguageCode();
40457
+ const trackName = await track.getName();
40458
+ const trackDisposition = await track.getDisposition();
40110
40459
  this.output.addAudioTrack(audioSource, {
40111
40460
  // TODO: This condition can be removed when all demuxers properly homogenize to BCP47 in v2
40112
40461
  languageCode: isIso639Dash2LanguageCode(audioTrackLanguageCode) ? audioTrackLanguageCode : void 0,
40113
- name: await track.getName() ?? void 0,
40114
- disposition: await track.getDisposition(),
40462
+ name: trackName ?? void 0,
40463
+ disposition: trackDisposition,
40115
40464
  group: ownGroup ?? trackOptions.group
40116
40465
  });
40117
40466
  this.utilizedTracks.push(track);
@@ -40150,7 +40499,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
40150
40499
  }
40151
40500
  /** @internal */
40152
40501
  async _checkpoint(pump, timestamp) {
40153
- while (this.state !== "canceled" && (timestamp >= this._executionUntil || this._pauseRequested)) {
40502
+ while (this._state !== "canceled" && (timestamp >= this._executionUntil || this._pauseRequested)) {
40154
40503
  pump.resolvers.resolve();
40155
40504
  const { promise, resolve } = promiseWithResolvers();
40156
40505
  pump.wake = resolve;
@@ -40191,14 +40540,14 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
40191
40540
  this.conversion = conversion;
40192
40541
  }
40193
40542
  declareTrack(trackId) {
40194
- this.maxTimestamps.set(trackId, 0);
40543
+ this.maxTimestamps.set(trackId, -Infinity);
40195
40544
  }
40196
40545
  shouldWait(trackId, timestamp) {
40197
40546
  const currentValue = this.maxTimestamps.get(trackId);
40198
40547
  assert(currentValue !== void 0);
40199
40548
  this.maxTimestamps.set(trackId, Math.max(timestamp, currentValue));
40200
40549
  const newMin = this.computeMinAndMaybeResolve();
40201
- if (this.conversion.state === "canceled" || this.conversion._pauseRequested || timestamp >= this.conversion._executionUntil) {
40550
+ if (this.conversion._state === "canceled" || this.conversion._pauseRequested || timestamp >= this.conversion._executionUntil) {
40202
40551
  return false;
40203
40552
  }
40204
40553
  return timestamp - newMin > MAX_TIMESTAMP_GAP;