@remotion/media-parser 4.0.289 → 4.0.291

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 (167) hide show
  1. package/dist/containers/flac/get-channel-count.d.ts +1 -1
  2. package/dist/containers/iso-base-media/base-media-box.d.ts +0 -1
  3. package/dist/containers/iso-base-media/collect-sample-positions-from-moof-boxes.d.ts +4 -1
  4. package/dist/containers/iso-base-media/collect-sample-positions-from-moof-boxes.js +9 -5
  5. package/dist/containers/iso-base-media/find-keyframe-before-time.js +16 -11
  6. package/dist/containers/iso-base-media/find-track-to-seek.d.ts +14 -0
  7. package/dist/containers/iso-base-media/find-track-to-seek.js +39 -0
  8. package/dist/containers/iso-base-media/get-children.js +2 -2
  9. package/dist/containers/iso-base-media/get-keyframes.js +6 -1
  10. package/dist/containers/iso-base-media/get-mfra-seeking-box.d.ts +3 -1
  11. package/dist/containers/iso-base-media/get-mfra-seeking-box.js +5 -1
  12. package/dist/containers/iso-base-media/get-moov-atom.js +6 -3
  13. package/dist/containers/iso-base-media/get-sample-position-bounds.js +3 -1
  14. package/dist/containers/iso-base-media/get-sample-positions-from-track.js +1 -1
  15. package/dist/containers/iso-base-media/get-seeking-byte-from-fragmented-mp4.d.ts +14 -0
  16. package/dist/containers/iso-base-media/get-seeking-byte-from-fragmented-mp4.js +89 -0
  17. package/dist/containers/iso-base-media/get-seeking-byte.d.ts +3 -3
  18. package/dist/containers/iso-base-media/get-seeking-byte.js +32 -96
  19. package/dist/containers/iso-base-media/get-video-codec-from-iso-track.d.ts +1 -1
  20. package/dist/containers/iso-base-media/mdat/calculate-jump-marks.d.ts +6 -0
  21. package/dist/containers/iso-base-media/mdat/calculate-jump-marks.js +131 -0
  22. package/dist/containers/iso-base-media/mdat/mdat.d.ts +2 -2
  23. package/dist/containers/iso-base-media/mdat/mdat.js +18 -2
  24. package/dist/containers/iso-base-media/mfra/find-best-segment-from-tfra.d.ts +3 -3
  25. package/dist/containers/iso-base-media/mfra/find-best-segment-from-tfra.js +2 -2
  26. package/dist/containers/iso-base-media/mfra/get-mfra-atom.d.ts +5 -1
  27. package/dist/containers/iso-base-media/mfra/get-mfra-atom.js +3 -1
  28. package/dist/containers/iso-base-media/mfra/get-mfro-atom.d.ts +5 -1
  29. package/dist/containers/iso-base-media/mfra/get-mfro-atom.js +3 -1
  30. package/dist/containers/iso-base-media/parse-boxes.js +5 -2
  31. package/dist/containers/iso-base-media/process-box.d.ts +16 -5
  32. package/dist/containers/iso-base-media/process-box.js +206 -118
  33. package/dist/containers/iso-base-media/sample-positions.d.ts +25 -0
  34. package/dist/containers/iso-base-media/sample-positions.js +37 -0
  35. package/dist/containers/iso-base-media/seeking-hints.d.ts +1 -1
  36. package/dist/containers/iso-base-media/stsd/samples.js +1 -0
  37. package/dist/containers/iso-base-media/stsd/stsc.d.ts +1 -6
  38. package/dist/containers/iso-base-media/stsd/stsc.js +2 -5
  39. package/dist/containers/iso-base-media/stsd/stss.d.ts +1 -1
  40. package/dist/containers/iso-base-media/stsd/stss.js +2 -2
  41. package/dist/containers/iso-base-media/turn-sample-positions-into-array.d.ts +19 -0
  42. package/dist/containers/iso-base-media/turn-sample-positions-into-array.js +73 -0
  43. package/dist/containers/m3u/after-manifest-fetch.d.ts +5 -1
  44. package/dist/containers/m3u/after-manifest-fetch.js +3 -1
  45. package/dist/containers/m3u/first-sample-in-m3u-chunk.d.ts +13 -0
  46. package/dist/containers/m3u/first-sample-in-m3u-chunk.js +31 -0
  47. package/dist/containers/m3u/get-seeking-byte.d.ts +13 -0
  48. package/dist/containers/m3u/get-seeking-byte.js +32 -0
  49. package/dist/containers/m3u/get-streams.d.ts +1 -0
  50. package/dist/containers/m3u/get-streams.js +1 -0
  51. package/dist/containers/m3u/iterate-over-segment-files.d.ts +5 -3
  52. package/dist/containers/m3u/iterate-over-segment-files.js +11 -1
  53. package/dist/containers/m3u/parse-m3u-media-directive.js +1 -0
  54. package/dist/containers/m3u/parse-m3u.js +8 -0
  55. package/dist/containers/m3u/process-m3u-chunk.d.ts +12 -0
  56. package/dist/containers/m3u/process-m3u-chunk.js +274 -0
  57. package/dist/containers/m3u/run-over-m3u.js +7 -80
  58. package/dist/containers/m3u/sample-sorter.d.ts +1 -0
  59. package/dist/containers/m3u/sample-sorter.js +4 -1
  60. package/dist/containers/m3u/seek/get-chunk-to-seek-to.d.ts +5 -0
  61. package/dist/containers/m3u/seek/get-chunk-to-seek-to.js +14 -0
  62. package/dist/containers/m3u/seeking-hints.d.ts +2 -0
  63. package/dist/containers/m3u/seeking-hints.js +9 -0
  64. package/dist/containers/m3u/select-stream.d.ts +2 -1
  65. package/dist/containers/m3u/select-stream.js +7 -2
  66. package/dist/containers/m3u/types.d.ts +1 -0
  67. package/dist/containers/mp3/get-duration.d.ts +5 -0
  68. package/dist/containers/riff/seek/fetch-idx1.d.ts +3 -1
  69. package/dist/containers/riff/seek/fetch-idx1.js +3 -1
  70. package/dist/containers/transport-stream/handle-aac-packet.d.ts +2 -2
  71. package/dist/containers/transport-stream/handle-avc-packet.d.ts +2 -2
  72. package/dist/containers/transport-stream/process-audio.d.ts +2 -2
  73. package/dist/containers/transport-stream/process-stream-buffers.d.ts +3 -3
  74. package/dist/containers/transport-stream/process-video.d.ts +2 -2
  75. package/dist/containers/wav/get-duration-from-wav.d.ts +0 -1
  76. package/dist/containers/webm/get-sample-from-block.d.ts +12 -2
  77. package/dist/containers/webm/get-sample-from-block.js +40 -9
  78. package/dist/containers/webm/parse-ebml.js +28 -10
  79. package/dist/containers/webm/seek/fetch-web-cues.d.ts +3 -1
  80. package/dist/containers/webm/seek/fetch-web-cues.js +3 -1
  81. package/dist/containers/webm/state-for-processing.d.ts +2 -2
  82. package/dist/controller/media-parser-controller.d.ts +1 -1
  83. package/dist/controller/media-parser-controller.js +6 -2
  84. package/dist/controller/seek-signal.d.ts +1 -5
  85. package/dist/download-and-parse-media.js +1 -1
  86. package/dist/esm/index.mjs +1400 -611
  87. package/dist/esm/node.mjs +23 -3
  88. package/dist/esm/server-worker.mjs +8 -1
  89. package/dist/esm/universal.mjs +168 -15
  90. package/dist/esm/web.mjs +145 -13
  91. package/dist/esm/worker-server-entry.mjs +1467 -635
  92. package/dist/esm/worker-web-entry.mjs +1439 -634
  93. package/dist/esm/worker.mjs +8 -1
  94. package/dist/get-audio-codec.js +3 -0
  95. package/dist/get-duration.js +2 -1
  96. package/dist/get-fps.js +2 -1
  97. package/dist/get-sample-positions-from-mp4.js +10 -5
  98. package/dist/get-sample-positions.js +4 -4
  99. package/dist/get-seeking-byte.d.ts +5 -3
  100. package/dist/get-seeking-byte.js +19 -10
  101. package/dist/get-seeking-hints.d.ts +3 -3
  102. package/dist/get-seeking-hints.js +18 -13
  103. package/dist/get-tracks.d.ts +9 -1
  104. package/dist/get-tracks.js +13 -6
  105. package/dist/index.d.ts +21 -5
  106. package/dist/init-video.js +3 -2
  107. package/dist/internal-parse-media.js +13 -4
  108. package/dist/iterator/buffer-iterator.js +5 -3
  109. package/dist/metadata/metadata-from-iso.js +2 -1
  110. package/dist/options.d.ts +6 -1
  111. package/dist/parse-loop.js +22 -6
  112. package/dist/parse-media-on-worker-entry.js +1 -0
  113. package/dist/parse-media.js +1 -1
  114. package/dist/parse-result.d.ts +2 -2
  115. package/dist/perform-seek.d.ts +3 -1
  116. package/dist/perform-seek.js +3 -1
  117. package/dist/readers/fetch/get-body-and-reader.js +17 -2
  118. package/dist/readers/from-fetch.d.ts +17 -1
  119. package/dist/readers/from-fetch.js +68 -13
  120. package/dist/readers/from-node.js +24 -2
  121. package/dist/readers/from-web-file.js +3 -0
  122. package/dist/readers/reader.d.ts +19 -2
  123. package/dist/readers/universal.js +9 -0
  124. package/dist/readers/web.js +6 -0
  125. package/dist/register-track.d.ts +3 -3
  126. package/dist/seek-backwards.d.ts +3 -1
  127. package/dist/seek-backwards.js +4 -1
  128. package/dist/seek-forwards.d.ts +3 -1
  129. package/dist/seek-forwards.js +3 -1
  130. package/dist/seeking-hints.d.ts +4 -1
  131. package/dist/set-seeking-hints.js +4 -0
  132. package/dist/skip.d.ts +5 -0
  133. package/dist/skip.js +6 -1
  134. package/dist/state/can-skip-tracks.d.ts +1 -0
  135. package/dist/state/can-skip-tracks.js +10 -6
  136. package/dist/state/iso-base-media/cached-sample-positions.d.ts +15 -1
  137. package/dist/state/iso-base-media/cached-sample-positions.js +9 -4
  138. package/dist/state/iso-base-media/iso-state.d.ts +5 -1
  139. package/dist/state/iso-base-media/iso-state.js +2 -1
  140. package/dist/state/iso-base-media/lazy-mfra-load.d.ts +3 -1
  141. package/dist/state/iso-base-media/lazy-mfra-load.js +2 -1
  142. package/dist/state/keyframes.js +1 -0
  143. package/dist/state/m3u-state.d.ts +15 -4
  144. package/dist/state/m3u-state.js +20 -0
  145. package/dist/state/matroska/lazy-cues-fetch.d.ts +3 -1
  146. package/dist/state/matroska/lazy-cues-fetch.js +2 -1
  147. package/dist/state/matroska/webm.d.ts +3 -1
  148. package/dist/state/matroska/webm.js +2 -1
  149. package/dist/state/mp3.d.ts +16 -5
  150. package/dist/state/mp3.js +7 -5
  151. package/dist/state/parser-state.d.ts +31 -15
  152. package/dist/state/parser-state.js +19 -5
  153. package/dist/state/riff/lazy-idx1-fetch.d.ts +5 -3
  154. package/dist/state/riff/lazy-idx1-fetch.js +2 -1
  155. package/dist/state/riff.d.ts +5 -3
  156. package/dist/state/riff.js +2 -1
  157. package/dist/state/sample-callbacks.d.ts +3 -2
  158. package/dist/state/sample-callbacks.js +3 -3
  159. package/dist/version.d.ts +1 -1
  160. package/dist/version.js +1 -1
  161. package/dist/work-on-seek-request.d.ts +6 -3
  162. package/dist/work-on-seek-request.js +13 -13
  163. package/dist/worker/forward-controller-to-worker.js +1 -1
  164. package/dist/worker/serialize-error.js +26 -3
  165. package/dist/worker/worker-types.d.ts +7 -1
  166. package/dist/worker-server.js +2 -2
  167. package/package.json +3 -3
@@ -1328,7 +1328,7 @@ var getArrayBufferIterator = (initialData, maxBytes) => {
1328
1328
  const six = getUint8();
1329
1329
  const seven = getUint8();
1330
1330
  const eight = getUint8();
1331
- return eight << 56 | seven << 48 | six << 40 | five << 32 | four << 24 | three << 16 | two << 8 | one;
1331
+ return (eight << 56 | seven << 48 | six << 40 | five << 32 | four << 24 | three << 16 | two << 8 | one) >>> 0;
1332
1332
  }
1333
1333
  function byteArrayToBigInt(byteArray) {
1334
1334
  let result = BigInt(0);
@@ -1350,7 +1350,8 @@ var getArrayBufferIterator = (initialData, maxBytes) => {
1350
1350
  return Number(bigInt);
1351
1351
  };
1352
1352
  const getFourByteNumber = () => {
1353
- return getUint8() << 24 | getUint8() << 16 | getUint8() << 8 | getUint8();
1353
+ const unsigned = getUint8() << 24 | getUint8() << 16 | getUint8() << 8 | getUint8();
1354
+ return unsigned >>> 0;
1354
1355
  };
1355
1356
  const getPaddedFourByteNumber = () => {
1356
1357
  let lastInt = 128;
@@ -1903,6 +1904,16 @@ var registerVideoTrackWhenProfileIsAvailable = ({
1903
1904
  });
1904
1905
  };
1905
1906
 
1907
+ // src/skip.ts
1908
+ var makeSkip = (skipTo) => ({
1909
+ action: "skip",
1910
+ skipTo
1911
+ });
1912
+ var makeFetchMoreData = (bytesNeeded) => ({
1913
+ action: "fetch-more-data",
1914
+ bytesNeeded
1915
+ });
1916
+
1906
1917
  // src/containers/iso-base-media/esds/decoder-specific-config.ts
1907
1918
  var parseDecoderSpecificConfig = (iterator) => {
1908
1919
  const layerTag = iterator.getUint8();
@@ -2358,7 +2369,7 @@ var getFpsFromIsoMaseMedia = (state) => {
2358
2369
  const moovBox = getMoovBoxFromState({
2359
2370
  structureState: state.structure,
2360
2371
  isoState: state.iso,
2361
- mp4HeaderSegment: state.mp4HeaderSegment,
2372
+ mp4HeaderSegment: state.m3uPlaylistContext?.mp4HeaderSegment ?? null,
2362
2373
  mayUsePrecomputed: true
2363
2374
  });
2364
2375
  if (!moovBox) {
@@ -3298,7 +3309,7 @@ var isoBaseMediaHasTracks = (state, mayUsePrecomputed) => {
3298
3309
  return Boolean(getMoovBoxFromState({
3299
3310
  structureState: state.structure,
3300
3311
  isoState: state.iso,
3301
- mp4HeaderSegment: state.mp4HeaderSegment,
3312
+ mp4HeaderSegment: state.m3uPlaylistContext?.mp4HeaderSegment ?? null,
3302
3313
  mayUsePrecomputed
3303
3314
  }));
3304
3315
  };
@@ -3383,11 +3394,16 @@ var getTracksFromMoovBox = (moovBox) => {
3383
3394
  otherTracks
3384
3395
  };
3385
3396
  };
3386
- var getTracksFromIsoBaseMedia = (state, mayUsePrecomputed) => {
3397
+ var getTracksFromIsoBaseMedia = ({
3398
+ mayUsePrecomputed,
3399
+ structure,
3400
+ isoState,
3401
+ m3uPlaylistContext
3402
+ }) => {
3387
3403
  const moovBox = getMoovBoxFromState({
3388
- structureState: state.structure,
3389
- isoState: state.iso,
3390
- mp4HeaderSegment: state.mp4HeaderSegment,
3404
+ structureState: structure,
3405
+ isoState,
3406
+ mp4HeaderSegment: m3uPlaylistContext?.mp4HeaderSegment ?? null,
3391
3407
  mayUsePrecomputed
3392
3408
  });
3393
3409
  if (!moovBox) {
@@ -3416,7 +3432,12 @@ var getTracks = (state, mayUsePrecomputed) => {
3416
3432
  return getCategorizedTracksFromMatroska(state);
3417
3433
  }
3418
3434
  if (structure.type === "iso-base-media") {
3419
- return getTracksFromIsoBaseMedia(state, mayUsePrecomputed);
3435
+ return getTracksFromIsoBaseMedia({
3436
+ isoState: state.iso,
3437
+ m3uPlaylistContext: state.m3uPlaylistContext,
3438
+ structure: state.structure,
3439
+ mayUsePrecomputed
3440
+ });
3420
3441
  }
3421
3442
  if (structure.type === "riff") {
3422
3443
  return getTracksFromAvi(structure, state);
@@ -3622,6 +3643,9 @@ var getAudioCodecFromAudioCodecInfo = (codec) => {
3622
3643
  if (codec.format === "ac-3") {
3623
3644
  return "ac3";
3624
3645
  }
3646
+ if (codec.format === "Opus") {
3647
+ return "opus";
3648
+ }
3625
3649
  if (codec.format === "mp4a") {
3626
3650
  if (codec.primarySpecificator === 64) {
3627
3651
  return "aac";
@@ -4643,7 +4667,7 @@ var parseStsc = ({
4643
4667
  }
4644
4668
  const flags = iterator.getSlice(3);
4645
4669
  const entryCount = iterator.getUint32();
4646
- const entries = [];
4670
+ const entries = new Map;
4647
4671
  for (let i = 0;i < entryCount; i++) {
4648
4672
  const firstChunk = iterator.getUint32();
4649
4673
  const samplesPerChunk = iterator.getUint32();
@@ -4651,10 +4675,7 @@ var parseStsc = ({
4651
4675
  if (sampleDescriptionIndex !== 1) {
4652
4676
  throw new Error(`Expected sampleDescriptionIndex to be 1, but got ${sampleDescriptionIndex}`);
4653
4677
  }
4654
- entries.push({
4655
- firstChunk,
4656
- samplesPerChunk
4657
- });
4678
+ entries.set(firstChunk, samplesPerChunk);
4658
4679
  }
4659
4680
  return {
4660
4681
  type: "stsc-box",
@@ -4712,9 +4733,9 @@ var parseStss = ({
4712
4733
  }
4713
4734
  const flags = iterator.getSlice(3);
4714
4735
  const sampleCount = iterator.getUint32();
4715
- const sampleNumber = [];
4736
+ const sampleNumber = new Set;
4716
4737
  for (let i = 0;i < sampleCount; i++) {
4717
- sampleNumber.push(iterator.getUint32());
4738
+ sampleNumber.add(iterator.getUint32());
4718
4739
  }
4719
4740
  const bytesRemainingInBox = boxSize - (iterator.counter.getOffset() - offset);
4720
4741
  if (bytesRemainingInBox > 0) {
@@ -5030,8 +5051,11 @@ var processBox = async ({
5030
5051
  const boxSizeRaw = iterator.getFourByteNumber();
5031
5052
  if (boxSizeRaw === 0) {
5032
5053
  return {
5033
- type: "void-box",
5034
- boxSize: 0
5054
+ type: "box",
5055
+ box: {
5056
+ type: "void-box",
5057
+ boxSize: 0
5058
+ }
5035
5059
  };
5036
5060
  }
5037
5061
  if (boxSizeRaw === 1 && iterator.bytesRemaining() < 12 || iterator.bytesRemaining() < 4) {
@@ -5045,120 +5069,177 @@ var processBox = async ({
5045
5069
  const headerLength = iterator.counter.getOffset() - startOff;
5046
5070
  if (boxType === "mdat") {
5047
5071
  if (!onlyIfMdatAtomExpected) {
5048
- return null;
5072
+ return { type: "nothing" };
5049
5073
  }
5050
5074
  const { mediaSectionState } = onlyIfMdatAtomExpected;
5051
5075
  mediaSectionState.addMediaSection({
5052
5076
  size: boxSize - headerLength,
5053
5077
  start: iterator.counter.getOffset()
5054
5078
  });
5055
- return null;
5079
+ return { type: "nothing" };
5056
5080
  }
5057
5081
  if (bytesRemaining < boxSize) {
5058
5082
  returnToCheckpoint();
5059
- return null;
5083
+ return {
5084
+ type: "fetch-more-data",
5085
+ bytesNeeded: makeFetchMoreData(boxSize - bytesRemaining)
5086
+ };
5060
5087
  }
5061
5088
  if (boxType === "ftyp") {
5062
- return parseFtyp({ iterator, size: boxSize, offset: fileOffset });
5089
+ return {
5090
+ type: "box",
5091
+ box: await parseFtyp({ iterator, size: boxSize, offset: fileOffset })
5092
+ };
5063
5093
  }
5064
5094
  if (boxType === "colr") {
5065
- return parseColorParameterBox({
5066
- iterator,
5067
- size: boxSize
5068
- });
5095
+ return {
5096
+ type: "box",
5097
+ box: await parseColorParameterBox({
5098
+ iterator,
5099
+ size: boxSize
5100
+ })
5101
+ };
5069
5102
  }
5070
5103
  if (boxType === "mvhd") {
5071
- return parseMvhd({ iterator, offset: fileOffset, size: boxSize });
5104
+ return {
5105
+ type: "box",
5106
+ box: await parseMvhd({ iterator, offset: fileOffset, size: boxSize })
5107
+ };
5072
5108
  }
5073
5109
  if (boxType === "tkhd") {
5074
- return parseTkhd({ iterator, offset: fileOffset, size: boxSize });
5110
+ return {
5111
+ type: "box",
5112
+ box: await parseTkhd({ iterator, offset: fileOffset, size: boxSize })
5113
+ };
5075
5114
  }
5076
5115
  if (boxType === "trun") {
5077
- return parseTrun({ iterator, offset: fileOffset, size: boxSize });
5116
+ return {
5117
+ type: "box",
5118
+ box: await parseTrun({ iterator, offset: fileOffset, size: boxSize })
5119
+ };
5078
5120
  }
5079
5121
  if (boxType === "tfdt") {
5080
- return parseTfdt({ iterator, size: boxSize, offset: fileOffset });
5122
+ return {
5123
+ type: "box",
5124
+ box: await parseTfdt({ iterator, size: boxSize, offset: fileOffset })
5125
+ };
5081
5126
  }
5082
5127
  if (boxType === "stsd") {
5083
- return parseStsd({
5084
- offset: fileOffset,
5085
- size: boxSize,
5086
- iterator,
5087
- logLevel,
5088
- contentLength
5089
- });
5128
+ return {
5129
+ type: "box",
5130
+ box: await parseStsd({
5131
+ offset: fileOffset,
5132
+ size: boxSize,
5133
+ iterator,
5134
+ logLevel,
5135
+ contentLength
5136
+ })
5137
+ };
5090
5138
  }
5091
5139
  if (boxType === "stsz") {
5092
- return parseStsz({
5093
- iterator,
5094
- offset: fileOffset,
5095
- size: boxSize
5096
- });
5140
+ return {
5141
+ type: "box",
5142
+ box: await parseStsz({
5143
+ iterator,
5144
+ offset: fileOffset,
5145
+ size: boxSize
5146
+ })
5147
+ };
5097
5148
  }
5098
5149
  if (boxType === "stco" || boxType === "co64") {
5099
- return parseStco({
5100
- iterator,
5101
- offset: fileOffset,
5102
- size: boxSize,
5103
- mode64Bit: boxType === "co64"
5104
- });
5150
+ return {
5151
+ type: "box",
5152
+ box: await parseStco({
5153
+ iterator,
5154
+ offset: fileOffset,
5155
+ size: boxSize,
5156
+ mode64Bit: boxType === "co64"
5157
+ })
5158
+ };
5105
5159
  }
5106
5160
  if (boxType === "pasp") {
5107
- return parsePasp({
5108
- iterator,
5109
- offset: fileOffset,
5110
- size: boxSize
5111
- });
5161
+ return {
5162
+ type: "box",
5163
+ box: await parsePasp({
5164
+ iterator,
5165
+ offset: fileOffset,
5166
+ size: boxSize
5167
+ })
5168
+ };
5112
5169
  }
5113
5170
  if (boxType === "stss") {
5114
- return parseStss({
5115
- iterator,
5116
- offset: fileOffset,
5117
- boxSize
5118
- });
5171
+ return {
5172
+ type: "box",
5173
+ box: await parseStss({
5174
+ iterator,
5175
+ offset: fileOffset,
5176
+ boxSize
5177
+ })
5178
+ };
5119
5179
  }
5120
5180
  if (boxType === "ctts") {
5121
- return parseCtts({
5122
- iterator,
5123
- offset: fileOffset,
5124
- size: boxSize
5125
- });
5181
+ return {
5182
+ type: "box",
5183
+ box: await parseCtts({
5184
+ iterator,
5185
+ offset: fileOffset,
5186
+ size: boxSize
5187
+ })
5188
+ };
5126
5189
  }
5127
5190
  if (boxType === "stsc") {
5128
- return parseStsc({
5129
- iterator,
5130
- offset: fileOffset,
5131
- size: boxSize
5132
- });
5191
+ return {
5192
+ type: "box",
5193
+ box: await parseStsc({
5194
+ iterator,
5195
+ offset: fileOffset,
5196
+ size: boxSize
5197
+ })
5198
+ };
5133
5199
  }
5134
5200
  if (boxType === "mebx") {
5135
- return parseMebx({
5136
- offset: fileOffset,
5137
- size: boxSize,
5138
- iterator,
5139
- logLevel,
5140
- contentLength
5141
- });
5201
+ return {
5202
+ type: "box",
5203
+ box: await parseMebx({
5204
+ offset: fileOffset,
5205
+ size: boxSize,
5206
+ iterator,
5207
+ logLevel,
5208
+ contentLength
5209
+ })
5210
+ };
5142
5211
  }
5143
5212
  if (boxType === "hdlr") {
5144
- return parseHdlr({ iterator, size: boxSize, offset: fileOffset });
5213
+ return {
5214
+ type: "box",
5215
+ box: await parseHdlr({ iterator, size: boxSize, offset: fileOffset })
5216
+ };
5145
5217
  }
5146
5218
  if (boxType === "keys") {
5147
- return parseKeys({ iterator, size: boxSize, offset: fileOffset });
5219
+ return {
5220
+ type: "box",
5221
+ box: await parseKeys({ iterator, size: boxSize, offset: fileOffset })
5222
+ };
5148
5223
  }
5149
5224
  if (boxType === "ilst") {
5150
- return parseIlstBox({
5151
- iterator,
5152
- offset: fileOffset,
5153
- size: boxSize
5154
- });
5225
+ return {
5226
+ type: "box",
5227
+ box: await parseIlstBox({
5228
+ iterator,
5229
+ offset: fileOffset,
5230
+ size: boxSize
5231
+ })
5232
+ };
5155
5233
  }
5156
5234
  if (boxType === "tfra") {
5157
- return parseTfraBox({
5158
- iterator,
5159
- offset: fileOffset,
5160
- size: boxSize
5161
- });
5235
+ return {
5236
+ type: "box",
5237
+ box: await parseTfraBox({
5238
+ iterator,
5239
+ offset: fileOffset,
5240
+ size: boxSize
5241
+ })
5242
+ };
5162
5243
  }
5163
5244
  if (boxType === "moov") {
5164
5245
  if (!onlyIfMoovAtomExpected) {
@@ -5167,12 +5248,12 @@ var processBox = async ({
5167
5248
  const { tracks: tracks2, isoState } = onlyIfMoovAtomExpected;
5168
5249
  if (tracks2.hasAllTracks()) {
5169
5250
  iterator.discard(boxSize - 8);
5170
- return null;
5251
+ return { type: "nothing" };
5171
5252
  }
5172
5253
  if (isoState && isoState.moov.getMoovBoxAndPrecomputed() && !isoState.moov.getMoovBoxAndPrecomputed()?.precomputed) {
5173
5254
  Log.verbose(logLevel, "Moov box already parsed, skipping");
5174
5255
  iterator.discard(boxSize - 8);
5175
- return null;
5256
+ return { type: "nothing" };
5176
5257
  }
5177
5258
  const box = await parseMoov({
5178
5259
  offset: fileOffset,
@@ -5183,7 +5264,7 @@ var processBox = async ({
5183
5264
  contentLength
5184
5265
  });
5185
5266
  tracks2.setIsDone(logLevel);
5186
- return box;
5267
+ return { type: "box", box };
5187
5268
  }
5188
5269
  if (boxType === "trak") {
5189
5270
  if (!onlyIfMoovAtomExpected) {
@@ -5218,54 +5299,75 @@ var processBox = async ({
5218
5299
  onAudioTrack
5219
5300
  });
5220
5301
  }
5221
- return box;
5302
+ return { type: "box", box };
5222
5303
  }
5223
5304
  if (boxType === "stts") {
5224
- return parseStts({
5225
- data: iterator,
5226
- size: boxSize,
5227
- fileOffset
5228
- });
5305
+ return {
5306
+ type: "box",
5307
+ box: await parseStts({
5308
+ data: iterator,
5309
+ size: boxSize,
5310
+ fileOffset
5311
+ })
5312
+ };
5229
5313
  }
5230
5314
  if (boxType === "avcC") {
5231
- return parseAvcc({
5232
- data: iterator,
5233
- size: boxSize
5234
- });
5315
+ return {
5316
+ type: "box",
5317
+ box: await parseAvcc({
5318
+ data: iterator,
5319
+ size: boxSize
5320
+ })
5321
+ };
5235
5322
  }
5236
5323
  if (boxType === "av1C") {
5237
- return parseAv1C({
5238
- data: iterator,
5239
- size: boxSize
5240
- });
5324
+ return {
5325
+ type: "box",
5326
+ box: await parseAv1C({
5327
+ data: iterator,
5328
+ size: boxSize
5329
+ })
5330
+ };
5241
5331
  }
5242
5332
  if (boxType === "hvcC") {
5243
- return parseHvcc({
5244
- data: iterator,
5245
- size: boxSize,
5246
- offset: fileOffset
5247
- });
5333
+ return {
5334
+ type: "box",
5335
+ box: await parseHvcc({
5336
+ data: iterator,
5337
+ size: boxSize,
5338
+ offset: fileOffset
5339
+ })
5340
+ };
5248
5341
  }
5249
5342
  if (boxType === "tfhd") {
5250
- return getTfhd({
5251
- iterator,
5252
- offset: fileOffset,
5253
- size: boxSize
5254
- });
5343
+ return {
5344
+ type: "box",
5345
+ box: await getTfhd({
5346
+ iterator,
5347
+ offset: fileOffset,
5348
+ size: boxSize
5349
+ })
5350
+ };
5255
5351
  }
5256
5352
  if (boxType === "mdhd") {
5257
- return parseMdhd({
5258
- data: iterator,
5259
- size: boxSize,
5260
- fileOffset
5261
- });
5353
+ return {
5354
+ type: "box",
5355
+ box: await parseMdhd({
5356
+ data: iterator,
5357
+ size: boxSize,
5358
+ fileOffset
5359
+ })
5360
+ };
5262
5361
  }
5263
5362
  if (boxType === "esds") {
5264
- return parseEsds({
5265
- data: iterator,
5266
- size: boxSize,
5267
- fileOffset
5268
- });
5363
+ return {
5364
+ type: "box",
5365
+ box: await parseEsds({
5366
+ data: iterator,
5367
+ size: boxSize,
5368
+ fileOffset
5369
+ })
5370
+ };
5269
5371
  }
5270
5372
  if (boxType === "moof") {
5271
5373
  onlyIfMoovAtomExpected?.isoState?.mfra.triggerLoad();
@@ -5279,20 +5381,26 @@ var processBox = async ({
5279
5381
  contentLength
5280
5382
  });
5281
5383
  return {
5282
- type: "regular-box",
5283
- boxType,
5284
- boxSize,
5285
- children,
5286
- offset: fileOffset
5384
+ type: "box",
5385
+ box: {
5386
+ type: "regular-box",
5387
+ boxType,
5388
+ boxSize,
5389
+ children,
5390
+ offset: fileOffset
5391
+ }
5287
5392
  };
5288
5393
  }
5289
5394
  iterator.discard(boxSize - 8);
5290
5395
  return {
5291
- type: "regular-box",
5292
- boxType,
5293
- boxSize,
5294
- children: [],
5295
- offset: fileOffset
5396
+ type: "box",
5397
+ box: {
5398
+ type: "regular-box",
5399
+ boxType,
5400
+ boxSize,
5401
+ children: [],
5402
+ offset: fileOffset
5403
+ }
5296
5404
  };
5297
5405
  };
5298
5406
 
@@ -5314,10 +5422,10 @@ var getIsoBaseMediaChildren = async ({
5314
5422
  onlyIfMdatAtomExpected: null,
5315
5423
  contentLength
5316
5424
  });
5317
- if (!parsed) {
5425
+ if (parsed.type !== "box") {
5318
5426
  throw new Error("Expected box");
5319
5427
  }
5320
- boxes.push(parsed);
5428
+ boxes.push(parsed.box);
5321
5429
  }
5322
5430
  if (iterator.counter.getOffset() > size + initial) {
5323
5431
  throw new Error(`read too many bytes - size: ${size}, read: ${iterator.counter.getOffset() - initial}. initial offset: ${initial}`);
@@ -5383,7 +5491,8 @@ var audioTags = [
5383
5491
  1836253269,
5384
5492
  ".mp3",
5385
5493
  "mp4a",
5386
- "ac-3"
5494
+ "ac-3",
5495
+ "Opus"
5387
5496
  ];
5388
5497
  var processIsoFormatBox = async ({
5389
5498
  iterator,
@@ -5873,23 +5982,62 @@ var parseBlockFlags = (iterator, type) => {
5873
5982
  };
5874
5983
 
5875
5984
  // src/containers/webm/get-sample-from-block.ts
5876
- var addAvcToTrackIfNecessary = ({
5985
+ var addAvcToTrackAndActivateTrackIfNecessary = async ({
5877
5986
  partialVideoSample,
5878
5987
  codec,
5879
5988
  structureState,
5880
5989
  webmState,
5881
- trackNumber: trackNumber2
5990
+ trackNumber: trackNumber2,
5991
+ logLevel,
5992
+ callbacks,
5993
+ onVideoTrack
5882
5994
  }) => {
5883
- if (codec === "V_MPEG4/ISO/AVC" && getTracksFromMatroska({ structureState, webmState }).missingInfo.length > 0) {
5884
- const parsed = parseAvc(partialVideoSample.data);
5885
- for (const parse of parsed) {
5886
- if (parse.type === "avc-profile") {
5887
- webmState.setAvcProfileForTrackNumber(trackNumber2, parse);
5995
+ if (codec !== "V_MPEG4/ISO/AVC") {
5996
+ return;
5997
+ }
5998
+ const missingTracks = getTracksFromMatroska({
5999
+ structureState,
6000
+ webmState
6001
+ }).missingInfo;
6002
+ if (missingTracks.length === 0) {
6003
+ return;
6004
+ }
6005
+ const parsed = parseAvc(partialVideoSample.data);
6006
+ for (const parse of parsed) {
6007
+ if (parse.type === "avc-profile") {
6008
+ webmState.setAvcProfileForTrackNumber(trackNumber2, parse);
6009
+ const track = missingTracks.find((t) => t.trackId === trackNumber2);
6010
+ if (!track) {
6011
+ throw new Error("Could not find track " + trackNumber2);
6012
+ }
6013
+ const resolvedTracks = getTracksFromMatroska({
6014
+ structureState,
6015
+ webmState
6016
+ }).resolved;
6017
+ const resolvedTrack = resolvedTracks.find((t) => t.trackId === trackNumber2);
6018
+ if (!resolvedTrack) {
6019
+ throw new Error("Could not find track " + trackNumber2);
5888
6020
  }
6021
+ await registerVideoTrack({
6022
+ track: resolvedTrack,
6023
+ container: "webm",
6024
+ logLevel,
6025
+ onVideoTrack,
6026
+ registerVideoSampleCallback: callbacks.registerVideoSampleCallback,
6027
+ tracks: callbacks.tracks
6028
+ });
5889
6029
  }
5890
6030
  }
5891
6031
  };
5892
- var getSampleFromBlock = (ebml, webmState, offset, structureState) => {
6032
+ var getSampleFromBlock = async ({
6033
+ ebml,
6034
+ webmState,
6035
+ offset,
6036
+ structureState,
6037
+ callbacks,
6038
+ logLevel,
6039
+ onVideoTrack
6040
+ }) => {
5893
6041
  const iterator = getArrayBufferIterator(ebml.value, ebml.value.length);
5894
6042
  const trackNumber2 = iterator.getVint();
5895
6043
  if (trackNumber2 === null) {
@@ -5927,12 +6075,15 @@ var getSampleFromBlock = (ebml, webmState, offset, structureState) => {
5927
6075
  partialVideoSample
5928
6076
  };
5929
6077
  }
5930
- addAvcToTrackIfNecessary({
6078
+ await addAvcToTrackAndActivateTrackIfNecessary({
5931
6079
  codec,
5932
6080
  partialVideoSample,
5933
6081
  structureState,
5934
6082
  webmState,
5935
- trackNumber: trackNumber2
6083
+ trackNumber: trackNumber2,
6084
+ callbacks,
6085
+ logLevel,
6086
+ onVideoTrack
5936
6087
  });
5937
6088
  const sample = {
5938
6089
  ...partialVideoSample,
@@ -6092,21 +6243,31 @@ var postprocessEbml = async ({
6092
6243
  });
6093
6244
  }
6094
6245
  if (track && track.type === "video") {
6095
- await registerVideoTrack({
6096
- track,
6097
- container: "webm",
6098
- logLevel,
6099
- onVideoTrack,
6100
- registerVideoSampleCallback: callbacks.registerVideoSampleCallback,
6101
- tracks: callbacks.tracks
6102
- });
6246
+ if (track.codec !== NO_CODEC_PRIVATE_SHOULD_BE_DERIVED_FROM_SPS) {
6247
+ await registerVideoTrack({
6248
+ track,
6249
+ container: "webm",
6250
+ logLevel,
6251
+ onVideoTrack,
6252
+ registerVideoSampleCallback: callbacks.registerVideoSampleCallback,
6253
+ tracks: callbacks.tracks
6254
+ });
6255
+ }
6103
6256
  }
6104
6257
  }
6105
6258
  if (ebml.type === "Timestamp") {
6106
6259
  webmState.setTimestampOffset(offset, ebml.value.value);
6107
6260
  }
6108
6261
  if (ebml.type === "Block" || ebml.type === "SimpleBlock") {
6109
- const sample = getSampleFromBlock(ebml, webmState, offset, structureState);
6262
+ const sample = await getSampleFromBlock({
6263
+ ebml,
6264
+ webmState,
6265
+ offset,
6266
+ structureState,
6267
+ callbacks,
6268
+ logLevel,
6269
+ onVideoTrack
6270
+ });
6110
6271
  if (sample.type === "video-sample") {
6111
6272
  await callbacks.onVideoSample(sample.videoSample.trackId, sample.videoSample);
6112
6273
  return {
@@ -6137,7 +6298,15 @@ var postprocessEbml = async ({
6137
6298
  throw new Error("Expected block segment");
6138
6299
  }
6139
6300
  const hasReferenceBlock = ebml.value.find((c) => c.type === "ReferenceBlock");
6140
- const sample = block2.value.length === 0 ? null : getSampleFromBlock(block2, webmState, offset, structureState);
6301
+ const sample = block2.value.length === 0 ? null : await getSampleFromBlock({
6302
+ ebml: block2,
6303
+ webmState,
6304
+ offset,
6305
+ structureState,
6306
+ callbacks,
6307
+ logLevel,
6308
+ onVideoTrack
6309
+ });
6141
6310
  if (sample && sample.type === "partial-video-sample") {
6142
6311
  const completeFrame = {
6143
6312
  ...sample.partialVideoSample,
@@ -6403,7 +6572,11 @@ var mediaParserController = () => {
6403
6572
  const performedSeeksSignal = performedSeeksStats();
6404
6573
  const checkForAbortAndPause = async () => {
6405
6574
  if (abortController.signal.aborted) {
6406
- throw new MediaParserAbortError("Aborted");
6575
+ const err = new MediaParserAbortError("Aborted");
6576
+ if (abortController.signal.reason) {
6577
+ err.cause = abortController.signal.reason;
6578
+ }
6579
+ throw err;
6407
6580
  }
6408
6581
  await pauseSignal.waitUntilResume();
6409
6582
  };
@@ -6425,7 +6598,7 @@ var mediaParserController = () => {
6425
6598
  abortController.abort(reason);
6426
6599
  emitter.dispatchAbort(reason);
6427
6600
  },
6428
- _experimentalSeek: seekSignal.seek,
6601
+ seek: seekSignal.seek,
6429
6602
  pause: pauseSignal.pause,
6430
6603
  resume: pauseSignal.resume,
6431
6604
  addEventListener: emitter.addEventListener,
@@ -6479,7 +6652,8 @@ var getM3uStreams = ({
6479
6652
  language: audioTrack.language,
6480
6653
  name: audioTrack.name,
6481
6654
  src: readerInterface.createAdjacentFileSource(audioTrack.uri, originalSrc),
6482
- id: associatedPlaylists.length
6655
+ id: associatedPlaylists.length,
6656
+ isAudio: true
6483
6657
  });
6484
6658
  }
6485
6659
  }
@@ -6679,11 +6853,15 @@ var collectSamplePositionsFromMoofBoxes = ({
6679
6853
  tkhdBox
6680
6854
  }) => {
6681
6855
  const isComplete = tfraBoxes.length > 0 && tfraBoxes.every((t) => t.entries.length === moofBoxes.length);
6682
- const samplePositions = moofBoxes.map((m) => {
6683
- return getSamplesFromMoof({
6684
- moofBox: m,
6685
- trackId: tkhdBox.trackId
6686
- });
6856
+ const samplePositions = moofBoxes.map((m, index) => {
6857
+ const isLastFragment = index === moofBoxes.length - 1 && isComplete;
6858
+ return {
6859
+ isLastFragment,
6860
+ samples: getSamplesFromMoof({
6861
+ moofBox: m,
6862
+ trackId: tkhdBox.trackId
6863
+ })
6864
+ };
6687
6865
  });
6688
6866
  return { samplePositions, isComplete };
6689
6867
  };
@@ -6716,14 +6894,14 @@ var getSamplePositions = ({
6716
6894
  const samples = [];
6717
6895
  let samplesPerChunk = 1;
6718
6896
  for (let i = 0;i < chunks.length; i++) {
6719
- const hasEntry = stscBox.entries.find((entry) => entry.firstChunk === i + 1);
6720
- if (hasEntry) {
6721
- samplesPerChunk = hasEntry.samplesPerChunk;
6897
+ const hasEntry = stscBox.entries.get(i + 1);
6898
+ if (hasEntry !== undefined) {
6899
+ samplesPerChunk = hasEntry;
6722
6900
  }
6723
6901
  let offsetInThisChunk = 0;
6724
6902
  for (let j = 0;j < samplesPerChunk; j++) {
6725
6903
  const size = stszBox.countType === "fixed" ? stszBox.sampleSize : stszBox.entries[samples.length];
6726
- const isKeyframe = stssBox ? stssBox.sampleNumber.includes(samples.length + 1) : true;
6904
+ const isKeyframe = stssBox ? stssBox.sampleNumber.has(samples.length + 1) : true;
6727
6905
  const delta = sttsDeltas[samples.length];
6728
6906
  const ctsOffset = cttsEntries[samples.length];
6729
6907
  const cts = dts + ctsOffset;
@@ -6767,11 +6945,16 @@ var getGroupedSamplesPositionsFromMp4 = ({
6767
6945
  }
6768
6946
  const samples = [];
6769
6947
  let timestamp = 0;
6948
+ const stscKeys = Array.from(stscBox.entries.keys());
6770
6949
  for (let i = 0;i < stcoBox.entries.length; i++) {
6771
6950
  const entry = stcoBox.entries[i];
6772
6951
  const chunk = i + 1;
6773
- const stscEntry = stscBox.entries.findLast((e) => e.firstChunk <= chunk);
6774
- if (!stscEntry) {
6952
+ const stscEntry = stscKeys.findLast((e) => e <= chunk);
6953
+ if (stscEntry === undefined) {
6954
+ throw new Error("should not be");
6955
+ }
6956
+ const samplesPerChunk = stscBox.entries.get(stscEntry);
6957
+ if (samplesPerChunk === undefined) {
6775
6958
  throw new Error("should not be");
6776
6959
  }
6777
6960
  samples.push({
@@ -6779,13 +6962,13 @@ var getGroupedSamplesPositionsFromMp4 = ({
6779
6962
  cts: timestamp,
6780
6963
  dts: timestamp,
6781
6964
  offset: Number(entry),
6782
- size: stszBox.sampleSize * stscEntry.samplesPerChunk,
6783
- duration: stscEntry.samplesPerChunk,
6965
+ size: stszBox.sampleSize * samplesPerChunk,
6966
+ duration: samplesPerChunk,
6784
6967
  isKeyframe: true,
6785
6968
  bigEndian,
6786
6969
  chunkSize: stszBox.sampleSize
6787
6970
  });
6788
- timestamp += stscEntry.samplesPerChunk;
6971
+ timestamp += samplesPerChunk;
6789
6972
  }
6790
6973
  return samples;
6791
6974
  };
@@ -6862,7 +7045,7 @@ var getSamplePositionsFromTrack = ({
6862
7045
  tkhdBox
6863
7046
  });
6864
7047
  return {
6865
- samplePositions: samplePositions.flat(1),
7048
+ samplePositions: samplePositions.map((s) => s.samples).flat(1),
6866
7049
  isComplete
6867
7050
  };
6868
7051
  }
@@ -7096,7 +7279,7 @@ var getDurationFromIsoBaseMedia = (parserState) => {
7096
7279
  const moovBox = getMoovBoxFromState({
7097
7280
  structureState: parserState.structure,
7098
7281
  isoState: parserState.iso,
7099
- mp4HeaderSegment: parserState.mp4HeaderSegment,
7282
+ mp4HeaderSegment: parserState.m3uPlaylistContext?.mp4HeaderSegment ?? null,
7100
7283
  mayUsePrecomputed: true
7101
7284
  });
7102
7285
  if (!moovBox) {
@@ -7202,7 +7385,12 @@ var hasHdr = (state) => {
7202
7385
 
7203
7386
  // src/containers/iso-base-media/get-keyframes.ts
7204
7387
  var getKeyframesFromIsoBaseMedia = (state) => {
7205
- const { videoTracks } = getTracksFromIsoBaseMedia(state, true);
7388
+ const { videoTracks } = getTracksFromIsoBaseMedia({
7389
+ isoState: state.iso,
7390
+ m3uPlaylistContext: state.m3uPlaylistContext,
7391
+ structure: state.structure,
7392
+ mayUsePrecomputed: true
7393
+ });
7206
7394
  const structure = state.structure.getIsoStructure();
7207
7395
  const moofBoxes = getMoofBoxes(structure.boxes);
7208
7396
  const tfraBoxes = getTfraBoxes(structure);
@@ -7356,7 +7544,7 @@ var getMetadataFromIsoBase = (state) => {
7356
7544
  const moov = getMoovBoxFromState({
7357
7545
  structureState: state.structure,
7358
7546
  isoState: state.iso,
7359
- mp4HeaderSegment: state.mp4HeaderSegment,
7547
+ mp4HeaderSegment: state.m3uPlaylistContext?.mp4HeaderSegment ?? null,
7360
7548
  mayUsePrecomputed: true
7361
7549
  });
7362
7550
  if (!moov) {
@@ -7622,12 +7810,81 @@ var getSeekingByteForFlac = ({
7622
7810
  return null;
7623
7811
  };
7624
7812
 
7625
- // src/state/video-section.ts
7626
- var isByteInMediaSection = ({
7627
- position,
7628
- mediaSections
7629
- }) => {
7630
- if (mediaSections.length === 0) {
7813
+ // src/containers/iso-base-media/find-keyframe-before-time.ts
7814
+ var findKeyframeBeforeTime = ({
7815
+ samplePositions,
7816
+ time,
7817
+ timescale,
7818
+ mediaSections,
7819
+ logLevel
7820
+ }) => {
7821
+ let videoByte = 0;
7822
+ let videoSample = null;
7823
+ for (const sample of samplePositions) {
7824
+ const ctsInSeconds = sample.cts / timescale;
7825
+ const dtsInSeconds = sample.dts / timescale;
7826
+ if (!sample.isKeyframe) {
7827
+ continue;
7828
+ }
7829
+ if (!(ctsInSeconds <= time || dtsInSeconds <= time)) {
7830
+ continue;
7831
+ }
7832
+ if (videoByte <= sample.offset) {
7833
+ videoByte = sample.offset;
7834
+ videoSample = sample;
7835
+ }
7836
+ }
7837
+ if (!videoSample) {
7838
+ throw new Error("No sample found");
7839
+ }
7840
+ const mediaSection = mediaSections.find((section) => videoSample.offset >= section.start && videoSample.offset < section.start + section.size);
7841
+ if (!mediaSection) {
7842
+ Log.trace(logLevel, "Found a sample, but the offset has not yet been marked as a video section yet. Not yet able to seek, but probably once we have started reading the next box.", videoSample);
7843
+ return null;
7844
+ }
7845
+ return videoSample.offset;
7846
+ };
7847
+
7848
+ // src/containers/iso-base-media/find-track-to-seek.ts
7849
+ var findAnyTrackWithSamplePositions = (allTracks, struc) => {
7850
+ for (const track of allTracks) {
7851
+ if (track.type === "video" || track.type === "audio") {
7852
+ const { samplePositions } = getSamplePositionsFromTrack({
7853
+ trakBox: track.trakBox,
7854
+ moofBoxes: getMoofBoxes(struc.boxes),
7855
+ tfraBoxes: getTfraBoxes(struc)
7856
+ });
7857
+ if (samplePositions.length === 0) {
7858
+ continue;
7859
+ }
7860
+ return { track, samplePositions };
7861
+ }
7862
+ }
7863
+ return null;
7864
+ };
7865
+ var findTrackToSeek = (allTracks, structure) => {
7866
+ const firstVideoTrack = allTracks.find((t) => t.type === "video");
7867
+ const struc = structure.getIsoStructure();
7868
+ if (!firstVideoTrack) {
7869
+ return findAnyTrackWithSamplePositions(allTracks, struc);
7870
+ }
7871
+ const { samplePositions } = getSamplePositionsFromTrack({
7872
+ trakBox: firstVideoTrack.trakBox,
7873
+ moofBoxes: getMoofBoxes(struc.boxes),
7874
+ tfraBoxes: getTfraBoxes(struc)
7875
+ });
7876
+ if (samplePositions.length === 0) {
7877
+ return findAnyTrackWithSamplePositions(allTracks, struc);
7878
+ }
7879
+ return { track: firstVideoTrack, samplePositions };
7880
+ };
7881
+
7882
+ // src/state/video-section.ts
7883
+ var isByteInMediaSection = ({
7884
+ position,
7885
+ mediaSections
7886
+ }) => {
7887
+ if (mediaSections.length === 0) {
7631
7888
  return "no-section-defined";
7632
7889
  }
7633
7890
  for (const section of mediaSections) {
@@ -7690,42 +7947,13 @@ var mediaSectionState = () => {
7690
7947
  };
7691
7948
  };
7692
7949
 
7693
- // src/containers/iso-base-media/find-keyframe-before-time.ts
7694
- var findKeyframeBeforeTime = ({
7695
- samplePositions,
7696
- time,
7697
- timescale,
7698
- mediaSections,
7699
- logLevel
7700
- }) => {
7701
- let byte = 0;
7702
- let sam = null;
7703
- for (const sample of samplePositions) {
7704
- const ctsInSeconds = sample.cts / timescale;
7705
- const dtsInSeconds = sample.dts / timescale;
7706
- if ((ctsInSeconds <= time || dtsInSeconds <= time) && byte <= sample.offset && sample.isKeyframe) {
7707
- byte = sample.offset;
7708
- sam = sample;
7709
- }
7710
- }
7711
- if (!sam) {
7712
- throw new Error("No sample found");
7713
- }
7714
- const mediaSection = mediaSections.find((section) => sam.offset >= section.start && sam.offset < section.start + section.size);
7715
- if (!mediaSection) {
7716
- Log.trace(logLevel, "Found a sample, but the offset has not yet been marked as a video section yet. Not yet able to seek, but probably once we have started reading the next box.", sam);
7717
- return null;
7718
- }
7719
- return sam.offset;
7720
- };
7721
-
7722
7950
  // src/containers/iso-base-media/get-sample-position-bounds.ts
7723
7951
  var getSamplePositionBounds = (samplePositions, timescale) => {
7724
7952
  let min = Infinity;
7725
7953
  let max = -Infinity;
7726
7954
  for (const samplePosition of samplePositions) {
7727
7955
  const timestampMin = Math.min(samplePosition.cts, samplePosition.dts);
7728
- const timestampMax = Math.max(samplePosition.cts, samplePosition.dts);
7956
+ const timestampMax = Math.max(samplePosition.cts, samplePosition.dts) + (samplePosition.duration ?? 0);
7729
7957
  if (timestampMin < min) {
7730
7958
  min = timestampMin;
7731
7959
  }
@@ -7740,10 +7968,10 @@ var getSamplePositionBounds = (samplePositions, timescale) => {
7740
7968
  var findBestSegmentFromTfra = ({
7741
7969
  mfra,
7742
7970
  time,
7743
- firstVideoTrack,
7971
+ firstTrack,
7744
7972
  timescale
7745
7973
  }) => {
7746
- const tfra = mfra.find((b) => b.type === "tfra-box" && b.trackId === firstVideoTrack.trackId);
7974
+ const tfra = mfra.find((b) => b.type === "tfra-box" && b.trackId === firstTrack.trackId);
7747
7975
  if (!tfra) {
7748
7976
  return null;
7749
7977
  }
@@ -7764,130 +7992,191 @@ var findBestSegmentFromTfra = ({
7764
7992
  };
7765
7993
  };
7766
7994
 
7995
+ // src/containers/iso-base-media/get-seeking-byte-from-fragmented-mp4.ts
7996
+ var getSeekingByteFromFragmentedMp4 = async ({
7997
+ info,
7998
+ time,
7999
+ logLevel,
8000
+ currentPosition,
8001
+ isoState,
8002
+ allTracks,
8003
+ isLastChunkInPlaylist
8004
+ }) => {
8005
+ const firstVideoTrack = allTracks.find((t) => t.type === "video");
8006
+ const firstTrack = firstVideoTrack ?? allTracks.find((t) => t.type === "audio");
8007
+ if (!firstTrack) {
8008
+ throw new Error("no video and no audio tracks");
8009
+ }
8010
+ const tkhdBox = getTkhdBox(firstTrack.trakBox);
8011
+ if (!tkhdBox) {
8012
+ throw new Error("Expected tkhd box in trak box");
8013
+ }
8014
+ const { samplePositions: samplePositionsArray } = collectSamplePositionsFromMoofBoxes({
8015
+ moofBoxes: info.moofBoxes,
8016
+ tfraBoxes: info.tfraBoxes,
8017
+ tkhdBox
8018
+ });
8019
+ Log.trace(logLevel, "Fragmented MP4 - Checking if we have seeking info for this time range");
8020
+ for (const positions of samplePositionsArray) {
8021
+ const { min, max } = getSamplePositionBounds(positions.samples, firstTrack.timescale);
8022
+ if (min <= time && (positions.isLastFragment || isLastChunkInPlaylist || time <= max)) {
8023
+ Log.trace(logLevel, `Fragmented MP4 - Found that we have seeking info for this time range: ${min} <= ${time} <= ${max}`);
8024
+ const kf = findKeyframeBeforeTime({
8025
+ samplePositions: positions.samples,
8026
+ time,
8027
+ timescale: firstTrack.timescale,
8028
+ logLevel,
8029
+ mediaSections: info.mediaSections
8030
+ });
8031
+ if (kf) {
8032
+ return {
8033
+ type: "do-seek",
8034
+ byte: kf
8035
+ };
8036
+ }
8037
+ }
8038
+ }
8039
+ const atom = await (info.mfraAlreadyLoaded ? Promise.resolve(info.mfraAlreadyLoaded) : isoState.mfra.triggerLoad());
8040
+ if (atom) {
8041
+ const moofOffset = findBestSegmentFromTfra({
8042
+ mfra: atom,
8043
+ time,
8044
+ firstTrack,
8045
+ timescale: firstTrack.timescale
8046
+ });
8047
+ if (moofOffset !== null && !(moofOffset.start <= currentPosition && currentPosition < moofOffset.end)) {
8048
+ Log.verbose(logLevel, `Fragmented MP4 - Found based on mfra information that we should seek to: ${moofOffset.start} ${moofOffset.end}`);
8049
+ return {
8050
+ type: "intermediary-seek",
8051
+ byte: moofOffset.start
8052
+ };
8053
+ }
8054
+ }
8055
+ Log.trace(logLevel, "Fragmented MP4 - No seeking info found for this time range.");
8056
+ if (isByteInMediaSection({
8057
+ position: currentPosition,
8058
+ mediaSections: info.mediaSections
8059
+ }) !== "in-section") {
8060
+ return {
8061
+ type: "valid-but-must-wait"
8062
+ };
8063
+ }
8064
+ Log.trace(logLevel, "Fragmented MP4 - Inside the wrong video section, skipping to the end of the section");
8065
+ const mediaSection = getCurrentMediaSection({
8066
+ offset: currentPosition,
8067
+ mediaSections: info.mediaSections
8068
+ });
8069
+ if (!mediaSection) {
8070
+ throw new Error("No video section defined");
8071
+ }
8072
+ return {
8073
+ type: "intermediary-seek",
8074
+ byte: mediaSection.start + mediaSection.size
8075
+ };
8076
+ };
8077
+
7767
8078
  // src/containers/iso-base-media/get-seeking-byte.ts
7768
- var getSeekingByteFromIsoBaseMedia = async ({
8079
+ var getSeekingByteFromIsoBaseMedia = ({
7769
8080
  info,
7770
8081
  time,
7771
8082
  logLevel,
7772
8083
  currentPosition,
7773
8084
  isoState,
7774
- mp4HeaderSegment,
8085
+ m3uPlaylistContext,
7775
8086
  structure
7776
8087
  }) => {
7777
- const tracks2 = getTracksFromMoovBox(info.moovBox);
8088
+ const tracks2 = getTracksFromIsoBaseMedia({
8089
+ isoState,
8090
+ m3uPlaylistContext,
8091
+ structure,
8092
+ mayUsePrecomputed: false
8093
+ });
7778
8094
  const allTracks = [
7779
8095
  ...tracks2.videoTracks,
7780
8096
  ...tracks2.audioTracks,
7781
8097
  ...tracks2.otherTracks
7782
8098
  ];
7783
8099
  const hasMoov = Boolean(getMoovBoxFromState({
7784
- mp4HeaderSegment,
7785
8100
  structureState: structure,
7786
8101
  isoState,
7787
- mayUsePrecomputed: false
8102
+ mayUsePrecomputed: false,
8103
+ mp4HeaderSegment: m3uPlaylistContext?.mp4HeaderSegment ?? null
7788
8104
  }));
7789
8105
  if (!hasMoov) {
7790
8106
  Log.trace(logLevel, "No moov box found, must wait");
7791
- return {
8107
+ return Promise.resolve({
7792
8108
  type: "valid-but-must-wait"
7793
- };
7794
- }
7795
- const firstVideoTrack = allTracks.find((t) => t.type === "video");
7796
- if (!firstVideoTrack) {
7797
- throw new Error("No video track found");
8109
+ });
7798
8110
  }
7799
- const { timescale } = firstVideoTrack;
7800
8111
  if (info.moofBoxes.length > 0) {
7801
- const tkhdBox = getTkhdBox(firstVideoTrack.trakBox);
7802
- if (!tkhdBox) {
7803
- throw new Error("Expected tkhd box in trak box");
7804
- }
7805
- const { samplePositions: samplePositionsArray } = collectSamplePositionsFromMoofBoxes({
7806
- moofBoxes: info.moofBoxes,
7807
- tfraBoxes: info.tfraBoxes,
7808
- tkhdBox
7809
- });
7810
- Log.trace(logLevel, "Fragmented MP4 - Checking if we have seeking info for this time range");
7811
- for (const positions of samplePositionsArray) {
7812
- const { min, max } = getSamplePositionBounds(positions, timescale);
7813
- if (min <= time && time <= max) {
7814
- Log.trace(logLevel, `Fragmented MP4 - Found that we have seeking info for this time range: ${min} <= ${time} <= ${max}`);
7815
- const kf = findKeyframeBeforeTime({
7816
- samplePositions: positions,
7817
- time,
7818
- timescale,
7819
- logLevel,
7820
- mediaSections: info.mediaSections
7821
- });
7822
- if (kf) {
7823
- return {
7824
- type: "do-seek",
7825
- byte: kf
7826
- };
7827
- }
7828
- }
7829
- }
7830
- const atom = await (info.mfraAlreadyLoaded ? Promise.resolve(info.mfraAlreadyLoaded) : isoState.mfra.triggerLoad());
7831
- if (atom) {
7832
- const moofOffset = findBestSegmentFromTfra({
7833
- mfra: atom,
7834
- time,
7835
- firstVideoTrack,
7836
- timescale
7837
- });
7838
- if (moofOffset !== null && !(moofOffset.start <= currentPosition && currentPosition < moofOffset.end)) {
7839
- Log.verbose(logLevel, `Fragmented MP4 - Found based on mfra information that we should seek to: ${moofOffset.start} ${moofOffset.end}`);
7840
- return {
7841
- type: "intermediary-seek",
7842
- byte: moofOffset.start
7843
- };
7844
- }
7845
- }
7846
- Log.trace(logLevel, "Fragmented MP4 - No seeking info found for this time range.");
7847
- if (isByteInMediaSection({
7848
- position: currentPosition,
7849
- mediaSections: info.mediaSections
7850
- }) !== "in-section") {
7851
- return {
7852
- type: "valid-but-must-wait"
7853
- };
7854
- }
7855
- Log.trace(logLevel, "Fragmented MP4 - Inside the wrong video section, skipping to the end of the section");
7856
- const mediaSection = getCurrentMediaSection({
7857
- offset: currentPosition,
7858
- mediaSections: info.mediaSections
8112
+ return getSeekingByteFromFragmentedMp4({
8113
+ info,
8114
+ time,
8115
+ logLevel,
8116
+ currentPosition,
8117
+ isoState,
8118
+ allTracks,
8119
+ isLastChunkInPlaylist: m3uPlaylistContext?.isLastChunkInPlaylist ?? false
7859
8120
  });
7860
- if (!mediaSection) {
7861
- throw new Error("No video section defined");
7862
- }
7863
- return {
7864
- type: "intermediary-seek",
7865
- byte: mediaSection.start + mediaSection.size
7866
- };
7867
8121
  }
7868
- const { samplePositions, isComplete } = getSamplePositionsFromTrack({
7869
- trakBox: firstVideoTrack.trakBox,
7870
- moofBoxes: info.moofBoxes,
7871
- tfraBoxes: info.tfraBoxes
7872
- });
7873
- if (!isComplete) {
7874
- throw new Error("Incomplete sample positions");
8122
+ const trackWithSamplePositions = findTrackToSeek(allTracks, structure);
8123
+ if (!trackWithSamplePositions) {
8124
+ return Promise.resolve({
8125
+ type: "valid-but-must-wait"
8126
+ });
7875
8127
  }
8128
+ const { track, samplePositions } = trackWithSamplePositions;
7876
8129
  const keyframe = findKeyframeBeforeTime({
7877
8130
  samplePositions,
7878
8131
  time,
7879
- timescale,
8132
+ timescale: track.timescale,
7880
8133
  logLevel,
7881
8134
  mediaSections: info.mediaSections
7882
8135
  });
7883
8136
  if (keyframe) {
7884
- return {
8137
+ return Promise.resolve({
7885
8138
  type: "do-seek",
7886
8139
  byte: keyframe
7887
- };
8140
+ });
7888
8141
  }
7889
- return {
8142
+ return Promise.resolve({
7890
8143
  type: "invalid"
8144
+ });
8145
+ };
8146
+
8147
+ // src/containers/m3u/get-seeking-byte.ts
8148
+ var clearM3uStateInPrepareForSeek = ({
8149
+ m3uState,
8150
+ logLevel
8151
+ }) => {
8152
+ const selectedPlaylists = m3uState.getSelectedPlaylists();
8153
+ for (const playlistUrl of selectedPlaylists) {
8154
+ const streamRun = m3uState.getM3uStreamRun(playlistUrl);
8155
+ if (streamRun) {
8156
+ streamRun.abort();
8157
+ }
8158
+ Log.trace(logLevel, "Clearing M3U stream run for", playlistUrl);
8159
+ m3uState.setM3uStreamRun(playlistUrl, null);
8160
+ }
8161
+ m3uState.clearAllChunksProcessed();
8162
+ m3uState.sampleSorter.clearSamples();
8163
+ };
8164
+ var getSeekingByteForM3u8 = ({
8165
+ time,
8166
+ currentPosition,
8167
+ m3uState,
8168
+ logLevel
8169
+ }) => {
8170
+ clearM3uStateInPrepareForSeek({ m3uState, logLevel });
8171
+ const selectedPlaylists = m3uState.getSelectedPlaylists();
8172
+ for (const playlistUrl of selectedPlaylists) {
8173
+ m3uState.setSeekToSecondsToProcess(playlistUrl, {
8174
+ targetTime: time
8175
+ });
8176
+ }
8177
+ return {
8178
+ type: "do-seek",
8179
+ byte: currentPosition
7891
8180
  };
7892
8181
  };
7893
8182
 
@@ -8525,9 +8814,10 @@ var getSeekingByte = ({
8525
8814
  transportStream,
8526
8815
  webmState,
8527
8816
  mediaSection,
8528
- mp4HeaderSegment,
8817
+ m3uPlaylistContext,
8529
8818
  structure,
8530
- riffState
8819
+ riffState,
8820
+ m3uState
8531
8821
  }) => {
8532
8822
  if (info.type === "iso-base-media-seeking-hints") {
8533
8823
  return getSeekingByteFromIsoBaseMedia({
@@ -8536,8 +8826,8 @@ var getSeekingByte = ({
8536
8826
  logLevel,
8537
8827
  currentPosition,
8538
8828
  isoState,
8539
- mp4HeaderSegment,
8540
- structure
8829
+ structure,
8830
+ m3uPlaylistContext
8541
8831
  });
8542
8832
  }
8543
8833
  if (info.type === "wav-seeking-hints") {
@@ -8602,6 +8892,14 @@ var getSeekingByte = ({
8602
8892
  seekingHints: info
8603
8893
  }));
8604
8894
  }
8895
+ if (info.type === "m3u8-seeking-hints") {
8896
+ return Promise.resolve(getSeekingByteForM3u8({
8897
+ time,
8898
+ currentPosition,
8899
+ m3uState,
8900
+ logLevel
8901
+ }));
8902
+ }
8605
8903
  throw new Error(`Unknown seeking info type: ${info}`);
8606
8904
  };
8607
8905
 
@@ -8690,6 +8988,13 @@ var getSeekingHintsFromMp4 = ({
8690
8988
  };
8691
8989
  var setSeekingHintsForMp4 = ({}) => {};
8692
8990
 
8991
+ // src/containers/m3u/seeking-hints.ts
8992
+ var getSeekingHintsForM3u = () => {
8993
+ return {
8994
+ type: "m3u8-seeking-hints"
8995
+ };
8996
+ };
8997
+
8693
8998
  // src/containers/mp3/seeking-hints.ts
8694
8999
  var getSeekingHintsForMp3 = ({
8695
9000
  mp3State,
@@ -8836,7 +9141,7 @@ var setSeekingHintsForWebm = ({
8836
9141
  // src/get-seeking-hints.ts
8837
9142
  var getSeekingHints = ({
8838
9143
  structureState,
8839
- mp4HeaderSegment,
9144
+ m3uPlaylistContext,
8840
9145
  mediaSectionState: mediaSectionState2,
8841
9146
  isoState,
8842
9147
  transportStream,
@@ -8858,7 +9163,7 @@ var getSeekingHints = ({
8858
9163
  return getSeekingHintsFromMp4({
8859
9164
  structureState,
8860
9165
  isoState,
8861
- mp4HeaderSegment,
9166
+ mp4HeaderSegment: m3uPlaylistContext?.mp4HeaderSegment ?? null,
8862
9167
  mediaSectionState: mediaSectionState2
8863
9168
  });
8864
9169
  }
@@ -8901,7 +9206,10 @@ var getSeekingHints = ({
8901
9206
  samplesObserved
8902
9207
  });
8903
9208
  }
8904
- throw new Error(`Seeking is not supported for this format: ${structure.type}`);
9209
+ if (structure.type === "m3u") {
9210
+ return getSeekingHintsForM3u();
9211
+ }
9212
+ throw new Error(`Seeking is not supported for this format: ${structure}`);
8905
9213
  };
8906
9214
 
8907
9215
  // src/seek-backwards.ts
@@ -8912,10 +9220,12 @@ var seekBackwards = async ({
8912
9220
  src,
8913
9221
  controller,
8914
9222
  logLevel,
8915
- currentReader
9223
+ currentReader,
9224
+ prefetchCache
8916
9225
  }) => {
8917
9226
  const howManyBytesWeCanGoBack = iterator.counter.getDiscardedOffset();
8918
9227
  if (iterator.counter.getOffset() - howManyBytesWeCanGoBack <= seekTo) {
9228
+ Log.verbose(logLevel, `Seeking back to ${seekTo}`);
8919
9229
  iterator.skipTo(seekTo);
8920
9230
  return;
8921
9231
  }
@@ -8924,7 +9234,9 @@ var seekBackwards = async ({
8924
9234
  const { reader: newReader } = await readerInterface.read({
8925
9235
  src,
8926
9236
  range: seekTo,
8927
- controller
9237
+ controller,
9238
+ logLevel,
9239
+ prefetchCache
8928
9240
  });
8929
9241
  iterator.replaceData(new Uint8Array([]), seekTo);
8930
9242
  Log.verbose(logLevel, `Re-reading took ${Date.now() - time}ms. New position: ${iterator.counter.getOffset()}`);
@@ -8994,7 +9306,8 @@ var seekForward = async ({
8994
9306
  readerInterface,
8995
9307
  src,
8996
9308
  controller,
8997
- discardReadBytes
9309
+ discardReadBytes,
9310
+ prefetchCache
8998
9311
  }) => {
8999
9312
  if (userInitiated) {
9000
9313
  disallowForwardSeekIfSamplesAreNeeded({
@@ -9015,7 +9328,9 @@ var seekForward = async ({
9015
9328
  const { reader: newReader } = await readerInterface.read({
9016
9329
  src,
9017
9330
  range: seekTo,
9018
- controller
9331
+ controller,
9332
+ logLevel,
9333
+ prefetchCache
9019
9334
  });
9020
9335
  iterator.skipTo(seekTo);
9021
9336
  await discardReadBytes(true);
@@ -9038,7 +9353,8 @@ var performSeek = async ({
9038
9353
  readerInterface,
9039
9354
  src,
9040
9355
  discardReadBytes,
9041
- fields
9356
+ fields,
9357
+ prefetchCache
9042
9358
  }) => {
9043
9359
  const byteInMediaSection = isByteInMediaSection({
9044
9360
  position: seekTo,
@@ -9087,7 +9403,8 @@ var performSeek = async ({
9087
9403
  readerInterface,
9088
9404
  src,
9089
9405
  controller,
9090
- discardReadBytes
9406
+ discardReadBytes,
9407
+ prefetchCache
9091
9408
  });
9092
9409
  } else {
9093
9410
  await seekBackwards({
@@ -9097,7 +9414,8 @@ var performSeek = async ({
9097
9414
  logLevel,
9098
9415
  currentReader,
9099
9416
  readerInterface,
9100
- src
9417
+ src,
9418
+ prefetchCache
9101
9419
  });
9102
9420
  }
9103
9421
  await controller._internals.checkForAbortAndPause();
@@ -9110,7 +9428,7 @@ var turnSeekIntoByte = async ({
9110
9428
  logLevel,
9111
9429
  iterator,
9112
9430
  structureState,
9113
- mp4HeaderSegment,
9431
+ m3uPlaylistContext,
9114
9432
  isoState,
9115
9433
  transportStream,
9116
9434
  tracksState,
@@ -9121,7 +9439,8 @@ var turnSeekIntoByte = async ({
9121
9439
  riffState,
9122
9440
  mp3State,
9123
9441
  contentLength,
9124
- aacState
9442
+ aacState,
9443
+ m3uState
9125
9444
  }) => {
9126
9445
  const mediaSections = mediaSectionState2.getMediaSections();
9127
9446
  if (mediaSections.length === 0) {
@@ -9138,7 +9457,6 @@ var turnSeekIntoByte = async ({
9138
9457
  riffState,
9139
9458
  samplesObserved,
9140
9459
  structureState,
9141
- mp4HeaderSegment,
9142
9460
  mediaSectionState: mediaSectionState2,
9143
9461
  isoState,
9144
9462
  transportStream,
@@ -9148,7 +9466,8 @@ var turnSeekIntoByte = async ({
9148
9466
  flacState,
9149
9467
  mp3State,
9150
9468
  contentLength,
9151
- aacState
9469
+ aacState,
9470
+ m3uPlaylistContext
9152
9471
  });
9153
9472
  if (!seekingHints) {
9154
9473
  Log.trace(logLevel, "No seeking info, cannot seek yet");
@@ -9165,18 +9484,13 @@ var turnSeekIntoByte = async ({
9165
9484
  transportStream,
9166
9485
  webmState,
9167
9486
  mediaSection: mediaSectionState2,
9168
- mp4HeaderSegment,
9487
+ m3uPlaylistContext,
9169
9488
  structure: structureState,
9170
- riffState
9489
+ riffState,
9490
+ m3uState
9171
9491
  });
9172
9492
  return seekingByte;
9173
9493
  }
9174
- if (seek2.type === "byte") {
9175
- return {
9176
- type: "do-seek",
9177
- byte: seek2.byte
9178
- };
9179
- }
9180
9494
  throw new Error(`Cannot process seek request for ${seek2}: ${JSON.stringify(seek2)}`);
9181
9495
  };
9182
9496
  var getWorkOnSeekRequestOptions = (state) => {
@@ -9190,7 +9504,7 @@ var getWorkOnSeekRequestOptions = (state) => {
9190
9504
  contentLength: state.contentLength,
9191
9505
  readerInterface: state.readerInterface,
9192
9506
  mediaSection: state.mediaSection,
9193
- mp4HeaderSegment: state.mp4HeaderSegment,
9507
+ m3uPlaylistContext: state.m3uPlaylistContext,
9194
9508
  mode: state.mode,
9195
9509
  seekInfiniteLoop: state.seekInfiniteLoop,
9196
9510
  currentReader: state.currentReader,
@@ -9204,7 +9518,9 @@ var getWorkOnSeekRequestOptions = (state) => {
9204
9518
  samplesObserved: state.samplesObserved,
9205
9519
  riffState: state.riff,
9206
9520
  mp3State: state.mp3,
9207
- aacState: state.aac
9521
+ aacState: state.aac,
9522
+ m3uState: state.m3u,
9523
+ prefetchCache: state.prefetchCache
9208
9524
  };
9209
9525
  };
9210
9526
  var workOnSeekRequest = async (options) => {
@@ -9212,7 +9528,7 @@ var workOnSeekRequest = async (options) => {
9212
9528
  logLevel,
9213
9529
  controller,
9214
9530
  mediaSection,
9215
- mp4HeaderSegment,
9531
+ m3uPlaylistContext,
9216
9532
  isoState,
9217
9533
  iterator,
9218
9534
  structureState,
@@ -9232,20 +9548,22 @@ var workOnSeekRequest = async (options) => {
9232
9548
  samplesObserved,
9233
9549
  riffState,
9234
9550
  mp3State,
9235
- aacState
9551
+ aacState,
9552
+ prefetchCache,
9553
+ m3uState
9236
9554
  } = options;
9237
9555
  const seek2 = controller._internals.seekSignal.getSeek();
9238
9556
  if (!seek2) {
9239
9557
  return;
9240
9558
  }
9241
- Log.trace(logLevel, `Has seek request: ${JSON.stringify(seek2)}`);
9559
+ Log.trace(logLevel, `Has seek request for ${src}: ${JSON.stringify(seek2)}`);
9242
9560
  const resolution = await turnSeekIntoByte({
9243
9561
  seek: seek2,
9244
9562
  mediaSectionState: mediaSection,
9245
9563
  logLevel,
9246
9564
  iterator,
9247
9565
  structureState,
9248
- mp4HeaderSegment,
9566
+ m3uPlaylistContext,
9249
9567
  isoState,
9250
9568
  transportStream,
9251
9569
  tracksState,
@@ -9256,7 +9574,8 @@ var workOnSeekRequest = async (options) => {
9256
9574
  riffState,
9257
9575
  mp3State,
9258
9576
  contentLength,
9259
- aacState
9577
+ aacState,
9578
+ m3uState
9260
9579
  });
9261
9580
  Log.trace(logLevel, `Seek action: ${JSON.stringify(resolution)}`);
9262
9581
  if (resolution.type === "intermediary-seek") {
@@ -9274,7 +9593,8 @@ var workOnSeekRequest = async (options) => {
9274
9593
  readerInterface,
9275
9594
  src,
9276
9595
  discardReadBytes,
9277
- fields
9596
+ fields,
9597
+ prefetchCache
9278
9598
  });
9279
9599
  return;
9280
9600
  }
@@ -9293,7 +9613,8 @@ var workOnSeekRequest = async (options) => {
9293
9613
  readerInterface,
9294
9614
  src,
9295
9615
  discardReadBytes,
9296
- fields
9616
+ fields,
9617
+ prefetchCache
9297
9618
  });
9298
9619
  const { hasChanged } = controller._internals.seekSignal.clearSeekIfStillSame(seek2);
9299
9620
  if (hasChanged) {
@@ -9914,12 +10235,6 @@ var parseAac = async (state) => {
9914
10235
  return Promise.resolve(null);
9915
10236
  };
9916
10237
 
9917
- // src/skip.ts
9918
- var makeSkip = (skipTo) => ({
9919
- action: "skip",
9920
- skipTo
9921
- });
9922
-
9923
10238
  // src/containers/flac/get-block-size.ts
9924
10239
  var getBlockSize = (iterator) => {
9925
10240
  const bits = iterator.getBits(4);
@@ -10373,11 +10688,12 @@ var calculateFlatSamples = (state) => {
10373
10688
  samplePosition
10374
10689
  };
10375
10690
  });
10376
- }).flat(1);
10691
+ });
10377
10692
  return flatSamples;
10378
10693
  };
10379
10694
  var cachedSamplePositionsState = () => {
10380
10695
  const cachedForMdatStart = {};
10696
+ const jumpMarksForMdatStart = {};
10381
10697
  return {
10382
10698
  getSamples: (mdatStart) => {
10383
10699
  if (cachedForMdatStart[mdatStart]) {
@@ -10387,6 +10703,12 @@ var cachedSamplePositionsState = () => {
10387
10703
  },
10388
10704
  setSamples: (mdatStart, samples) => {
10389
10705
  cachedForMdatStart[mdatStart] = samples;
10706
+ },
10707
+ setJumpMarks: (mdatStart, marks) => {
10708
+ jumpMarksForMdatStart[mdatStart] = marks;
10709
+ },
10710
+ getJumpMarks: (mdatStart) => {
10711
+ return jumpMarksForMdatStart[mdatStart];
10390
10712
  }
10391
10713
  };
10392
10714
  };
@@ -10416,17 +10738,21 @@ var makeCanSkipTracksState = ({
10416
10738
  hasVideoTrackHandlers,
10417
10739
  structure
10418
10740
  }) => {
10741
+ const doFieldsNeedTracks = () => {
10742
+ const keys = Object.keys(fields ?? {});
10743
+ const selectedKeys = keys.filter((k) => fields[k]);
10744
+ return selectedKeys.some((k) => needsTracksForField({
10745
+ field: k,
10746
+ structure: structure.getStructureOrNull()
10747
+ }));
10748
+ };
10419
10749
  return {
10750
+ doFieldsNeedTracks,
10420
10751
  canSkipTracks: () => {
10421
10752
  if (hasAudioTrackHandlers || hasVideoTrackHandlers) {
10422
10753
  return false;
10423
10754
  }
10424
- const keys = Object.keys(fields ?? {});
10425
- const selectedKeys = keys.filter((k) => fields[k]);
10426
- return !selectedKeys.some((k) => needsTracksForField({
10427
- field: k,
10428
- structure: structure.getStructureOrNull()
10429
- }));
10755
+ return !doFieldsNeedTracks();
10430
10756
  }
10431
10757
  };
10432
10758
  };
@@ -10546,7 +10872,7 @@ var getMoovAtom = async ({
10546
10872
  endOfMdat,
10547
10873
  state
10548
10874
  }) => {
10549
- const headerSegment = state.mp4HeaderSegment;
10875
+ const headerSegment = state.m3uPlaylistContext?.mp4HeaderSegment;
10550
10876
  if (headerSegment) {
10551
10877
  const segment = getMoovFromFromIsoStructure(headerSegment);
10552
10878
  if (!segment) {
@@ -10559,7 +10885,9 @@ var getMoovAtom = async ({
10559
10885
  const { reader } = await state.readerInterface.read({
10560
10886
  src: state.src,
10561
10887
  range: endOfMdat,
10562
- controller: state.controller
10888
+ controller: state.controller,
10889
+ logLevel: state.logLevel,
10890
+ prefetchCache: state.prefetchCache
10563
10891
  });
10564
10892
  const onAudioTrack = state.onAudioTrack ? async ({ track, container }) => {
10565
10893
  await registerAudioTrack({
@@ -10616,8 +10944,8 @@ var getMoovAtom = async ({
10616
10944
  onlyIfMdatAtomExpected: null,
10617
10945
  contentLength: state.contentLength - endOfMdat
10618
10946
  });
10619
- if (box) {
10620
- boxes.push(box);
10947
+ if (box.type === "box") {
10948
+ boxes.push(box.box);
10621
10949
  }
10622
10950
  if (iterator.counter.getOffset() + endOfMdat > state.contentLength) {
10623
10951
  throw new Error("Read past end of file");
@@ -10634,6 +10962,121 @@ var getMoovAtom = async ({
10634
10962
  return moov;
10635
10963
  };
10636
10964
 
10965
+ // src/containers/iso-base-media/mdat/calculate-jump-marks.ts
10966
+ var MAX_SPREAD_IN_SECONDS = 8;
10967
+ var getKey = (samplePositionTrack) => {
10968
+ return `${samplePositionTrack.track.trackId}-${samplePositionTrack.samplePosition.dts}`;
10969
+ };
10970
+ var findBestJump = ({
10971
+ allSamplesSortedByOffset,
10972
+ visited,
10973
+ progresses
10974
+ }) => {
10975
+ const minProgress = Math.min(...Object.values(progresses));
10976
+ const trackNumberWithLowestProgress = Object.entries(progresses).find(([, progress]) => progress === minProgress)?.[0];
10977
+ const firstSampleAboveMinProgress = allSamplesSortedByOffset.findIndex((sample) => sample.track.trackId === Number(trackNumberWithLowestProgress) && !visited.has(getKey(sample)));
10978
+ return firstSampleAboveMinProgress;
10979
+ };
10980
+ var calculateJumpMarks = (samplePositionTracks, endOfMdat) => {
10981
+ const progresses = {};
10982
+ for (const track of samplePositionTracks) {
10983
+ progresses[track[0].track.trackId] = 0;
10984
+ }
10985
+ const jumpMarks = [];
10986
+ const allSamplesSortedByOffset = samplePositionTracks.flat(1).sort((a, b) => a.samplePosition.offset - b.samplePosition.offset);
10987
+ let indexToVisit = 0;
10988
+ const visited = new Set;
10989
+ let rollOverToProcess = false;
10990
+ const increaseIndex = () => {
10991
+ indexToVisit++;
10992
+ if (indexToVisit >= allSamplesSortedByOffset.length) {
10993
+ rollOverToProcess = true;
10994
+ indexToVisit = 0;
10995
+ }
10996
+ };
10997
+ let lastVisitedSample = null;
10998
+ const addJumpMark = ({
10999
+ firstSampleAboveMinProgress
11000
+ }) => {
11001
+ if (!lastVisitedSample) {
11002
+ throw new Error("no last visited sample");
11003
+ }
11004
+ const jumpMark = {
11005
+ afterSampleWithOffset: lastVisitedSample.samplePosition.offset,
11006
+ jumpToOffset: allSamplesSortedByOffset[firstSampleAboveMinProgress].samplePosition.offset
11007
+ };
11008
+ indexToVisit = firstSampleAboveMinProgress;
11009
+ jumpMarks.push(jumpMark);
11010
+ };
11011
+ const addFinalJumpIfNecessary = () => {
11012
+ if (indexToVisit === allSamplesSortedByOffset.length - 1) {
11013
+ return;
11014
+ }
11015
+ jumpMarks.push({
11016
+ afterSampleWithOffset: allSamplesSortedByOffset[indexToVisit].samplePosition.offset,
11017
+ jumpToOffset: endOfMdat
11018
+ });
11019
+ };
11020
+ const considerJump = () => {
11021
+ const firstSampleAboveMinProgress = findBestJump({
11022
+ allSamplesSortedByOffset,
11023
+ visited,
11024
+ progresses
11025
+ });
11026
+ if (firstSampleAboveMinProgress > -1 && firstSampleAboveMinProgress !== indexToVisit + 1) {
11027
+ addJumpMark({ firstSampleAboveMinProgress });
11028
+ indexToVisit = firstSampleAboveMinProgress;
11029
+ } else {
11030
+ while (true) {
11031
+ increaseIndex();
11032
+ if (!visited.has(getKey(allSamplesSortedByOffset[indexToVisit]))) {
11033
+ break;
11034
+ }
11035
+ }
11036
+ }
11037
+ };
11038
+ while (true) {
11039
+ const currentSamplePosition = allSamplesSortedByOffset[indexToVisit];
11040
+ const sampleKey = getKey(currentSamplePosition);
11041
+ if (visited.has(sampleKey)) {
11042
+ considerJump();
11043
+ continue;
11044
+ }
11045
+ visited.add(sampleKey);
11046
+ if (rollOverToProcess) {
11047
+ if (!lastVisitedSample) {
11048
+ throw new Error("no last visited sample");
11049
+ }
11050
+ jumpMarks.push({
11051
+ afterSampleWithOffset: lastVisitedSample.samplePosition.offset,
11052
+ jumpToOffset: currentSamplePosition.samplePosition.offset
11053
+ });
11054
+ rollOverToProcess = false;
11055
+ }
11056
+ lastVisitedSample = currentSamplePosition;
11057
+ if (visited.size === allSamplesSortedByOffset.length) {
11058
+ addFinalJumpIfNecessary();
11059
+ break;
11060
+ }
11061
+ const timestamp = currentSamplePosition.samplePosition.dts / currentSamplePosition.track.timescale;
11062
+ progresses[currentSamplePosition.track.trackId] = timestamp;
11063
+ const progressValues = Object.values(progresses);
11064
+ const maxProgress = Math.max(...progressValues);
11065
+ const minProgress = Math.min(...progressValues);
11066
+ const spread = maxProgress - minProgress;
11067
+ if (visited.size === allSamplesSortedByOffset.length) {
11068
+ addFinalJumpIfNecessary();
11069
+ break;
11070
+ }
11071
+ if (spread > MAX_SPREAD_IN_SECONDS) {
11072
+ considerJump();
11073
+ } else {
11074
+ increaseIndex();
11075
+ }
11076
+ }
11077
+ return jumpMarks;
11078
+ };
11079
+
10637
11080
  // src/containers/iso-base-media/mdat/postprocess-bytes.ts
10638
11081
  var postprocessBytes = ({
10639
11082
  bytes,
@@ -10683,9 +11126,13 @@ var parseMdatSection = async (state) => {
10683
11126
  return parseMdatSection(state);
10684
11127
  }
10685
11128
  if (!state.iso.flatSamples.getSamples(mediaSection.start)) {
10686
- state.iso.flatSamples.setSamples(mediaSection.start, calculateFlatSamples(state));
11129
+ const flattedSamples = calculateFlatSamples(state);
11130
+ const calcedJumpMarks = calculateJumpMarks(flattedSamples, endOfMdat);
11131
+ state.iso.flatSamples.setJumpMarks(mediaSection.start, calcedJumpMarks);
11132
+ state.iso.flatSamples.setSamples(mediaSection.start, flattedSamples.flat(1));
10687
11133
  }
10688
11134
  const flatSamples = state.iso.flatSamples.getSamples(mediaSection.start);
11135
+ const jumpMarks = state.iso.flatSamples.getJumpMarks(mediaSection.start);
10689
11136
  const { iterator } = state;
10690
11137
  const samplesWithIndex = flatSamples.find((sample) => {
10691
11138
  return sample.samplePosition.offset === iterator.counter.getOffset();
@@ -10698,8 +11145,11 @@ var parseMdatSection = async (state) => {
10698
11145
  }
10699
11146
  return makeSkip(endOfMdat);
10700
11147
  }
11148
+ if (samplesWithIndex.samplePosition.offset + samplesWithIndex.samplePosition.size > state.contentLength) {
11149
+ return makeSkip(endOfMdat);
11150
+ }
10701
11151
  if (iterator.bytesRemaining() < samplesWithIndex.samplePosition.size) {
10702
- return null;
11152
+ return makeFetchMoreData(samplesWithIndex.samplePosition.size - iterator.bytesRemaining());
10703
11153
  }
10704
11154
  const { cts, dts, duration: duration2, isKeyframe, offset, bigEndian, chunkSize } = samplesWithIndex.samplePosition;
10705
11155
  const bytes = postprocessBytes({
@@ -10747,6 +11197,10 @@ var parseMdatSection = async (state) => {
10747
11197
  });
10748
11198
  await state.callbacks.onVideoSample(samplesWithIndex.track.trackId, videoSample);
10749
11199
  }
11200
+ const jump = jumpMarks.find((j) => j.afterSampleWithOffset === offset);
11201
+ if (jump) {
11202
+ return makeSkip(jump.jumpToOffset);
11203
+ }
10750
11204
  return null;
10751
11205
  };
10752
11206
 
@@ -10773,8 +11227,11 @@ var parseIsoBaseMedia = async (state) => {
10773
11227
  },
10774
11228
  contentLength: state.contentLength
10775
11229
  });
10776
- if (result) {
10777
- state.structure.getIsoStructure().boxes.push(result);
11230
+ if (result.type === "fetch-more-data") {
11231
+ return result.bytesNeeded;
11232
+ }
11233
+ if (result.type === "box") {
11234
+ state.structure.getIsoStructure().boxes.push(result.box);
10778
11235
  }
10779
11236
  return null;
10780
11237
  };
@@ -10853,7 +11310,8 @@ var parseM3uMediaDirective = (str) => {
10853
11310
  groupId: map["GROUP-ID"],
10854
11311
  language: map.LANGUAGE || null,
10855
11312
  name: map.NAME || null,
10856
- uri: map.URI
11313
+ uri: map.URI,
11314
+ mediaType: map.TYPE || null
10857
11315
  };
10858
11316
  };
10859
11317
 
@@ -11008,7 +11466,8 @@ var fetchM3u8Stream = async ({
11008
11466
  // src/containers/m3u/select-stream.ts
11009
11467
  var selectAssociatedPlaylists = async ({
11010
11468
  playlists,
11011
- fn
11469
+ fn,
11470
+ skipAudioTracks
11012
11471
  }) => {
11013
11472
  if (playlists.length < 1) {
11014
11473
  return Promise.resolve([]);
@@ -11017,12 +11476,17 @@ var selectAssociatedPlaylists = async ({
11017
11476
  if (!Array.isArray(streams)) {
11018
11477
  throw new Error("Expected an array of associated playlists");
11019
11478
  }
11479
+ const selectedStreams = [];
11020
11480
  for (const stream of streams) {
11481
+ if (stream.isAudio && skipAudioTracks) {
11482
+ continue;
11483
+ }
11021
11484
  if (!playlists.find((playlist) => playlist.src === stream.src)) {
11022
11485
  throw new Error(`The associated playlist ${JSON.stringify(streams)} cannot be selected because it was not in the list of selectable playlists`);
11023
11486
  }
11487
+ selectedStreams.push(stream);
11024
11488
  }
11025
- return streams;
11489
+ return selectedStreams;
11026
11490
  };
11027
11491
  var defaultSelectM3uAssociatedPlaylists = ({ associatedPlaylists }) => {
11028
11492
  if (associatedPlaylists.length === 1) {
@@ -11056,7 +11520,9 @@ var afterManifestFetch = async ({
11056
11520
  selectM3uStreamFn,
11057
11521
  logLevel,
11058
11522
  selectAssociatedPlaylistsFn,
11059
- readerInterface
11523
+ readerInterface,
11524
+ onAudioTrack,
11525
+ canSkipTracks
11060
11526
  }) => {
11061
11527
  const independentSegments = isIndependentSegments(structure);
11062
11528
  if (!independentSegments) {
@@ -11081,9 +11547,11 @@ var afterManifestFetch = async ({
11081
11547
  type: "selected-stream",
11082
11548
  stream: selectedPlaylist
11083
11549
  });
11550
+ const skipAudioTracks = onAudioTrack === null && canSkipTracks.doFieldsNeedTracks() === false;
11084
11551
  const associatedPlaylists = await selectAssociatedPlaylists({
11085
11552
  playlists: selectedPlaylist.associatedPlaylists,
11086
- fn: selectAssociatedPlaylistsFn
11553
+ fn: selectAssociatedPlaylistsFn,
11554
+ skipAudioTracks
11087
11555
  });
11088
11556
  m3uState.setAssociatedPlaylists(associatedPlaylists);
11089
11557
  const playlistUrls = [
@@ -11160,10 +11628,22 @@ var getLengthAndReader = async ({
11160
11628
  if (requestedWithoutRange || canLiveWithoutContentLength && contentLength === null) {
11161
11629
  const buffer = await res.arrayBuffer();
11162
11630
  const encoded = new Uint8Array(buffer);
11631
+ let streamCancelled = false;
11163
11632
  const stream = new ReadableStream({
11164
11633
  start(controller) {
11165
- controller.enqueue(encoded);
11166
- controller.close();
11634
+ if (ownController.signal.aborted) {
11635
+ return;
11636
+ }
11637
+ if (streamCancelled) {
11638
+ return;
11639
+ }
11640
+ try {
11641
+ controller.enqueue(encoded);
11642
+ controller.close();
11643
+ } catch {}
11644
+ },
11645
+ cancel() {
11646
+ streamCancelled = true;
11167
11647
  }
11168
11648
  });
11169
11649
  return {
@@ -11239,14 +11719,11 @@ var validateContentRangeAndDetectIfSupported = ({
11239
11719
  }
11240
11720
  return { supportsContentRange: true };
11241
11721
  };
11242
- var fetchReadContent = async ({
11243
- src,
11722
+ var makeFetchRequest = async ({
11244
11723
  range: range2,
11724
+ src,
11245
11725
  controller
11246
11726
  }) => {
11247
- if (typeof src !== "string" && src instanceof URL === false) {
11248
- throw new Error("src must be a string when using `fetchReader`");
11249
- }
11250
11727
  const resolvedUrl = resolveUrl(src);
11251
11728
  const resolvedUrlString = resolvedUrl.toString();
11252
11729
  if (!resolvedUrlString.startsWith("https://") && !resolvedUrlString.startsWith("blob:") && !resolvedUrlString.startsWith("http://")) {
@@ -11275,21 +11752,83 @@ var fetchReadContent = async ({
11275
11752
  parsedContentRange,
11276
11753
  statusCode: res.status
11277
11754
  });
11278
- controller._internals.signal.addEventListener("abort", () => {
11279
- ownController.abort(new MediaParserAbortError("Aborted by user"));
11280
- }, { once: true });
11755
+ if (controller) {
11756
+ controller._internals.signal.addEventListener("abort", () => {
11757
+ ownController.abort(new MediaParserAbortError("Aborted by user"));
11758
+ }, { once: true });
11759
+ }
11281
11760
  if (res.status.toString().startsWith("4") || res.status.toString().startsWith("5")) {
11282
- throw new Error(`Server returned status code ${res.status} for ${src} and range ${requestedRange}`);
11761
+ throw new Error(`Server returned status code ${res.status} for ${resolvedUrl} and range ${requestedRange}`);
11762
+ }
11763
+ const contentDisposition = res.headers.get("content-disposition");
11764
+ const name = contentDisposition?.match(/filename="([^"]+)"/)?.[1];
11765
+ const { contentLength, needsContentRange, reader } = await getLengthAndReader({
11766
+ canLiveWithoutContentLength,
11767
+ res,
11768
+ ownController,
11769
+ requestedWithoutRange: requestWithoutRange
11770
+ });
11771
+ const contentType = res.headers.get("content-type");
11772
+ return {
11773
+ contentLength,
11774
+ needsContentRange,
11775
+ reader,
11776
+ name,
11777
+ contentType,
11778
+ supportsContentRange
11779
+ };
11780
+ };
11781
+ var cacheKey = ({
11782
+ src,
11783
+ range: range2
11784
+ }) => {
11785
+ return `${src}-${JSON.stringify(range2)}`;
11786
+ };
11787
+ var makeFetchRequestOrGetCached = ({
11788
+ range: range2,
11789
+ src,
11790
+ controller,
11791
+ logLevel,
11792
+ prefetchCache
11793
+ }) => {
11794
+ const key = cacheKey({ src, range: range2 });
11795
+ const cached = prefetchCache.get(key);
11796
+ if (cached) {
11797
+ Log.verbose(logLevel, `Reading from preload cache for ${key}`);
11798
+ return cached;
11799
+ }
11800
+ Log.verbose(logLevel, `Fetching ${key}`);
11801
+ const result = makeFetchRequest({ range: range2, src, controller });
11802
+ prefetchCache.set(key, result);
11803
+ return result;
11804
+ };
11805
+ var fetchReadContent = async ({
11806
+ src,
11807
+ range: range2,
11808
+ controller,
11809
+ logLevel,
11810
+ prefetchCache
11811
+ }) => {
11812
+ if (typeof src !== "string" && src instanceof URL === false) {
11813
+ throw new Error("src must be a string when using `fetchReader`");
11283
11814
  }
11284
- const contentDisposition = res.headers.get("content-disposition");
11285
- const name = contentDisposition?.match(/filename="([^"]+)"/)?.[1];
11286
11815
  const fallbackName = src.toString().split("/").pop();
11287
- const { contentLength, needsContentRange, reader } = await getLengthAndReader({
11288
- canLiveWithoutContentLength,
11289
- res,
11290
- ownController,
11291
- requestedWithoutRange: requestWithoutRange
11816
+ const {
11817
+ reader,
11818
+ contentLength,
11819
+ needsContentRange,
11820
+ name,
11821
+ supportsContentRange,
11822
+ contentType
11823
+ } = await makeFetchRequestOrGetCached({
11824
+ range: range2,
11825
+ src,
11826
+ controller,
11827
+ logLevel,
11828
+ prefetchCache
11292
11829
  });
11830
+ const key = cacheKey({ src, range: range2 });
11831
+ prefetchCache.delete(key);
11293
11832
  if (controller) {
11294
11833
  controller._internals.signal.addEventListener("abort", () => {
11295
11834
  reader.reader.cancel().catch(() => {});
@@ -11298,12 +11837,33 @@ var fetchReadContent = async ({
11298
11837
  return {
11299
11838
  reader,
11300
11839
  contentLength,
11301
- contentType: res.headers.get("content-type"),
11840
+ contentType,
11302
11841
  name: name ?? fallbackName,
11303
11842
  supportsContentRange,
11304
11843
  needsContentRange
11305
11844
  };
11306
11845
  };
11846
+ var fetchPreload = ({
11847
+ src,
11848
+ range: range2,
11849
+ logLevel,
11850
+ prefetchCache
11851
+ }) => {
11852
+ if (typeof src !== "string" && src instanceof URL === false) {
11853
+ throw new Error("src must be a string when using `fetchReader`");
11854
+ }
11855
+ const key = cacheKey({ src, range: range2 });
11856
+ if (prefetchCache.has(key)) {
11857
+ return prefetchCache.get(key);
11858
+ }
11859
+ makeFetchRequestOrGetCached({
11860
+ range: range2,
11861
+ src,
11862
+ controller: null,
11863
+ logLevel,
11864
+ prefetchCache
11865
+ });
11866
+ };
11307
11867
  var fetchReadWholeAsText = async (src) => {
11308
11868
  if (typeof src !== "string" && src instanceof URL === false) {
11309
11869
  throw new Error("src must be a string when using `fetchReader`");
@@ -11390,6 +11950,12 @@ var webReader = {
11390
11950
  return webFileReadWholeAsText(src);
11391
11951
  }
11392
11952
  return fetchReadWholeAsText(src);
11953
+ },
11954
+ preload: ({ range: range2, src, logLevel, prefetchCache }) => {
11955
+ if (src instanceof Blob) {
11956
+ return;
11957
+ }
11958
+ return fetchPreload({ range: range2, src, logLevel, prefetchCache });
11393
11959
  }
11394
11960
  };
11395
11961
  // src/parse-media.ts
@@ -11433,7 +11999,7 @@ var parseMedia = (options) => {
11433
11999
  controller: options.controller ?? undefined,
11434
12000
  selectM3uStream: options.selectM3uStream ?? defaultSelectM3uStreamFn,
11435
12001
  selectM3uAssociatedPlaylists: options.selectM3uAssociatedPlaylists ?? defaultSelectM3uAssociatedPlaylists,
11436
- mp4HeaderSegment: options.mp4HeaderSegment ?? null,
12002
+ m3uPlaylistContext: options.m3uPlaylistContext ?? null,
11437
12003
  src: options.src,
11438
12004
  mode: "query",
11439
12005
  onDiscardedData: null,
@@ -11445,6 +12011,39 @@ var parseMedia = (options) => {
11445
12011
  });
11446
12012
  };
11447
12013
 
12014
+ // src/containers/m3u/first-sample-in-m3u-chunk.ts
12015
+ var considerSeekBasedOnChunk = async ({
12016
+ sample,
12017
+ parentController,
12018
+ childController,
12019
+ callback,
12020
+ m3uState,
12021
+ playlistUrl,
12022
+ subtractChunks,
12023
+ chunkIndex
12024
+ }) => {
12025
+ const pendingSeek = m3uState.getSeekToSecondsToProcess(playlistUrl);
12026
+ if (pendingSeek === null) {
12027
+ await callback(sample);
12028
+ return;
12029
+ }
12030
+ const timestamp = Math.min(sample.dts / sample.timescale, sample.cts / sample.timescale);
12031
+ if (timestamp > pendingSeek.targetTime && chunkIndex !== null && chunkIndex > 0) {
12032
+ m3uState.setNextSeekShouldSubtractChunks(playlistUrl, subtractChunks + 1);
12033
+ parentController.seek({
12034
+ type: "keyframe-before-time",
12035
+ timeInSeconds: pendingSeek.targetTime
12036
+ });
12037
+ return;
12038
+ }
12039
+ childController.seek({
12040
+ type: "keyframe-before-time",
12041
+ timeInSeconds: pendingSeek.targetTime
12042
+ });
12043
+ m3uState.setNextSeekShouldSubtractChunks(playlistUrl, 0);
12044
+ m3uState.setSeekToSecondsToProcess(playlistUrl, null);
12045
+ };
12046
+
11448
12047
  // src/containers/m3u/get-chunks.ts
11449
12048
  var getChunks = (playlist) => {
11450
12049
  const chunks = [];
@@ -11467,122 +12066,277 @@ var getChunks = (playlist) => {
11467
12066
  return chunks;
11468
12067
  };
11469
12068
 
11470
- // src/containers/m3u/iterate-over-segment-files.ts
11471
- var iteratorOverSegmentFiles = async ({
11472
- structure,
11473
- onVideoTrack,
11474
- m3uState,
11475
- onAudioTrack,
11476
- onDoneWithTracks,
12069
+ // src/containers/m3u/seek/get-chunk-to-seek-to.ts
12070
+ var getChunkToSeekTo = ({
12071
+ chunks,
12072
+ seekToSecondsToProcess
12073
+ }) => {
12074
+ let duration2 = 0;
12075
+ for (let i = 0;i < chunks.length; i++) {
12076
+ if (duration2 >= seekToSecondsToProcess) {
12077
+ return Math.max(0, i - 1);
12078
+ }
12079
+ duration2 += chunks[i].duration;
12080
+ }
12081
+ return Math.max(0, chunks.length - 1);
12082
+ };
12083
+
12084
+ // src/containers/m3u/process-m3u-chunk.ts
12085
+ var processM3uChunk = ({
11477
12086
  playlistUrl,
11478
- logLevel,
11479
- parentController,
11480
- onInitialProgress,
11481
- readerInterface
12087
+ state,
12088
+ structure,
12089
+ audioDone,
12090
+ videoDone
11482
12091
  }) => {
11483
- const playlist = getPlaylist(structure, playlistUrl);
11484
- const chunks = getChunks(playlist);
11485
- let resolver = onInitialProgress;
11486
- let rejector = (_e) => {};
11487
- for (const chunk of chunks) {
11488
- resolver = onInitialProgress;
11489
- rejector = (_e) => {};
11490
- const childController = mediaParserController();
11491
- const forwarded = forwardMediaParserControllerPauseResume({
11492
- childController,
11493
- parentController
12092
+ const { promise, reject, resolve } = withResolvers();
12093
+ const onGlobalAudioTrack = audioDone ? null : async (track) => {
12094
+ const existingTracks = state.callbacks.tracks.getTracks();
12095
+ let { trackId } = track;
12096
+ while (existingTracks.find((t) => t.trackId === trackId)) {
12097
+ trackId++;
12098
+ }
12099
+ const onAudioSample = await registerAudioTrack({
12100
+ container: "m3u8",
12101
+ track: {
12102
+ ...track,
12103
+ trackId
12104
+ },
12105
+ registerAudioSampleCallback: state.callbacks.registerAudioSampleCallback,
12106
+ tracks: state.callbacks.tracks,
12107
+ logLevel: state.logLevel,
12108
+ onAudioTrack: state.onAudioTrack
11494
12109
  });
11495
- const makeContinuationFn = () => {
11496
- return {
11497
- continue() {
11498
- const { promise, reject, resolve } = withResolvers();
11499
- resolver = resolve;
11500
- rejector = reject;
11501
- childController.resume();
11502
- return promise;
11503
- },
11504
- abort() {
11505
- childController.abort();
11506
- }
11507
- };
12110
+ state.m3u.sampleSorter.addToStreamWithTrack(playlistUrl);
12111
+ if (onAudioSample === null) {
12112
+ return null;
12113
+ }
12114
+ state.m3u.sampleSorter.addAudioStreamToConsider(playlistUrl, onAudioSample);
12115
+ return async (sample) => {
12116
+ await state.m3u.sampleSorter.addAudioSample(playlistUrl, sample);
11508
12117
  };
11509
- const isLastChunk = chunk === chunks[chunks.length - 1];
11510
- await childController._internals.checkForAbortAndPause();
11511
- const src = readerInterface.createAdjacentFileSource(chunk.url, playlistUrl);
11512
- try {
11513
- const mp4HeaderSegment = m3uState.getMp4HeaderSegment(playlistUrl);
11514
- const data = await parseMedia({
11515
- src,
11516
- acknowledgeRemotionLicense: true,
11517
- logLevel,
11518
- controller: childController,
11519
- progressIntervalInMs: 0,
11520
- onParseProgress: () => {
11521
- childController.pause();
11522
- resolver(makeContinuationFn());
11523
- },
11524
- fields: chunk.isHeader ? { structure: true } : undefined,
11525
- onTracks: () => {
11526
- if (!m3uState.hasEmittedDoneWithTracks(playlistUrl)) {
11527
- m3uState.setHasEmittedDoneWithTracks(playlistUrl);
11528
- onDoneWithTracks();
11529
- return null;
12118
+ };
12119
+ const onGlobalVideoTrack = videoDone ? null : async (track) => {
12120
+ const existingTracks = state.callbacks.tracks.getTracks();
12121
+ let { trackId } = track;
12122
+ while (existingTracks.find((t) => t.trackId === trackId)) {
12123
+ trackId++;
12124
+ }
12125
+ const onVideoSample = await registerVideoTrack({
12126
+ container: "m3u8",
12127
+ track: {
12128
+ ...track,
12129
+ trackId
12130
+ },
12131
+ logLevel: state.logLevel,
12132
+ onVideoTrack: state.onVideoTrack,
12133
+ registerVideoSampleCallback: state.callbacks.registerVideoSampleCallback,
12134
+ tracks: state.callbacks.tracks
12135
+ });
12136
+ state.m3u.sampleSorter.addToStreamWithTrack(playlistUrl);
12137
+ if (onVideoSample === null) {
12138
+ return null;
12139
+ }
12140
+ state.m3u.sampleSorter.addVideoStreamToConsider(playlistUrl, onVideoSample);
12141
+ return async (sample) => {
12142
+ await state.m3u.sampleSorter.addVideoSample(playlistUrl, sample);
12143
+ };
12144
+ };
12145
+ const pausableIterator = async () => {
12146
+ const playlist = getPlaylist(structure, playlistUrl);
12147
+ const chunks = getChunks(playlist);
12148
+ const seekToSecondsToProcess = state.m3u.getSeekToSecondsToProcess(playlistUrl);
12149
+ const chunksToSubtract = state.m3u.getNextSeekShouldSubtractChunks(playlistUrl);
12150
+ let chunkIndex = null;
12151
+ if (seekToSecondsToProcess !== null) {
12152
+ chunkIndex = Math.max(0, getChunkToSeekTo({
12153
+ chunks,
12154
+ seekToSecondsToProcess: seekToSecondsToProcess.targetTime
12155
+ }) - chunksToSubtract);
12156
+ }
12157
+ const currentPromise = {
12158
+ resolver: () => {
12159
+ return;
12160
+ },
12161
+ rejector: reject
12162
+ };
12163
+ const requiresHeaderToBeFetched = chunks[0].isHeader;
12164
+ for (const chunk of chunks) {
12165
+ const mp4HeaderSegment = state.m3u.getMp4HeaderSegment(playlistUrl);
12166
+ if (requiresHeaderToBeFetched && mp4HeaderSegment && chunk.isHeader) {
12167
+ continue;
12168
+ }
12169
+ if (chunkIndex !== null && chunks.indexOf(chunk) < chunkIndex && !chunk.isHeader) {
12170
+ continue;
12171
+ }
12172
+ currentPromise.resolver = (newRun) => {
12173
+ state.m3u.setM3uStreamRun(playlistUrl, newRun);
12174
+ resolve();
12175
+ };
12176
+ currentPromise.rejector = reject;
12177
+ const childController = mediaParserController();
12178
+ const forwarded = forwardMediaParserControllerPauseResume({
12179
+ childController,
12180
+ parentController: state.controller
12181
+ });
12182
+ const nextChunk = chunks[chunks.indexOf(chunk) + 1];
12183
+ if (nextChunk) {
12184
+ const nextChunkSource = state.readerInterface.createAdjacentFileSource(nextChunk.url, playlistUrl);
12185
+ state.readerInterface.preload({
12186
+ logLevel: state.logLevel,
12187
+ range: null,
12188
+ src: nextChunkSource,
12189
+ prefetchCache: state.prefetchCache
12190
+ });
12191
+ }
12192
+ const makeContinuationFn = () => {
12193
+ return {
12194
+ continue() {
12195
+ const resolver = withResolvers();
12196
+ currentPromise.resolver = resolver.resolve;
12197
+ currentPromise.rejector = resolver.reject;
12198
+ childController.resume();
12199
+ return resolver.promise;
12200
+ },
12201
+ abort() {
12202
+ childController.abort();
11530
12203
  }
11531
- },
11532
- onAudioTrack: onAudioTrack === null ? null : async ({ track }) => {
11533
- const callbackOrFalse = m3uState.hasEmittedAudioTrack(playlistUrl);
11534
- if (callbackOrFalse === false) {
11535
- const callback = await onAudioTrack(track);
11536
- if (!callback) {
11537
- m3uState.setHasEmittedAudioTrack(playlistUrl, null);
12204
+ };
12205
+ };
12206
+ const isLastChunk = chunk === chunks[chunks.length - 1];
12207
+ await childController._internals.checkForAbortAndPause();
12208
+ const src = state.readerInterface.createAdjacentFileSource(chunk.url, playlistUrl);
12209
+ try {
12210
+ const data = await parseMedia({
12211
+ src,
12212
+ acknowledgeRemotionLicense: true,
12213
+ logLevel: state.logLevel,
12214
+ controller: childController,
12215
+ progressIntervalInMs: 0,
12216
+ onParseProgress: () => {
12217
+ childController.pause();
12218
+ currentPromise.resolver(makeContinuationFn());
12219
+ },
12220
+ fields: chunk.isHeader ? { structure: true } : undefined,
12221
+ onTracks: () => {
12222
+ if (!state.m3u.hasEmittedDoneWithTracks(playlistUrl)) {
12223
+ state.m3u.setHasEmittedDoneWithTracks(playlistUrl);
12224
+ const allDone = state.m3u.setTracksDone(playlistUrl);
12225
+ if (allDone) {
12226
+ state.callbacks.tracks.setIsDone(state.logLevel);
12227
+ }
12228
+ return null;
12229
+ }
12230
+ },
12231
+ onAudioTrack: onGlobalAudioTrack === null ? null : async ({ track }) => {
12232
+ const callbackOrFalse = state.m3u.hasEmittedAudioTrack(playlistUrl);
12233
+ if (callbackOrFalse === false) {
12234
+ const callback = await onGlobalAudioTrack(track);
12235
+ if (!callback) {
12236
+ state.m3u.setHasEmittedAudioTrack(playlistUrl, null);
12237
+ return null;
12238
+ }
12239
+ state.m3u.setHasEmittedAudioTrack(playlistUrl, callback);
12240
+ return async (sample) => {
12241
+ await considerSeekBasedOnChunk({
12242
+ sample,
12243
+ callback,
12244
+ parentController: state.controller,
12245
+ childController,
12246
+ m3uState: state.m3u,
12247
+ playlistUrl,
12248
+ subtractChunks: chunksToSubtract,
12249
+ chunkIndex
12250
+ });
12251
+ };
12252
+ }
12253
+ if (callbackOrFalse === null) {
11538
12254
  return null;
11539
12255
  }
11540
- m3uState.setHasEmittedAudioTrack(playlistUrl, callback);
11541
- return (sample) => {
11542
- return callback(sample);
12256
+ return async (sample) => {
12257
+ await considerSeekBasedOnChunk({
12258
+ sample,
12259
+ m3uState: state.m3u,
12260
+ playlistUrl,
12261
+ callback: callbackOrFalse,
12262
+ parentController: state.controller,
12263
+ childController,
12264
+ subtractChunks: chunksToSubtract,
12265
+ chunkIndex
12266
+ });
11543
12267
  };
11544
- }
11545
- return callbackOrFalse;
11546
- },
11547
- onVideoTrack: onVideoTrack === null ? null : async ({ track }) => {
11548
- const callbackOrFalse = m3uState.hasEmittedVideoTrack(playlistUrl);
11549
- if (callbackOrFalse === false) {
11550
- const callback = await onVideoTrack({
11551
- ...track,
11552
- m3uStreamFormat: chunk.isHeader || mp4HeaderSegment ? "mp4" : "ts"
11553
- });
11554
- if (!callback) {
11555
- m3uState.setHasEmittedVideoTrack(playlistUrl, null);
12268
+ },
12269
+ onVideoTrack: onGlobalVideoTrack === null ? null : async ({ track }) => {
12270
+ const callbackOrFalse = state.m3u.hasEmittedVideoTrack(playlistUrl);
12271
+ if (callbackOrFalse === false) {
12272
+ const callback = await onGlobalVideoTrack({
12273
+ ...track,
12274
+ m3uStreamFormat: chunk.isHeader || mp4HeaderSegment ? "mp4" : "ts"
12275
+ });
12276
+ if (!callback) {
12277
+ state.m3u.setHasEmittedVideoTrack(playlistUrl, null);
12278
+ return null;
12279
+ }
12280
+ state.m3u.setHasEmittedVideoTrack(playlistUrl, callback);
12281
+ return async (sample) => {
12282
+ await considerSeekBasedOnChunk({
12283
+ sample,
12284
+ m3uState: state.m3u,
12285
+ playlistUrl,
12286
+ callback,
12287
+ parentController: state.controller,
12288
+ childController,
12289
+ subtractChunks: chunksToSubtract,
12290
+ chunkIndex
12291
+ });
12292
+ };
12293
+ }
12294
+ if (callbackOrFalse === null) {
11556
12295
  return null;
11557
12296
  }
11558
- m3uState.setHasEmittedVideoTrack(playlistUrl, callback);
11559
- return (sample) => {
11560
- return callback(sample);
12297
+ return async (sample) => {
12298
+ await considerSeekBasedOnChunk({
12299
+ sample,
12300
+ m3uState: state.m3u,
12301
+ playlistUrl,
12302
+ callback: callbackOrFalse,
12303
+ parentController: state.controller,
12304
+ childController,
12305
+ subtractChunks: chunksToSubtract,
12306
+ chunkIndex
12307
+ });
11561
12308
  };
12309
+ },
12310
+ reader: state.readerInterface,
12311
+ makeSamplesStartAtZero: false,
12312
+ m3uPlaylistContext: {
12313
+ mp4HeaderSegment,
12314
+ isLastChunkInPlaylist: isLastChunk
11562
12315
  }
11563
- return callbackOrFalse;
11564
- },
11565
- reader: readerInterface,
11566
- mp4HeaderSegment,
11567
- makeSamplesStartAtZero: false
11568
- });
11569
- if (chunk.isHeader) {
11570
- if (data.structure.type !== "iso-base-media") {
11571
- throw new Error("Expected an mp4 file");
12316
+ });
12317
+ if (chunk.isHeader) {
12318
+ if (data.structure.type !== "iso-base-media") {
12319
+ throw new Error("Expected an mp4 file");
12320
+ }
12321
+ state.m3u.setMp4HeaderSegment(playlistUrl, data.structure);
11572
12322
  }
11573
- m3uState.setMp4HeaderSegment(playlistUrl, data.structure);
12323
+ } catch (e) {
12324
+ currentPromise.rejector(e);
12325
+ throw e;
12326
+ }
12327
+ forwarded.cleanup();
12328
+ if (!isLastChunk) {
12329
+ childController.pause();
12330
+ currentPromise.resolver(makeContinuationFn());
11574
12331
  }
11575
- } catch (e) {
11576
- rejector(e);
11577
- throw e;
11578
- }
11579
- forwarded.cleanup();
11580
- if (!isLastChunk) {
11581
- childController.pause();
11582
- resolver(makeContinuationFn());
11583
12332
  }
11584
- }
11585
- resolver(null);
12333
+ currentPromise.resolver(null);
12334
+ };
12335
+ const run = pausableIterator();
12336
+ run.catch((err) => {
12337
+ reject(err);
12338
+ });
12339
+ return promise;
11586
12340
  };
11587
12341
 
11588
12342
  // src/containers/m3u/run-over-m3u.ts
@@ -11613,80 +12367,12 @@ var runOverM3u = async ({
11613
12367
  return;
11614
12368
  }
11615
12369
  Log.trace(logLevel, "Starting new M3U parsing process for", playlistUrl);
11616
- return new Promise((resolve, reject) => {
11617
- const run = iteratorOverSegmentFiles({
11618
- playlistUrl,
11619
- structure,
11620
- onInitialProgress: (newRun) => {
11621
- state.m3u.setM3uStreamRun(playlistUrl, newRun);
11622
- resolve();
11623
- },
11624
- logLevel: state.logLevel,
11625
- onDoneWithTracks() {
11626
- const allDone = state.m3u.setTracksDone(playlistUrl);
11627
- if (allDone) {
11628
- state.callbacks.tracks.setIsDone(state.logLevel);
11629
- }
11630
- },
11631
- onAudioTrack: audioDone ? null : async (track) => {
11632
- const existingTracks = state.callbacks.tracks.getTracks();
11633
- let { trackId } = track;
11634
- while (existingTracks.find((t) => t.trackId === trackId)) {
11635
- trackId++;
11636
- }
11637
- const onAudioSample = await registerAudioTrack({
11638
- container: "m3u8",
11639
- track: {
11640
- ...track,
11641
- trackId
11642
- },
11643
- registerAudioSampleCallback: state.callbacks.registerAudioSampleCallback,
11644
- tracks: state.callbacks.tracks,
11645
- logLevel: state.logLevel,
11646
- onAudioTrack: state.onAudioTrack
11647
- });
11648
- state.m3u.sampleSorter.addToStreamWithTrack(playlistUrl);
11649
- if (onAudioSample === null) {
11650
- return null;
11651
- }
11652
- state.m3u.sampleSorter.addAudioStreamToConsider(playlistUrl, onAudioSample);
11653
- return async (sample) => {
11654
- await state.m3u.sampleSorter.addAudioSample(playlistUrl, sample);
11655
- };
11656
- },
11657
- onVideoTrack: videoDone ? null : async (track) => {
11658
- const existingTracks = state.callbacks.tracks.getTracks();
11659
- let { trackId } = track;
11660
- while (existingTracks.find((t) => t.trackId === trackId)) {
11661
- trackId++;
11662
- }
11663
- const onVideoSample = await registerVideoTrack({
11664
- container: "m3u8",
11665
- track: {
11666
- ...track,
11667
- trackId
11668
- },
11669
- logLevel: state.logLevel,
11670
- onVideoTrack: state.onVideoTrack,
11671
- registerVideoSampleCallback: state.callbacks.registerVideoSampleCallback,
11672
- tracks: state.callbacks.tracks
11673
- });
11674
- state.m3u.sampleSorter.addToStreamWithTrack(playlistUrl);
11675
- if (onVideoSample === null) {
11676
- return null;
11677
- }
11678
- state.m3u.sampleSorter.addVideoStreamToConsider(playlistUrl, onVideoSample);
11679
- return async (sample) => {
11680
- await state.m3u.sampleSorter.addVideoSample(playlistUrl, sample);
11681
- };
11682
- },
11683
- m3uState: state.m3u,
11684
- parentController: state.controller,
11685
- readerInterface: state.readerInterface
11686
- });
11687
- run.catch((err) => {
11688
- reject(err);
11689
- });
12370
+ await processM3uChunk({
12371
+ playlistUrl,
12372
+ state,
12373
+ structure,
12374
+ audioDone,
12375
+ videoDone
11690
12376
  });
11691
12377
  };
11692
12378
 
@@ -11708,6 +12394,10 @@ var parseM3u = async ({ state }) => {
11708
12394
  if (typeof state.src !== "string" && !(state.src instanceof URL)) {
11709
12395
  throw new Error("Expected src to be a string");
11710
12396
  }
12397
+ state.mediaSection.addMediaSection({
12398
+ start: 0,
12399
+ size: state.contentLength + 1
12400
+ });
11711
12401
  await afterManifestFetch({
11712
12402
  structure,
11713
12403
  m3uState: state.m3u,
@@ -11715,7 +12405,9 @@ var parseM3u = async ({ state }) => {
11715
12405
  selectM3uStreamFn: state.selectM3uStreamFn,
11716
12406
  logLevel: state.logLevel,
11717
12407
  selectAssociatedPlaylistsFn: state.selectM3uAssociatedPlaylistsFn,
11718
- readerInterface: state.readerInterface
12408
+ readerInterface: state.readerInterface,
12409
+ onAudioTrack: state.onAudioTrack,
12410
+ canSkipTracks: state.callbacks.canSkipTracksState
11719
12411
  });
11720
12412
  return null;
11721
12413
  }
@@ -14045,9 +14737,9 @@ var initVideo = async ({ state }) => {
14045
14737
  });
14046
14738
  return;
14047
14739
  }
14048
- if (state.mp4HeaderSegment) {
14740
+ if (state.m3uPlaylistContext?.mp4HeaderSegment) {
14049
14741
  Log.verbose(state.logLevel, "Detected ISO Base Media segment");
14050
- const moovAtom = getMoovFromFromIsoStructure(state.mp4HeaderSegment);
14742
+ const moovAtom = getMoovFromFromIsoStructure(state.m3uPlaylistContext.mp4HeaderSegment);
14051
14743
  if (!moovAtom) {
14052
14744
  throw new Error("No moov box found");
14053
14745
  }
@@ -14270,19 +14962,33 @@ var parseLoop = async ({
14270
14962
  try {
14271
14963
  await triggerInfoEmit(state);
14272
14964
  await state.controller._internals.checkForAbortAndPause();
14273
- const skip = await runParseIteration({
14965
+ const result = await runParseIteration({
14274
14966
  state
14275
14967
  });
14276
- if (skip !== null) {
14277
- state.increaseSkippedBytes(skip.skipTo - state.iterator.counter.getOffset());
14278
- if (skip.skipTo === state.contentLength) {
14279
- state.iterator.discard(skip.skipTo - state.iterator.counter.getOffset());
14968
+ if (result !== null && result.action === "fetch-more-data") {
14969
+ Log.verbose(state.logLevel, `Need to fetch ${result.bytesNeeded} more bytes before we can continue`);
14970
+ const startBytesRemaining = state.iterator.bytesRemaining();
14971
+ while (true) {
14972
+ const done = await fetchMoreData(state);
14973
+ if (done) {
14974
+ break;
14975
+ }
14976
+ if (state.iterator.bytesRemaining() - startBytesRemaining >= result.bytesNeeded) {
14977
+ break;
14978
+ }
14979
+ }
14980
+ continue;
14981
+ }
14982
+ if (result !== null && result.action === "skip") {
14983
+ state.increaseSkippedBytes(result.skipTo - state.iterator.counter.getOffset());
14984
+ if (result.skipTo === state.contentLength) {
14985
+ state.iterator.discard(result.skipTo - state.iterator.counter.getOffset());
14280
14986
  Log.verbose(state.logLevel, "Skipped to end of file, not fetching.");
14281
14987
  break;
14282
14988
  }
14283
14989
  const seekStart = Date.now();
14284
14990
  await performSeek({
14285
- seekTo: skip.skipTo,
14991
+ seekTo: result.skipTo,
14286
14992
  userInitiated: false,
14287
14993
  controller: state.controller,
14288
14994
  mediaSection: state.mediaSection,
@@ -14295,7 +15001,8 @@ var parseLoop = async ({
14295
15001
  readerInterface: state.readerInterface,
14296
15002
  fields: state.fields,
14297
15003
  src: state.src,
14298
- discardReadBytes: state.discardReadBytes
15004
+ discardReadBytes: state.discardReadBytes,
15005
+ prefetchCache: state.prefetchCache
14299
15006
  });
14300
15007
  state.timings.timeSeeking += Date.now() - seekStart;
14301
15008
  }
@@ -14402,6 +15109,9 @@ var setSeekingHints = ({
14402
15109
  setSeekingHintsForAac();
14403
15110
  return;
14404
15111
  }
15112
+ if (hints.type === "m3u8-seeking-hints") {
15113
+ return;
15114
+ }
14405
15115
  throw new Error(`Unknown seeking hints type: ${hints}`);
14406
15116
  };
14407
15117
 
@@ -14557,12 +15267,16 @@ var getMfraAtom = async ({
14557
15267
  contentLength,
14558
15268
  readerInterface,
14559
15269
  controller,
14560
- parentSize
15270
+ parentSize,
15271
+ logLevel,
15272
+ prefetchCache
14561
15273
  }) => {
14562
15274
  const result = await readerInterface.read({
14563
15275
  controller,
14564
15276
  range: [contentLength - parentSize, contentLength - 1],
14565
- src
15277
+ src,
15278
+ logLevel,
15279
+ prefetchCache
14566
15280
  });
14567
15281
  const iterator = getArrayBufferIterator(new Uint8Array, parentSize);
14568
15282
  while (true) {
@@ -14582,12 +15296,16 @@ var getMfroAtom = async ({
14582
15296
  src,
14583
15297
  contentLength,
14584
15298
  readerInterface,
14585
- controller
15299
+ controller,
15300
+ logLevel,
15301
+ prefetchCache
14586
15302
  }) => {
14587
15303
  const result = await readerInterface.read({
14588
15304
  controller,
14589
15305
  range: [contentLength - 16, contentLength - 1],
14590
- src
15306
+ src,
15307
+ logLevel,
15308
+ prefetchCache
14591
15309
  });
14592
15310
  const { value } = await result.reader.reader.read();
14593
15311
  if (!value) {
@@ -14622,13 +15340,16 @@ var getMfraSeekingBox = async ({
14622
15340
  controller,
14623
15341
  readerInterface,
14624
15342
  src,
14625
- logLevel
15343
+ logLevel,
15344
+ prefetchCache
14626
15345
  }) => {
14627
15346
  const parentSize = await getMfroAtom({
14628
15347
  contentLength,
14629
15348
  controller,
14630
15349
  readerInterface,
14631
- src
15350
+ src,
15351
+ logLevel,
15352
+ prefetchCache
14632
15353
  });
14633
15354
  if (!parentSize) {
14634
15355
  return null;
@@ -14638,7 +15359,9 @@ var getMfraSeekingBox = async ({
14638
15359
  controller,
14639
15360
  readerInterface,
14640
15361
  src,
14641
- parentSize
15362
+ parentSize,
15363
+ logLevel,
15364
+ prefetchCache
14642
15365
  });
14643
15366
  mfraAtom.discard(8);
14644
15367
  return getIsoBaseMediaChildren({
@@ -14656,7 +15379,8 @@ var lazyMfraLoad = ({
14656
15379
  controller,
14657
15380
  readerInterface,
14658
15381
  src,
14659
- logLevel
15382
+ logLevel,
15383
+ prefetchCache
14660
15384
  }) => {
14661
15385
  let prom = null;
14662
15386
  let result = null;
@@ -14670,7 +15394,8 @@ var lazyMfraLoad = ({
14670
15394
  controller,
14671
15395
  readerInterface,
14672
15396
  src,
14673
- logLevel
15397
+ logLevel,
15398
+ prefetchCache
14674
15399
  }).then((boxes) => {
14675
15400
  Log.verbose(logLevel, "Lazily found mfra atom.");
14676
15401
  result = boxes;
@@ -14711,7 +15436,8 @@ var isoBaseMediaState = ({
14711
15436
  controller,
14712
15437
  readerInterface,
14713
15438
  src,
14714
- logLevel
15439
+ logLevel,
15440
+ prefetchCache
14715
15441
  }) => {
14716
15442
  return {
14717
15443
  flatSamples: cachedSamplePositionsState(),
@@ -14721,7 +15447,8 @@ var isoBaseMediaState = ({
14721
15447
  controller,
14722
15448
  readerInterface,
14723
15449
  src,
14724
- logLevel
15450
+ logLevel,
15451
+ prefetchCache
14725
15452
  }),
14726
15453
  moof: precomputedMoofState(),
14727
15454
  tfra: precomputedTfraState()
@@ -14738,6 +15465,7 @@ var keyframesState = () => {
14738
15465
  keyframes.push(keyframe);
14739
15466
  };
14740
15467
  const getKeyframes2 = () => {
15468
+ keyframes.sort((a, b) => a.positionInBytes - b.positionInBytes);
14741
15469
  return keyframes;
14742
15470
  };
14743
15471
  const setFromSeekingHints = (keyframesFromHints) => {
@@ -14760,8 +15488,11 @@ var sampleSorter = ({
14760
15488
  const streamsWithTracks = [];
14761
15489
  const audioCallbacks = {};
14762
15490
  const videoCallbacks = {};
14763
- const latestSample = {};
15491
+ let latestSample = {};
14764
15492
  return {
15493
+ clearSamples: () => {
15494
+ latestSample = {};
15495
+ },
14765
15496
  addToStreamWithTrack: (src) => {
14766
15497
  streamsWithTracks.push(src);
14767
15498
  },
@@ -14834,6 +15565,8 @@ var m3uState = (logLevel) => {
14834
15565
  const hasEmittedAudioTrack = {};
14835
15566
  const hasEmittedDoneWithTracks = {};
14836
15567
  let hasFinishedManifest = false;
15568
+ const seekToSecondsToProcess = {};
15569
+ const nextSeekShouldSubtractChunks = {};
14837
15570
  let readyToIterateOverM3u = false;
14838
15571
  const allChunksProcessed = {};
14839
15572
  const m3uStreamRuns = {};
@@ -14895,6 +15628,11 @@ var m3uState = (logLevel) => {
14895
15628
  setAllChunksProcessed: (src) => {
14896
15629
  allChunksProcessed[src] = true;
14897
15630
  },
15631
+ clearAllChunksProcessed: () => {
15632
+ Object.keys(allChunksProcessed).forEach((key) => {
15633
+ delete allChunksProcessed[key];
15634
+ });
15635
+ },
14898
15636
  getAllChunksProcessedForPlaylist,
14899
15637
  getAllChunksProcessedOverall: () => {
14900
15638
  if (!selectedMainPlaylist) {
@@ -14922,6 +15660,11 @@ var m3uState = (logLevel) => {
14922
15660
  getTrackDone: (playlistUrl) => {
14923
15661
  return tracksDone[playlistUrl];
14924
15662
  },
15663
+ clearTracksDone: () => {
15664
+ Object.keys(tracksDone).forEach((key) => {
15665
+ delete tracksDone[key];
15666
+ });
15667
+ },
14925
15668
  getM3uStreamRun: (playlistUrl) => m3uStreamRuns[playlistUrl] ?? null,
14926
15669
  abortM3UStreamRuns: () => {
14927
15670
  const values = Object.values(m3uStreamRuns);
@@ -14940,7 +15683,15 @@ var m3uState = (logLevel) => {
14940
15683
  getSelectedPlaylists,
14941
15684
  sampleSorter: sampleSorter({ logLevel, getAllChunksProcessedForPlaylist }),
14942
15685
  setMp4HeaderSegment,
14943
- getMp4HeaderSegment
15686
+ getMp4HeaderSegment,
15687
+ setSeekToSecondsToProcess: (playlistUrl, m3uSeek) => {
15688
+ seekToSecondsToProcess[playlistUrl] = m3uSeek;
15689
+ },
15690
+ getSeekToSecondsToProcess: (playlistUrl) => seekToSecondsToProcess[playlistUrl] ?? null,
15691
+ setNextSeekShouldSubtractChunks: (playlistUrl, chunks) => {
15692
+ nextSeekShouldSubtractChunks[playlistUrl] = chunks;
15693
+ },
15694
+ getNextSeekShouldSubtractChunks: (playlistUrl) => nextSeekShouldSubtractChunks[playlistUrl] ?? 0
14944
15695
  };
14945
15696
  };
14946
15697
 
@@ -14989,12 +15740,15 @@ var fetchWebmCues = async ({
14989
15740
  readerInterface,
14990
15741
  controller,
14991
15742
  position,
14992
- logLevel
15743
+ logLevel,
15744
+ prefetchCache
14993
15745
  }) => {
14994
15746
  const result = await readerInterface.read({
14995
15747
  controller,
14996
15748
  range: position,
14997
- src
15749
+ src,
15750
+ logLevel,
15751
+ prefetchCache
14998
15752
  });
14999
15753
  const { value } = await result.reader.reader.read();
15000
15754
  if (!value) {
@@ -15021,7 +15775,8 @@ var lazyCuesFetch = ({
15021
15775
  controller,
15022
15776
  logLevel,
15023
15777
  readerInterface,
15024
- src
15778
+ src,
15779
+ prefetchCache
15025
15780
  }) => {
15026
15781
  let prom = null;
15027
15782
  let sOffset = null;
@@ -15043,7 +15798,8 @@ var lazyCuesFetch = ({
15043
15798
  logLevel,
15044
15799
  position,
15045
15800
  readerInterface,
15046
- src
15801
+ src,
15802
+ prefetchCache
15047
15803
  }).then((cues) => {
15048
15804
  Log.verbose(logLevel, "Cues loaded");
15049
15805
  result = cues;
@@ -15105,7 +15861,8 @@ var webmState = ({
15105
15861
  controller,
15106
15862
  logLevel,
15107
15863
  readerInterface,
15108
- src
15864
+ src,
15865
+ prefetchCache
15109
15866
  }) => {
15110
15867
  const trackEntries = {};
15111
15868
  const onTrackEntrySegment = (trackEntry2) => {
@@ -15166,7 +15923,8 @@ var webmState = ({
15166
15923
  controller,
15167
15924
  logLevel,
15168
15925
  readerInterface,
15169
- src
15926
+ src,
15927
+ prefetchCache
15170
15928
  });
15171
15929
  const getTimeStampMapForSeekingHints = () => {
15172
15930
  return timestampMap;
@@ -15247,13 +16005,16 @@ var fetchIdx1 = async ({
15247
16005
  readerInterface,
15248
16006
  controller,
15249
16007
  position,
15250
- logLevel
16008
+ logLevel,
16009
+ prefetchCache
15251
16010
  }) => {
15252
16011
  Log.verbose(logLevel, "Making request to fetch idx1 from ", src, "position", position);
15253
16012
  const result = await readerInterface.read({
15254
16013
  controller,
15255
16014
  range: position,
15256
- src
16015
+ src,
16016
+ logLevel,
16017
+ prefetchCache
15257
16018
  });
15258
16019
  const iterator = getArrayBufferIterator(new Uint8Array, Infinity);
15259
16020
  while (true) {
@@ -15284,7 +16045,8 @@ var lazyIdx1Fetch = ({
15284
16045
  controller,
15285
16046
  logLevel,
15286
16047
  readerInterface,
15287
- src
16048
+ src,
16049
+ prefetchCache
15288
16050
  }) => {
15289
16051
  let prom = null;
15290
16052
  let result = null;
@@ -15300,7 +16062,8 @@ var lazyIdx1Fetch = ({
15300
16062
  logLevel,
15301
16063
  position,
15302
16064
  readerInterface,
15303
- src
16065
+ src,
16066
+ prefetchCache
15304
16067
  }).then((entries) => {
15305
16068
  prom = null;
15306
16069
  result = entries;
@@ -15421,7 +16184,8 @@ var riffSpecificState = ({
15421
16184
  controller,
15422
16185
  logLevel,
15423
16186
  readerInterface,
15424
- src
16187
+ src,
16188
+ prefetchCache
15425
16189
  }) => {
15426
16190
  let avcProfile = null;
15427
16191
  let nextTrackIndex = 0;
@@ -15440,7 +16204,8 @@ var riffSpecificState = ({
15440
16204
  controller,
15441
16205
  logLevel,
15442
16206
  readerInterface,
15443
- src
16207
+ src,
16208
+ prefetchCache
15444
16209
  });
15445
16210
  const sampleCounter = riffSampleCounter();
15446
16211
  return {
@@ -15461,7 +16226,7 @@ var riffSpecificState = ({
15461
16226
  };
15462
16227
 
15463
16228
  // src/state/sample-callbacks.ts
15464
- var sampleCallback = ({
16229
+ var callbacksState = ({
15465
16230
  controller,
15466
16231
  hasAudioTrackHandlers,
15467
16232
  hasVideoTrackHandlers,
@@ -15768,14 +16533,15 @@ var makeParserState = ({
15768
16533
  onDiscardedData,
15769
16534
  selectM3uStreamFn,
15770
16535
  selectM3uAssociatedPlaylistsFn,
15771
- mp4HeaderSegment,
16536
+ m3uPlaylistContext,
15772
16537
  contentType,
15773
16538
  name,
15774
16539
  callbacks,
15775
16540
  fieldsInReturnValue,
15776
16541
  mimeType,
15777
16542
  initialReaderInstance,
15778
- makeSamplesStartAtZero
16543
+ makeSamplesStartAtZero,
16544
+ prefetchCache
15779
16545
  }) => {
15780
16546
  let skippedBytes = 0;
15781
16547
  const returnValue = {};
@@ -15807,22 +16573,35 @@ var makeParserState = ({
15807
16573
  callbacks
15808
16574
  });
15809
16575
  return {
15810
- riff: riffSpecificState({ controller, logLevel, readerInterface, src }),
16576
+ riff: riffSpecificState({
16577
+ controller,
16578
+ logLevel,
16579
+ readerInterface,
16580
+ src,
16581
+ prefetchCache
16582
+ }),
15811
16583
  transportStream: transportStreamState(),
15812
- webm: webmState({ controller, logLevel, readerInterface, src }),
16584
+ webm: webmState({
16585
+ controller,
16586
+ logLevel,
16587
+ readerInterface,
16588
+ src,
16589
+ prefetchCache
16590
+ }),
15813
16591
  iso: isoBaseMediaState({
15814
16592
  contentLength,
15815
16593
  controller,
15816
16594
  readerInterface,
15817
16595
  src,
15818
- logLevel
16596
+ logLevel,
16597
+ prefetchCache
15819
16598
  }),
15820
16599
  mp3,
15821
16600
  aac: aacState(),
15822
16601
  flac: flacState(),
15823
16602
  m3u: m3uState(logLevel),
15824
16603
  timings,
15825
- callbacks: sampleCallback({
16604
+ callbacks: callbacksState({
15826
16605
  controller,
15827
16606
  hasAudioTrackHandlers,
15828
16607
  hasVideoTrackHandlers,
@@ -15860,7 +16639,7 @@ var makeParserState = ({
15860
16639
  discardReadBytes,
15861
16640
  selectM3uStreamFn,
15862
16641
  selectM3uAssociatedPlaylistsFn,
15863
- mp4HeaderSegment,
16642
+ m3uPlaylistContext,
15864
16643
  contentType,
15865
16644
  name,
15866
16645
  returnValue,
@@ -15870,7 +16649,8 @@ var makeParserState = ({
15870
16649
  errored,
15871
16650
  currentReader: currentReaderState,
15872
16651
  seekInfiniteLoop,
15873
- makeSamplesStartAtZero
16652
+ makeSamplesStartAtZero,
16653
+ prefetchCache
15874
16654
  };
15875
16655
  };
15876
16656
 
@@ -15949,7 +16729,7 @@ var internalParseMedia = async function({
15949
16729
  apiName,
15950
16730
  selectM3uStream: selectM3uStreamFn,
15951
16731
  selectM3uAssociatedPlaylists: selectM3uAssociatedPlaylistsFn,
15952
- mp4HeaderSegment,
16732
+ m3uPlaylistContext,
15953
16733
  makeSamplesStartAtZero,
15954
16734
  seekingHints,
15955
16735
  ...more
@@ -15961,6 +16741,7 @@ var internalParseMedia = async function({
15961
16741
  apiName
15962
16742
  });
15963
16743
  Log.verbose(logLevel, `Reading ${typeof src === "string" ? src : src instanceof URL ? src.toString() : src instanceof File ? src.name : src.toString()}`);
16744
+ const prefetchCache = new Map;
15964
16745
  const {
15965
16746
  reader: readerInstance,
15966
16747
  contentLength,
@@ -15968,7 +16749,13 @@ var internalParseMedia = async function({
15968
16749
  contentType,
15969
16750
  supportsContentRange,
15970
16751
  needsContentRange
15971
- } = await readerInterface.read({ src, range: null, controller });
16752
+ } = await readerInterface.read({
16753
+ src,
16754
+ range: null,
16755
+ controller,
16756
+ logLevel,
16757
+ prefetchCache
16758
+ });
15972
16759
  if (contentLength === null) {
15973
16760
  throw new Error(`Cannot read media ${src} without a content length. This is currently not supported. Ensure the media has a "Content-Length" HTTP header.`);
15974
16761
  }
@@ -15991,14 +16778,15 @@ var internalParseMedia = async function({
15991
16778
  onDiscardedData,
15992
16779
  selectM3uStreamFn,
15993
16780
  selectM3uAssociatedPlaylistsFn,
15994
- mp4HeaderSegment,
16781
+ m3uPlaylistContext,
15995
16782
  contentType,
15996
16783
  name,
15997
16784
  callbacks: more,
15998
16785
  fieldsInReturnValue: _fieldsInReturnValue ?? {},
15999
16786
  mimeType: contentType,
16000
16787
  initialReaderInstance: readerInstance,
16001
- makeSamplesStartAtZero
16788
+ makeSamplesStartAtZero,
16789
+ prefetchCache
16002
16790
  });
16003
16791
  if (seekingHints) {
16004
16792
  setSeekingHints({ hints: seekingHints, state });
@@ -16008,7 +16796,7 @@ var internalParseMedia = async function({
16008
16796
  keyframesState: state.keyframes,
16009
16797
  webmState: state.webm,
16010
16798
  structureState: state.structure,
16011
- mp4HeaderSegment: state.mp4HeaderSegment,
16799
+ m3uPlaylistContext: state.m3uPlaylistContext,
16012
16800
  mediaSectionState: state.mediaSection,
16013
16801
  isoState: state.iso,
16014
16802
  transportStream: state.transportStream,
@@ -16037,6 +16825,7 @@ var internalParseMedia = async function({
16037
16825
  state.iterator?.destroy();
16038
16826
  state.callbacks.tracks.ensureHasTracksAtEnd(state.fields);
16039
16827
  state.m3u.abortM3UStreamRuns();
16828
+ prefetchCache.clear();
16040
16829
  if (state.errored) {
16041
16830
  throw state.errored;
16042
16831
  }
@@ -16063,7 +16852,7 @@ var downloadAndParseMedia = async (options) => {
16063
16852
  onDimensions: options.onDimensions ?? null,
16064
16853
  selectM3uStream: options.selectM3uStream ?? defaultSelectM3uStreamFn,
16065
16854
  selectM3uAssociatedPlaylists: options.selectM3uAssociatedPlaylists ?? defaultSelectM3uAssociatedPlaylists,
16066
- mp4HeaderSegment: options.mp4HeaderSegment ?? null,
16855
+ m3uPlaylistContext: options.m3uPlaylistContext ?? null,
16067
16856
  onDiscardedData: async (data) => {
16068
16857
  await content.write(data);
16069
16858
  },
@@ -16116,7 +16905,7 @@ var downloadAndParseMedia = async (options) => {
16116
16905
  return returnValue;
16117
16906
  };
16118
16907
  // src/version.ts
16119
- var VERSION = "4.0.289";
16908
+ var VERSION = "4.0.291";
16120
16909
 
16121
16910
  // src/index.ts
16122
16911
  var MediaParserInternals = {