mediabunny 1.29.0 → 1.30.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 (43) hide show
  1. package/dist/bundles/mediabunny.cjs +394 -205
  2. package/dist/bundles/mediabunny.min.cjs +8 -7
  3. package/dist/bundles/mediabunny.min.mjs +8 -7
  4. package/dist/bundles/mediabunny.mjs +394 -205
  5. package/dist/mediabunny.d.ts +19 -3
  6. package/dist/modules/src/adts/adts-demuxer.js +3 -0
  7. package/dist/modules/src/conversion.d.ts +4 -2
  8. package/dist/modules/src/conversion.d.ts.map +1 -1
  9. package/dist/modules/src/flac/flac-demuxer.js +3 -0
  10. package/dist/modules/src/index.d.ts.map +1 -1
  11. package/dist/modules/src/index.js +8 -0
  12. package/dist/modules/src/input-track.d.ts +7 -0
  13. package/dist/modules/src/input-track.d.ts.map +1 -1
  14. package/dist/modules/src/input-track.js +8 -0
  15. package/dist/modules/src/isobmff/isobmff-demuxer.js +15 -0
  16. package/dist/modules/src/matroska/matroska-demuxer.js +17 -0
  17. package/dist/modules/src/misc.d.ts +1 -0
  18. package/dist/modules/src/misc.d.ts.map +1 -1
  19. package/dist/modules/src/misc.js +3 -0
  20. package/dist/modules/src/mp3/mp3-demuxer.js +3 -0
  21. package/dist/modules/src/mpeg-ts/mpeg-ts-demuxer.d.ts +16 -10
  22. package/dist/modules/src/mpeg-ts/mpeg-ts-demuxer.d.ts.map +1 -1
  23. package/dist/modules/src/mpeg-ts/mpeg-ts-demuxer.js +321 -225
  24. package/dist/modules/src/ogg/ogg-demuxer.js +6 -0
  25. package/dist/modules/src/source.d.ts +9 -1
  26. package/dist/modules/src/source.d.ts.map +1 -1
  27. package/dist/modules/src/source.js +6 -1
  28. package/dist/modules/src/tsconfig.tsbuildinfo +1 -1
  29. package/dist/modules/src/wave/wave-demuxer.js +3 -0
  30. package/package.json +1 -1
  31. package/src/adts/adts-demuxer.ts +4 -0
  32. package/src/conversion.ts +4 -2
  33. package/src/flac/flac-demuxer.ts +4 -0
  34. package/src/index.ts +11 -0
  35. package/src/input-track.ts +10 -0
  36. package/src/isobmff/isobmff-demuxer.ts +19 -0
  37. package/src/matroska/matroska-demuxer.ts +21 -0
  38. package/src/misc.ts +4 -0
  39. package/src/mp3/mp3-demuxer.ts +4 -0
  40. package/src/mpeg-ts/mpeg-ts-demuxer.ts +398 -262
  41. package/src/ogg/ogg-demuxer.ts +10 -0
  42. package/src/source.ts +9 -1
  43. package/src/wave/wave-demuxer.ts +4 -0
@@ -536,6 +536,9 @@ var Mediabunny = (() => {
536
536
  var roundToMultiple = (value, multiple) => {
537
537
  return Math.round(value / multiple) * multiple;
538
538
  };
539
+ var floorToMultiple = (value, multiple) => {
540
+ return Math.floor(value / multiple) * multiple;
541
+ };
539
542
  var ilog = (x) => {
540
543
  let ret = 0;
541
544
  while (x) {
@@ -7075,6 +7078,14 @@ var Mediabunny = (() => {
7075
7078
  get id() {
7076
7079
  return this._backing.getId();
7077
7080
  }
7081
+ /**
7082
+ * The 1-based index of this track among all tracks of the same type in the input file. For example, the first
7083
+ * video track has number 1, the second video track has number 2, and so on. The index refers to the order in
7084
+ * which the tracks are returned by {@link Input.getTracks}.
7085
+ */
7086
+ get number() {
7087
+ return this._backing.getNumber();
7088
+ }
7078
7089
  /**
7079
7090
  * The identifier of the codec used internally by the container. It is not homogenized by Mediabunny
7080
7091
  * and depends entirely on the container format.
@@ -9222,6 +9233,21 @@ var Mediabunny = (() => {
9222
9233
  getId() {
9223
9234
  return this.internalTrack.id;
9224
9235
  }
9236
+ getNumber() {
9237
+ const demuxer = this.internalTrack.demuxer;
9238
+ const inputTrack = this.internalTrack.inputTrack;
9239
+ const trackType = inputTrack.type;
9240
+ let number = 0;
9241
+ for (const track of demuxer.tracks) {
9242
+ if (track.inputTrack.type === trackType) {
9243
+ number++;
9244
+ }
9245
+ if (track === this.internalTrack) {
9246
+ break;
9247
+ }
9248
+ }
9249
+ return number;
9250
+ }
9225
9251
  getCodec() {
9226
9252
  throw new Error("Not implemented on base class.");
9227
9253
  }
@@ -11745,6 +11771,23 @@ var Mediabunny = (() => {
11745
11771
  getId() {
11746
11772
  return this.internalTrack.id;
11747
11773
  }
11774
+ getNumber() {
11775
+ const demuxer = this.internalTrack.demuxer;
11776
+ const inputTrack = this.internalTrack.inputTrack;
11777
+ const trackType = inputTrack.type;
11778
+ let number = 0;
11779
+ for (const segment of demuxer.segments) {
11780
+ for (const track of segment.tracks) {
11781
+ if (track.inputTrack.type === trackType) {
11782
+ number++;
11783
+ }
11784
+ if (track === this.internalTrack) {
11785
+ break;
11786
+ }
11787
+ }
11788
+ }
11789
+ return number;
11790
+ }
11748
11791
  getCodec() {
11749
11792
  throw new Error("Not implemented on base class.");
11750
11793
  }
@@ -13517,6 +13560,9 @@ var Mediabunny = (() => {
13517
13560
  getId() {
13518
13561
  return 1;
13519
13562
  }
13563
+ getNumber() {
13564
+ return 1;
13565
+ }
13520
13566
  async getFirstTimestamp() {
13521
13567
  return 0;
13522
13568
  }
@@ -14043,6 +14089,13 @@ var Mediabunny = (() => {
14043
14089
  getId() {
14044
14090
  return this.bitstream.serialNumber;
14045
14091
  }
14092
+ getNumber() {
14093
+ const index = this.demuxer.tracks.findIndex(
14094
+ (t) => t._backing.bitstream === this.bitstream
14095
+ );
14096
+ assert(index !== -1);
14097
+ return index + 1;
14098
+ }
14046
14099
  getNumberOfChannels() {
14047
14100
  return this.bitstream.numberOfChannels;
14048
14101
  }
@@ -14777,6 +14830,9 @@ var Mediabunny = (() => {
14777
14830
  getId() {
14778
14831
  return 1;
14779
14832
  }
14833
+ getNumber() {
14834
+ return 1;
14835
+ }
14780
14836
  getCodec() {
14781
14837
  return this.demuxer.getCodec();
14782
14838
  }
@@ -15045,6 +15101,9 @@ var Mediabunny = (() => {
15045
15101
  getId() {
15046
15102
  return 1;
15047
15103
  }
15104
+ getNumber() {
15105
+ return 1;
15106
+ }
15048
15107
  async getFirstTimestamp() {
15049
15108
  return 0;
15050
15109
  }
@@ -15626,6 +15685,9 @@ var Mediabunny = (() => {
15626
15685
  getId() {
15627
15686
  return 1;
15628
15687
  }
15688
+ getNumber() {
15689
+ return 1;
15690
+ }
15629
15691
  getCodec() {
15630
15692
  return "flac";
15631
15693
  }
@@ -15789,6 +15851,10 @@ var Mediabunny = (() => {
15789
15851
  this.tracks = [];
15790
15852
  this.packetOffset = 0;
15791
15853
  this.packetStride = -1;
15854
+ this.sectionEndPositions = [];
15855
+ this.seekChunkSize = 5 * 1024 * 1024;
15856
+ // 5 MiB, picked because most HLS segments are below this size
15857
+ this.minReferencePointByteDistance = -1;
15792
15858
  this.reader = input._reader;
15793
15859
  }
15794
15860
  async readMetadata() {
@@ -15810,12 +15876,19 @@ var Mediabunny = (() => {
15810
15876
  } else {
15811
15877
  throw new Error("Unreachable.");
15812
15878
  }
15879
+ const MIN_REFERENCE_POINT_PACKET_DISTANCE = 256;
15880
+ this.minReferencePointByteDistance = MIN_REFERENCE_POINT_PACKET_DISTANCE * this.packetStride;
15813
15881
  let currentPos = this.packetOffset;
15814
15882
  let programMapPid = null;
15815
15883
  let hasProgramAssociationTable = false;
15816
15884
  let hasProgramMap = false;
15817
15885
  while (true) {
15818
- const section = await this.readSection(currentPos, true);
15886
+ const section = await this.readSection(
15887
+ currentPos,
15888
+ true,
15889
+ !hasProgramMap
15890
+ // Expect contiguous sections as long as we don't have the PMT
15891
+ );
15819
15892
  if (!section) {
15820
15893
  break;
15821
15894
  }
@@ -16049,12 +16122,14 @@ var Mediabunny = (() => {
16049
16122
  const codecStrings = await Promise.all(tracks.map((x) => x.getCodecParameterString()));
16050
16123
  return buildMpegTsMimeType(codecStrings);
16051
16124
  }
16052
- async readSection(startPos, full) {
16125
+ async readSection(startPos, full, contiguous = false) {
16053
16126
  let endPos = startPos;
16054
16127
  let currentPos = startPos;
16055
16128
  const chunks = [];
16056
16129
  let chunksByteLength = 0;
16057
16130
  let firstPacket = null;
16131
+ let mustAddSectionEnd = true;
16132
+ let randomAccessIndicator = 0;
16058
16133
  while (true) {
16059
16134
  const packet = await this.readPacket(currentPos);
16060
16135
  currentPos += this.packetStride;
@@ -16068,7 +16143,11 @@ var Mediabunny = (() => {
16068
16143
  firstPacket = packet;
16069
16144
  } else {
16070
16145
  if (packet.pid !== firstPacket.pid) {
16071
- continue;
16146
+ if (contiguous) {
16147
+ break;
16148
+ } else {
16149
+ continue;
16150
+ }
16072
16151
  }
16073
16152
  if (packet.payloadUnitStartIndicator === 1) {
16074
16153
  break;
@@ -16079,6 +16158,9 @@ var Mediabunny = (() => {
16079
16158
  let adaptationFieldLength = 0;
16080
16159
  if (hasAdaptationField) {
16081
16160
  adaptationFieldLength = 1 + packet.body[0];
16161
+ if (packet === firstPacket && adaptationFieldLength > 1) {
16162
+ randomAccessIndicator = packet.body[1] >> 6 & 1;
16163
+ }
16082
16164
  }
16083
16165
  if (hasPayload) {
16084
16166
  if (adaptationFieldLength === 0) {
@@ -16091,9 +16173,19 @@ var Mediabunny = (() => {
16091
16173
  }
16092
16174
  endPos = currentPos;
16093
16175
  if (!full && chunksByteLength >= 64) {
16176
+ mustAddSectionEnd = false;
16177
+ break;
16178
+ }
16179
+ const isKnownSectionEnd = binarySearchExact(this.sectionEndPositions, endPos, (x) => x) !== -1;
16180
+ if (isKnownSectionEnd) {
16181
+ mustAddSectionEnd = false;
16094
16182
  break;
16095
16183
  }
16096
16184
  }
16185
+ if (mustAddSectionEnd) {
16186
+ const index = binarySearchLessOrEqual(this.sectionEndPositions, endPos, (x) => x);
16187
+ this.sectionEndPositions.splice(index + 1, 0, endPos);
16188
+ }
16097
16189
  if (!firstPacket) {
16098
16190
  return null;
16099
16191
  }
@@ -16113,7 +16205,8 @@ var Mediabunny = (() => {
16113
16205
  startPos,
16114
16206
  endPos: full ? endPos : null,
16115
16207
  pid: firstPacket.pid,
16116
- payload: merged
16208
+ payload: merged,
16209
+ randomAccessIndicator
16117
16210
  };
16118
16211
  }
16119
16212
  async readPacketHeader(pos) {
@@ -16199,7 +16292,8 @@ var Mediabunny = (() => {
16199
16292
  return {
16200
16293
  sectionStartPos: section.startPos,
16201
16294
  sectionEndPos: section.endPos,
16202
- pts
16295
+ pts,
16296
+ randomAccessIndicator: section.randomAccessIndicator
16203
16297
  };
16204
16298
  };
16205
16299
  var readPesPacket = (section) => {
@@ -16230,23 +16324,37 @@ var Mediabunny = (() => {
16230
16324
  data
16231
16325
  };
16232
16326
  };
16233
- var MpegTsTrackBacking = class {
16327
+ var MpegTsTrackBacking = class _MpegTsTrackBacking {
16234
16328
  constructor(elementaryStream) {
16235
16329
  this.elementaryStream = elementaryStream;
16236
16330
  /**
16237
- * Reference PES packets, spread throughout the file, to be used to speed up random access and perform
16238
- * binary search for packets.
16331
+ * Reference PES packets, spread throughout the file, to be used to speed up repeated random access. Sorted by both
16332
+ * byte offset and PTS.
16239
16333
  */
16240
16334
  this.referencePesPackets = [];
16241
16335
  this.endReferencePesPacketAdded = false;
16242
16336
  this.packetBuffers = /* @__PURE__ */ new WeakMap();
16243
16337
  /** Used for recreating PacketBuffers if necessary. */
16244
16338
  this.packetSectionStarts = /* @__PURE__ */ new WeakMap();
16245
- this.mutex = new AsyncMutex();
16246
16339
  }
16247
16340
  getId() {
16248
16341
  return this.elementaryStream.pid;
16249
16342
  }
16343
+ getNumber() {
16344
+ const demuxer = this.elementaryStream.demuxer;
16345
+ const trackType = this.elementaryStream.info.type;
16346
+ let number = 0;
16347
+ for (const track of demuxer.tracks) {
16348
+ if (track.type === trackType) {
16349
+ number++;
16350
+ }
16351
+ assert(track._backing instanceof _MpegTsTrackBacking);
16352
+ if (track._backing.elementaryStream === this.elementaryStream) {
16353
+ break;
16354
+ }
16355
+ }
16356
+ return number;
16357
+ }
16250
16358
  getCodec() {
16251
16359
  throw new Error("Not implemented on base class.");
16252
16360
  }
@@ -16274,34 +16382,42 @@ var Mediabunny = (() => {
16274
16382
  return firstPacket?.timestamp ?? 0;
16275
16383
  }
16276
16384
  createEncodedPacket(suppliedPacket, duration, options) {
16385
+ let packetType;
16386
+ if (this.allPacketsAreKeyPackets()) {
16387
+ packetType = "key";
16388
+ } else {
16389
+ packetType = suppliedPacket.randomAccessIndicator === 1 ? "key" : "delta";
16390
+ }
16277
16391
  return new EncodedPacket(
16278
16392
  options.metadataOnly ? PLACEHOLDER_DATA : suppliedPacket.data,
16279
- this.getPacketType(suppliedPacket.data),
16393
+ packetType,
16280
16394
  suppliedPacket.pts / TIMESCALE,
16281
16395
  Math.max(duration / TIMESCALE, 0),
16282
16396
  suppliedPacket.sequenceNumber,
16283
16397
  suppliedPacket.data.byteLength
16284
16398
  );
16285
16399
  }
16286
- maybeInsertReferencePacket(pesPacketHeader, force, dropIfMutexLocked) {
16287
- if (dropIfMutexLocked && this.mutex.pending > 0) {
16288
- return;
16289
- }
16290
- const index = binarySearchLessOrEqual(this.referencePesPackets, pesPacketHeader.pts, (x) => x.pts);
16400
+ maybeInsertReferencePacket(pesPacketHeader) {
16401
+ const index = binarySearchLessOrEqual(
16402
+ this.referencePesPackets,
16403
+ pesPacketHeader.sectionStartPos,
16404
+ (x) => x.sectionStartPos
16405
+ );
16291
16406
  if (index >= 0) {
16292
16407
  const entry = this.referencePesPackets[index];
16293
- if (pesPacketHeader.sectionStartPos <= entry.sectionStartPos) {
16408
+ if (pesPacketHeader.pts <= entry.pts) {
16294
16409
  return false;
16295
16410
  }
16296
- if (!force && pesPacketHeader.pts - entry.pts < TIMESCALE / 2) {
16411
+ const minByteDistance = this.elementaryStream.demuxer.minReferencePointByteDistance;
16412
+ if (pesPacketHeader.sectionStartPos - entry.sectionStartPos < minByteDistance) {
16297
16413
  return false;
16298
16414
  }
16299
16415
  if (index < this.referencePesPackets.length - 1) {
16300
16416
  const nextEntry = this.referencePesPackets[index + 1];
16301
- if (nextEntry.sectionStartPos < pesPacketHeader.sectionStartPos) {
16417
+ if (nextEntry.pts < pesPacketHeader.pts) {
16302
16418
  return false;
16303
16419
  }
16304
- if (!force && nextEntry.pts - pesPacketHeader.pts < TIMESCALE / 2) {
16420
+ if (nextEntry.sectionStartPos - pesPacketHeader.sectionStartPos < minByteDistance) {
16305
16421
  return false;
16306
16422
  }
16307
16423
  }
@@ -16383,168 +16499,105 @@ var Mediabunny = (() => {
16383
16499
  }
16384
16500
  /**
16385
16501
  * Searches for the packet with the largest timestamp not larger than `timestamp` in the file, using a combination
16386
- * of binary search and linear refinement.
16502
+ * of chunk-based binary search and linear refinement. The reason the coarse search is done in large chunks is to
16503
+ * make it more performant for small files and over high-latency readers such as the network.
16387
16504
  */
16388
16505
  async doPacketLookup(timestamp, keyframesOnly, options) {
16389
16506
  const searchPts = roundIfAlmostInteger(timestamp * TIMESCALE);
16390
16507
  const demuxer = this.elementaryStream.demuxer;
16391
- const reader = demuxer.reader;
16392
- const release = await this.mutex.acquire();
16393
- let currentPesPacketHeader;
16394
- try {
16395
- if (this.referencePesPackets.length === 0) {
16396
- const section2 = this.elementaryStream.firstSection;
16397
- assert(section2);
16398
- const pesPacketHeader = readPesPacketHeader(section2);
16399
- assert(pesPacketHeader);
16400
- this.maybeInsertReferencePacket(pesPacketHeader, false, false);
16401
- assert(this.referencePesPackets.length === 1);
16402
- }
16403
- let currentIndex = binarySearchLessOrEqual(this.referencePesPackets, searchPts, (x) => x.pts);
16404
- if (currentIndex === -1) {
16405
- return null;
16406
- }
16407
- const needsToLookForLastPacket = reader.fileSize !== null && currentIndex === this.referencePesPackets.length - 1 && !this.endReferencePesPacketAdded;
16408
- if (needsToLookForLastPacket) {
16409
- let currentPos = reader.fileSize - demuxer.packetStride + demuxer.packetOffset;
16410
- let packetHeader = await demuxer.readPacketHeader(currentPos);
16508
+ const { reader, seekChunkSize } = demuxer;
16509
+ const pid = this.elementaryStream.pid;
16510
+ const findFirstPesPacketHeaderInChunk = async (startPos, endPos) => {
16511
+ let currentPos = startPos;
16512
+ while (currentPos < endPos) {
16513
+ const packetHeader = await demuxer.readPacketHeader(currentPos);
16411
16514
  if (!packetHeader) {
16412
16515
  return null;
16413
16516
  }
16414
- while (packetHeader.pid !== this.elementaryStream.pid || packetHeader.payloadUnitStartIndicator === 0) {
16415
- currentPos -= demuxer.packetStride;
16416
- const previousPacketHeader = await demuxer.readPacketHeader(currentPos);
16417
- if (!previousPacketHeader) {
16418
- return null;
16419
- }
16420
- packetHeader = previousPacketHeader;
16421
- }
16422
- const section2 = await demuxer.readSection(currentPos, false);
16423
- assert(section2);
16424
- const pesPacketHeader = readPesPacketHeader(section2);
16425
- if (!pesPacketHeader) {
16426
- throw new Error(MISSING_PES_PACKET_ERROR);
16427
- }
16428
- this.maybeInsertReferencePacket(pesPacketHeader, true, false);
16429
- this.endReferencePesPacketAdded = true;
16430
- }
16431
- currentIndex = binarySearchLessOrEqual(this.referencePesPackets, searchPts, (x) => x.pts);
16432
- assert(currentIndex !== -1);
16433
- while (reader.fileSize !== null) {
16434
- const currentEntry = this.referencePesPackets[currentIndex];
16435
- const nextEntry = this.referencePesPackets[currentIndex + 1];
16436
- if (searchPts - currentEntry.pts < TIMESCALE || !nextEntry) {
16437
- break;
16438
- }
16439
- const midpoint = roundToMultiple(
16440
- (currentEntry.sectionStartPos + nextEntry.sectionStartPos) / 2,
16441
- demuxer.packetStride
16442
- ) + demuxer.packetOffset;
16443
- let currentPos = midpoint;
16444
- let packetHeader = await demuxer.readPacketHeader(currentPos);
16445
- assert(packetHeader);
16446
- while (currentPos < nextEntry.sectionStartPos && (packetHeader.pid !== this.elementaryStream.pid || packetHeader.payloadUnitStartIndicator === 0)) {
16447
- currentPos += demuxer.packetStride;
16448
- const previousPacketHeader = await demuxer.readPacketHeader(currentPos);
16449
- if (!previousPacketHeader) {
16517
+ if (packetHeader.pid === pid && packetHeader.payloadUnitStartIndicator === 1) {
16518
+ const section = await demuxer.readSection(currentPos, false);
16519
+ if (!section) {
16450
16520
  return null;
16451
16521
  }
16452
- packetHeader = previousPacketHeader;
16453
- }
16454
- if (currentPos >= nextEntry.sectionStartPos) {
16455
- break;
16456
- }
16457
- const section2 = await demuxer.readSection(currentPos, false);
16458
- assert(section2);
16459
- const pesPacketHeader = readPesPacketHeader(section2);
16460
- if (!pesPacketHeader) {
16461
- throw new Error(MISSING_PES_PACKET_ERROR);
16462
- }
16463
- const addedPoint = this.maybeInsertReferencePacket(pesPacketHeader, false, false);
16464
- if (!addedPoint) {
16465
- break;
16466
- }
16467
- if (pesPacketHeader.pts <= searchPts) {
16468
- currentIndex++;
16522
+ const pesPacketHeader = readPesPacketHeader(section);
16523
+ return pesPacketHeader;
16469
16524
  }
16525
+ currentPos += demuxer.packetStride;
16470
16526
  }
16471
- currentPesPacketHeader = this.referencePesPackets[currentIndex];
16472
- assert(currentPesPacketHeader.pts <= searchPts);
16473
- } finally {
16474
- release();
16527
+ return null;
16528
+ };
16529
+ const firstSection = this.elementaryStream.firstSection;
16530
+ assert(firstSection);
16531
+ const firstPesPacketHeader = readPesPacketHeader(firstSection);
16532
+ assert(firstPesPacketHeader);
16533
+ if (searchPts < firstPesPacketHeader.pts) {
16534
+ return null;
16475
16535
  }
16476
- release();
16477
- outer:
16478
- while (true) {
16479
- let currentPos = currentPesPacketHeader.sectionStartPos + demuxer.packetStride;
16480
- while (true) {
16481
- const packetHeader = await demuxer.readPacketHeader(currentPos);
16482
- if (!packetHeader) {
16483
- break outer;
16484
- }
16485
- if (packetHeader.pid === this.elementaryStream.pid && packetHeader.payloadUnitStartIndicator === 1) {
16486
- break;
16536
+ let scanStartPos;
16537
+ const referencePointIndex = binarySearchLessOrEqual(this.referencePesPackets, searchPts, (x) => x.pts);
16538
+ const referencePoint = referencePointIndex !== -1 ? this.referencePesPackets[referencePointIndex] : null;
16539
+ if (referencePoint && searchPts - referencePoint.pts < TIMESCALE / 2) {
16540
+ scanStartPos = referencePoint.sectionStartPos;
16541
+ } else {
16542
+ let startChunkIndex = 0;
16543
+ if (reader.fileSize !== null) {
16544
+ const numChunks = Math.ceil(reader.fileSize / seekChunkSize);
16545
+ if (numChunks > 1) {
16546
+ let low = 0;
16547
+ let high = numChunks - 1;
16548
+ startChunkIndex = low;
16549
+ while (low <= high) {
16550
+ const mid = Math.floor((low + high) / 2);
16551
+ const chunkStartPos = floorToMultiple(mid * seekChunkSize, demuxer.packetStride) + firstPesPacketHeader.sectionStartPos;
16552
+ const chunkEndPos = chunkStartPos + seekChunkSize;
16553
+ const pesHeader = await findFirstPesPacketHeaderInChunk(chunkStartPos, chunkEndPos);
16554
+ if (!pesHeader) {
16555
+ high = mid - 1;
16556
+ continue;
16557
+ }
16558
+ if (pesHeader.pts <= searchPts) {
16559
+ startChunkIndex = mid;
16560
+ low = mid + 1;
16561
+ } else {
16562
+ high = mid - 1;
16563
+ }
16487
16564
  }
16488
- currentPos += demuxer.packetStride;
16489
- }
16490
- const nextSection = await demuxer.readSection(currentPos, false);
16491
- if (!nextSection) {
16492
- break;
16493
- }
16494
- const nextPesPacketHeader = readPesPacketHeader(nextSection);
16495
- if (!nextPesPacketHeader) {
16496
- throw new Error(MISSING_PES_PACKET_ERROR);
16497
- }
16498
- if (nextPesPacketHeader.pts > searchPts) {
16499
- break;
16500
- }
16501
- currentPesPacketHeader = nextPesPacketHeader;
16502
- if (reader.fileSize === null) {
16503
- this.maybeInsertReferencePacket(nextPesPacketHeader, false, true);
16504
16565
  }
16505
16566
  }
16567
+ scanStartPos = floorToMultiple(
16568
+ startChunkIndex * seekChunkSize,
16569
+ demuxer.packetStride
16570
+ ) + firstPesPacketHeader.sectionStartPos;
16571
+ }
16572
+ let currentPesHeader = await findFirstPesPacketHeaderInChunk(
16573
+ scanStartPos,
16574
+ reader.fileSize ?? Infinity
16575
+ );
16576
+ if (!currentPesHeader) {
16577
+ currentPesHeader = firstPesPacketHeader;
16578
+ }
16506
16579
  const reorderSize = this.getReorderSize();
16507
- for (let i = 0; i < reorderSize; i++) {
16508
- let pos = currentPesPacketHeader.sectionStartPos - demuxer.packetStride;
16580
+ const retrieveEncodedPacket = async (sectionStartPos, predicate) => {
16581
+ const section = await demuxer.readSection(sectionStartPos, true);
16582
+ assert(section);
16583
+ const pesPacket = readPesPacket(section);
16584
+ assert(pesPacket);
16585
+ const context = new PacketReadingContext(this, pesPacket, true);
16586
+ const buffer = new PacketBuffer(this, context);
16509
16587
  while (true) {
16510
- const packetHeader = await demuxer.readPacketHeader(pos);
16511
- if (!packetHeader) {
16588
+ const topPts = last(buffer.presentationOrderPackets)?.pts ?? -Infinity;
16589
+ if (topPts >= searchPts) {
16512
16590
  break;
16513
16591
  }
16514
- if (packetHeader.pid === this.elementaryStream.pid && packetHeader.payloadUnitStartIndicator === 1) {
16515
- const headerSection = await demuxer.readSection(pos, false);
16516
- assert(headerSection);
16517
- const header = readPesPacketHeader(headerSection);
16518
- if (!header) {
16519
- throw new Error(MISSING_PES_PACKET_ERROR);
16520
- }
16521
- currentPesPacketHeader = header;
16592
+ const didRead = await buffer.readNextPacket();
16593
+ if (!didRead) {
16522
16594
  break;
16523
16595
  }
16524
- pos -= demuxer.packetStride;
16525
- }
16526
- }
16527
- const section = await demuxer.readSection(currentPesPacketHeader.sectionStartPos, true);
16528
- assert(section);
16529
- const pesPacket = readPesPacket(section);
16530
- assert(pesPacket);
16531
- const context = new PacketReadingContext(this, pesPacket, true);
16532
- const buffer = new PacketBuffer(this, context);
16533
- while (true) {
16534
- const topPts = last(buffer.presentationOrderPackets)?.pts ?? -Infinity;
16535
- if (topPts >= searchPts) {
16536
- break;
16537
16596
  }
16538
- const didRead = await buffer.readNextDecodeOrderPacket();
16539
- if (!didRead) {
16540
- break;
16597
+ const targetIndex = findLastIndex(buffer.presentationOrderPackets, predicate);
16598
+ if (targetIndex === -1) {
16599
+ return null;
16541
16600
  }
16542
- }
16543
- const targetIndex = findLastIndex(
16544
- buffer.presentationOrderPackets,
16545
- (p) => p.pts <= searchPts && (!keyframesOnly || this.getPacketType(p.data) === "key")
16546
- );
16547
- if (targetIndex !== -1) {
16548
16601
  const targetPacket = buffer.presentationOrderPackets[targetIndex];
16549
16602
  const lastDuration = targetIndex === 0 ? 0 : targetPacket.pts - buffer.presentationOrderPackets[targetIndex - 1].pts;
16550
16603
  while (buffer.decodeOrderPackets[0] !== targetPacket) {
@@ -16557,42 +16610,156 @@ var Mediabunny = (() => {
16557
16610
  this.packetBuffers.set(packet, buffer);
16558
16611
  this.packetSectionStarts.set(packet, result.packet.sectionStartPos);
16559
16612
  return packet;
16560
- }
16561
- if (!keyframesOnly) {
16562
- return null;
16563
- }
16564
- let searchPos = currentPesPacketHeader.sectionStartPos;
16565
- while (true) {
16566
- searchPos -= demuxer.packetStride;
16567
- const packetHeader = await demuxer.readPacketHeader(searchPos);
16568
- if (!packetHeader) {
16569
- return null;
16570
- }
16571
- if (packetHeader.pid !== this.elementaryStream.pid || packetHeader.payloadUnitStartIndicator !== 1) {
16572
- continue;
16573
- }
16574
- const section2 = await demuxer.readSection(searchPos, true);
16575
- assert(section2);
16576
- const pesPacket2 = readPesPacket(section2);
16577
- if (!pesPacket2) {
16578
- throw new Error(MISSING_PES_PACKET_ERROR);
16579
- }
16580
- const context2 = new PacketReadingContext(this, pesPacket2, false);
16581
- await this.markNextPacket(context2);
16582
- if (!context2.suppliedPacket) {
16583
- continue;
16584
- }
16585
- if (this.getPacketType(context2.suppliedPacket.data) !== "key") {
16586
- continue;
16613
+ };
16614
+ if (!keyframesOnly || this.allPacketsAreKeyPackets()) {
16615
+ outer:
16616
+ while (true) {
16617
+ let currentPos = currentPesHeader.sectionStartPos + demuxer.packetStride;
16618
+ while (true) {
16619
+ const packetHeader = await demuxer.readPacketHeader(currentPos);
16620
+ if (!packetHeader) {
16621
+ break outer;
16622
+ }
16623
+ if (packetHeader.pid === pid && packetHeader.payloadUnitStartIndicator === 1) {
16624
+ const section = await demuxer.readSection(currentPos, false);
16625
+ if (section) {
16626
+ const nextPesHeader = readPesPacketHeader(section);
16627
+ if (!nextPesHeader) {
16628
+ throw new Error(MISSING_PES_PACKET_ERROR);
16629
+ }
16630
+ if (nextPesHeader.pts > searchPts) {
16631
+ break outer;
16632
+ }
16633
+ currentPesHeader = nextPesHeader;
16634
+ this.maybeInsertReferencePacket(nextPesHeader);
16635
+ break;
16636
+ }
16637
+ }
16638
+ currentPos += demuxer.packetStride;
16639
+ }
16640
+ }
16641
+ outer:
16642
+ for (let i = 0; i < reorderSize; i++) {
16643
+ let pos = currentPesHeader.sectionStartPos - demuxer.packetStride;
16644
+ while (pos >= demuxer.packetOffset) {
16645
+ const packetHeader = await demuxer.readPacketHeader(pos);
16646
+ if (!packetHeader) {
16647
+ break outer;
16648
+ }
16649
+ if (packetHeader.pid === pid && packetHeader.payloadUnitStartIndicator === 1) {
16650
+ const section = await demuxer.readSection(pos, false);
16651
+ if (section) {
16652
+ const header = readPesPacketHeader(section);
16653
+ if (!header) {
16654
+ throw new Error(MISSING_PES_PACKET_ERROR);
16655
+ }
16656
+ currentPesHeader = header;
16657
+ break;
16658
+ }
16659
+ }
16660
+ pos -= demuxer.packetStride;
16661
+ }
16662
+ }
16663
+ return retrieveEncodedPacket(currentPesHeader.sectionStartPos, (p) => p.pts <= searchPts);
16664
+ } else {
16665
+ let currentChunkStartPos = scanStartPos;
16666
+ let nextChunkStartPos = null;
16667
+ while (true) {
16668
+ let bestKeyPesHeader = null;
16669
+ const isFirstChunk = currentChunkStartPos <= firstPesPacketHeader.sectionStartPos;
16670
+ let pesHeader;
16671
+ if (isFirstChunk) {
16672
+ pesHeader = firstPesPacketHeader;
16673
+ bestKeyPesHeader = firstPesPacketHeader;
16674
+ } else {
16675
+ pesHeader = await findFirstPesPacketHeaderInChunk(
16676
+ currentChunkStartPos,
16677
+ reader.fileSize ?? Infinity
16678
+ );
16679
+ }
16680
+ let passedSearchPts = false;
16681
+ let lookaheadCount = 0;
16682
+ outer:
16683
+ while (pesHeader) {
16684
+ if (nextChunkStartPos !== null && pesHeader.sectionStartPos >= nextChunkStartPos) {
16685
+ break;
16686
+ }
16687
+ const isKeyCandidate = pesHeader.randomAccessIndicator === 1;
16688
+ if (isKeyCandidate && pesHeader.pts <= searchPts) {
16689
+ bestKeyPesHeader = pesHeader;
16690
+ }
16691
+ if (pesHeader.pts > searchPts) {
16692
+ passedSearchPts = true;
16693
+ }
16694
+ if (passedSearchPts) {
16695
+ lookaheadCount++;
16696
+ if (lookaheadCount >= reorderSize) {
16697
+ break;
16698
+ }
16699
+ }
16700
+ let currentPos = pesHeader.sectionStartPos + demuxer.packetStride;
16701
+ while (true) {
16702
+ const packetHeader = await demuxer.readPacketHeader(currentPos);
16703
+ if (!packetHeader) {
16704
+ break outer;
16705
+ }
16706
+ if (packetHeader.pid === pid && packetHeader.payloadUnitStartIndicator === 1) {
16707
+ const section = await demuxer.readSection(currentPos, false);
16708
+ if (section) {
16709
+ pesHeader = readPesPacketHeader(section);
16710
+ if (!pesHeader) {
16711
+ throw new Error(MISSING_PES_PACKET_ERROR);
16712
+ }
16713
+ this.maybeInsertReferencePacket(pesHeader);
16714
+ break;
16715
+ }
16716
+ }
16717
+ currentPos += demuxer.packetStride;
16718
+ }
16719
+ }
16720
+ if (bestKeyPesHeader) {
16721
+ let startPesHeader = bestKeyPesHeader;
16722
+ if (lookaheadCount === 0) {
16723
+ outer:
16724
+ for (let i = 0; i < reorderSize - 1; i++) {
16725
+ let pos = startPesHeader.sectionStartPos - demuxer.packetStride;
16726
+ while (pos >= demuxer.packetOffset) {
16727
+ const packetHeader = await demuxer.readPacketHeader(pos);
16728
+ if (!packetHeader) {
16729
+ break outer;
16730
+ }
16731
+ if (packetHeader.pid === pid && packetHeader.payloadUnitStartIndicator === 1) {
16732
+ const section = await demuxer.readSection(pos, false);
16733
+ if (section) {
16734
+ const header = readPesPacketHeader(section);
16735
+ if (!header) {
16736
+ throw new Error(MISSING_PES_PACKET_ERROR);
16737
+ }
16738
+ startPesHeader = header;
16739
+ break;
16740
+ }
16741
+ }
16742
+ pos -= demuxer.packetStride;
16743
+ }
16744
+ }
16745
+ }
16746
+ const encodedPacket = await retrieveEncodedPacket(
16747
+ startPesHeader.sectionStartPos,
16748
+ (p) => p.pts <= searchPts && p.randomAccessIndicator === 1
16749
+ );
16750
+ assert(encodedPacket);
16751
+ return encodedPacket;
16752
+ }
16753
+ assert(!isFirstChunk);
16754
+ nextChunkStartPos = currentChunkStartPos;
16755
+ currentChunkStartPos = Math.max(
16756
+ floorToMultiple(
16757
+ currentChunkStartPos - firstPesPacketHeader.sectionStartPos - seekChunkSize,
16758
+ demuxer.packetStride
16759
+ ) + firstPesPacketHeader.sectionStartPos,
16760
+ firstPesPacketHeader.sectionStartPos
16761
+ );
16587
16762
  }
16588
- context2.uncapped = true;
16589
- const buffer2 = new PacketBuffer(this, context2);
16590
- const result = await buffer2.readNext();
16591
- assert(result);
16592
- const packet = this.createEncodedPacket(result.packet, result.duration, options);
16593
- this.packetBuffers.set(packet, buffer2);
16594
- this.packetSectionStarts.set(packet, result.packet.sectionStartPos);
16595
- return packet;
16596
16763
  }
16597
16764
  }
16598
16765
  };
@@ -16639,8 +16806,8 @@ var Mediabunny = (() => {
16639
16806
  async getDecoderConfig() {
16640
16807
  return this.decoderConfig;
16641
16808
  }
16642
- getPacketType(packetData) {
16643
- return determineVideoPacketType(this.elementaryStream.info.codec, this.decoderConfig, packetData) ?? "key";
16809
+ allPacketsAreKeyPackets() {
16810
+ return false;
16644
16811
  }
16645
16812
  getReorderSize() {
16646
16813
  return this.elementaryStream.info.reorderSize;
@@ -16649,6 +16816,9 @@ var Mediabunny = (() => {
16649
16816
  assert(!context.suppliedPacket);
16650
16817
  const codec = this.elementaryStream.info.codec;
16651
16818
  const CHUNK_SIZE = 1024;
16819
+ if (codec !== "avc" && codec !== "hevc") {
16820
+ throw new Error("Unhandled.");
16821
+ }
16652
16822
  let packetStartPos = null;
16653
16823
  while (true) {
16654
16824
  let remaining = context.ensureBuffered(CHUNK_SIZE);
@@ -16740,9 +16910,8 @@ var Mediabunny = (() => {
16740
16910
  sampleRate: this.elementaryStream.info.sampleRate
16741
16911
  };
16742
16912
  }
16743
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
16744
- getPacketType(packetData) {
16745
- return "key";
16913
+ allPacketsAreKeyPackets() {
16914
+ return true;
16746
16915
  }
16747
16916
  getReorderSize() {
16748
16917
  return 1;
@@ -16805,7 +16974,7 @@ var Mediabunny = (() => {
16805
16974
  context.seekTo(possibleHeaderStartPos + 1);
16806
16975
  }
16807
16976
  } else {
16808
- throw new Error("Unreachable");
16977
+ throw new Error("Unhandled.");
16809
16978
  }
16810
16979
  }
16811
16980
  if (remaining < CHUNK_SIZE) {
@@ -16963,17 +17132,23 @@ var Mediabunny = (() => {
16963
17132
  this.suppliedPacket = null;
16964
17133
  return;
16965
17134
  }
16966
- this.backing.maybeInsertReferencePacket(currentPesPacket, false, true);
17135
+ this.backing.maybeInsertReferencePacket(currentPesPacket);
16967
17136
  const pts = this.nextPts;
16968
17137
  this.nextPts += intrinsicDuration;
16969
17138
  const sectionStartPos = currentPesPacket.sectionStartPos;
16970
17139
  const sequenceNumber = sectionStartPos + (this.currentPos - this.currentPesPacketPos);
16971
17140
  const data = this.readBytes(packetLength);
17141
+ let randomAccessIndicator = currentPesPacket.randomAccessIndicator;
17142
+ assert(this.backing.elementaryStream.firstSection);
17143
+ if (currentPesPacket.sectionStartPos === this.backing.elementaryStream.firstSection.startPos) {
17144
+ randomAccessIndicator = 1;
17145
+ }
16972
17146
  this.suppliedPacket = {
16973
17147
  pts,
16974
17148
  data,
16975
17149
  sequenceNumber,
16976
- sectionStartPos
17150
+ sectionStartPos,
17151
+ randomAccessIndicator
16977
17152
  };
16978
17153
  this.pesPackets.splice(0, this.currentPesPacketIndex);
16979
17154
  this.currentPesPacketIndex = 0;
@@ -16993,7 +17168,7 @@ var Mediabunny = (() => {
16993
17168
  }
16994
17169
  async readNext() {
16995
17170
  if (this.decodeOrderPackets.length === 0) {
16996
- const didRead = await this.readNextDecodeOrderPacket();
17171
+ const didRead = await this.readNextPacket();
16997
17172
  if (!didRead) {
16998
17173
  return null;
16999
17174
  }
@@ -17020,7 +17195,7 @@ var Mediabunny = (() => {
17020
17195
  }
17021
17196
  return { packet, duration };
17022
17197
  }
17023
- async readNextDecodeOrderPacket() {
17198
+ async readNextPacket() {
17024
17199
  if (this.reachedEnd) {
17025
17200
  return false;
17026
17201
  }
@@ -17049,7 +17224,7 @@ var Mediabunny = (() => {
17049
17224
  if (presentationIndex !== -1 && presentationIndex <= this.presentationOrderPackets.length - 2) {
17050
17225
  break;
17051
17226
  }
17052
- const didRead = await this.readNextDecodeOrderPacket();
17227
+ const didRead = await this.readNextPacket();
17053
17228
  if (!didRead) {
17054
17229
  break;
17055
17230
  }
@@ -17589,7 +17764,12 @@ var Mediabunny = (() => {
17589
17764
  return Math.min(2 ** (previousAttempts - 2), 16);
17590
17765
  };
17591
17766
  var UrlSource = class extends Source {
17592
- /** Creates a new {@link UrlSource} backed by the resource at the specified URL. */
17767
+ /**
17768
+ * Creates a new {@link UrlSource} backed by the resource at the specified URL.
17769
+ *
17770
+ * When passing a `Request` instance, note that the `signal` and `headers.Range` options will be overridden by
17771
+ * Mediabunny. If you want to cancel ongoing requests, use {@link Input.dispose}.
17772
+ */
17593
17773
  constructor(url2, options = {}) {
17594
17774
  if (typeof url2 !== "string" && !(url2 instanceof URL) && !(typeof Request !== "undefined" && url2 instanceof Request)) {
17595
17775
  throw new TypeError("url must be a string, URL or Request.");
@@ -28288,6 +28468,15 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
28288
28468
  return this.finalizeCurrentBuffer();
28289
28469
  }
28290
28470
  };
28471
+
28472
+ // src/index.ts
28473
+ var MEDIABUNNY_LOADED_SYMBOL = Symbol.for("mediabunny loaded");
28474
+ if (globalThis[MEDIABUNNY_LOADED_SYMBOL]) {
28475
+ console.error(
28476
+ "[WARNING]\nMediabunny was loaded twice. This will likely cause Mediabunny not to work correctly. Check if multiple dependencies are importing different versions of Mediabunny, or if something is being bundled incorrectly."
28477
+ );
28478
+ }
28479
+ globalThis[MEDIABUNNY_LOADED_SYMBOL] = true;
28291
28480
  return __toCommonJS(index_exports);
28292
28481
  })();
28293
28482
  if (typeof module === "object" && typeof module.exports === "object") Object.assign(module.exports, Mediabunny)