@remotion/media-parser 4.0.290 → 4.0.292

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 (160) hide show
  1. package/dist/containers/iso-base-media/base-media-box.d.ts +0 -1
  2. package/dist/containers/iso-base-media/collect-sample-positions-from-moof-boxes.d.ts +4 -1
  3. package/dist/containers/iso-base-media/collect-sample-positions-from-moof-boxes.js +9 -5
  4. package/dist/containers/iso-base-media/find-keyframe-before-time.js +16 -11
  5. package/dist/containers/iso-base-media/find-track-to-seek.d.ts +14 -0
  6. package/dist/containers/iso-base-media/find-track-to-seek.js +39 -0
  7. package/dist/containers/iso-base-media/get-children.js +2 -2
  8. package/dist/containers/iso-base-media/get-keyframes.js +6 -1
  9. package/dist/containers/iso-base-media/get-mfra-seeking-box.d.ts +3 -1
  10. package/dist/containers/iso-base-media/get-mfra-seeking-box.js +5 -1
  11. package/dist/containers/iso-base-media/get-moov-atom.js +6 -3
  12. package/dist/containers/iso-base-media/get-sample-position-bounds.js +3 -1
  13. package/dist/containers/iso-base-media/get-sample-positions-from-track.js +1 -1
  14. package/dist/containers/iso-base-media/get-seeking-byte-from-fragmented-mp4.d.ts +14 -0
  15. package/dist/containers/iso-base-media/get-seeking-byte-from-fragmented-mp4.js +89 -0
  16. package/dist/containers/iso-base-media/get-seeking-byte.d.ts +3 -3
  17. package/dist/containers/iso-base-media/get-seeking-byte.js +32 -96
  18. package/dist/containers/iso-base-media/mdat/calculate-jump-marks.d.ts +6 -0
  19. package/dist/containers/iso-base-media/mdat/calculate-jump-marks.js +131 -0
  20. package/dist/containers/iso-base-media/mdat/mdat.d.ts +2 -2
  21. package/dist/containers/iso-base-media/mdat/mdat.js +18 -2
  22. package/dist/containers/iso-base-media/mfra/find-best-segment-from-tfra.d.ts +3 -3
  23. package/dist/containers/iso-base-media/mfra/find-best-segment-from-tfra.js +2 -2
  24. package/dist/containers/iso-base-media/mfra/get-mfra-atom.d.ts +5 -1
  25. package/dist/containers/iso-base-media/mfra/get-mfra-atom.js +3 -1
  26. package/dist/containers/iso-base-media/mfra/get-mfro-atom.d.ts +5 -1
  27. package/dist/containers/iso-base-media/mfra/get-mfro-atom.js +3 -1
  28. package/dist/containers/iso-base-media/parse-boxes.js +5 -2
  29. package/dist/containers/iso-base-media/process-box.d.ts +16 -5
  30. package/dist/containers/iso-base-media/process-box.js +206 -118
  31. package/dist/containers/iso-base-media/sample-positions.d.ts +25 -0
  32. package/dist/containers/iso-base-media/sample-positions.js +37 -0
  33. package/dist/containers/iso-base-media/stsd/samples.js +1 -0
  34. package/dist/containers/iso-base-media/stsd/stsc.d.ts +1 -6
  35. package/dist/containers/iso-base-media/stsd/stsc.js +2 -5
  36. package/dist/containers/iso-base-media/stsd/stss.d.ts +1 -1
  37. package/dist/containers/iso-base-media/stsd/stss.js +2 -2
  38. package/dist/containers/iso-base-media/turn-sample-positions-into-array.d.ts +19 -0
  39. package/dist/containers/iso-base-media/turn-sample-positions-into-array.js +73 -0
  40. package/dist/containers/m3u/after-manifest-fetch.d.ts +5 -1
  41. package/dist/containers/m3u/after-manifest-fetch.js +3 -1
  42. package/dist/containers/m3u/first-sample-in-m3u-chunk.d.ts +13 -0
  43. package/dist/containers/m3u/first-sample-in-m3u-chunk.js +31 -0
  44. package/dist/containers/m3u/get-seeking-byte.d.ts +13 -0
  45. package/dist/containers/m3u/get-seeking-byte.js +32 -0
  46. package/dist/containers/m3u/get-streams.d.ts +1 -0
  47. package/dist/containers/m3u/get-streams.js +1 -0
  48. package/dist/containers/m3u/iterate-over-segment-files.d.ts +5 -3
  49. package/dist/containers/m3u/iterate-over-segment-files.js +11 -1
  50. package/dist/containers/m3u/parse-m3u-media-directive.js +1 -0
  51. package/dist/containers/m3u/parse-m3u.js +8 -0
  52. package/dist/containers/m3u/process-m3u-chunk.d.ts +12 -0
  53. package/dist/containers/m3u/process-m3u-chunk.js +274 -0
  54. package/dist/containers/m3u/run-over-m3u.js +7 -80
  55. package/dist/containers/m3u/sample-sorter.d.ts +1 -0
  56. package/dist/containers/m3u/sample-sorter.js +4 -1
  57. package/dist/containers/m3u/seek/get-chunk-to-seek-to.d.ts +5 -0
  58. package/dist/containers/m3u/seek/get-chunk-to-seek-to.js +14 -0
  59. package/dist/containers/m3u/seeking-hints.d.ts +2 -0
  60. package/dist/containers/m3u/seeking-hints.js +9 -0
  61. package/dist/containers/m3u/select-stream.d.ts +2 -1
  62. package/dist/containers/m3u/select-stream.js +7 -2
  63. package/dist/containers/m3u/types.d.ts +1 -0
  64. package/dist/containers/riff/seek/fetch-idx1.d.ts +3 -1
  65. package/dist/containers/riff/seek/fetch-idx1.js +3 -1
  66. package/dist/containers/transport-stream/handle-aac-packet.d.ts +2 -2
  67. package/dist/containers/transport-stream/handle-avc-packet.d.ts +2 -2
  68. package/dist/containers/transport-stream/process-audio.d.ts +2 -2
  69. package/dist/containers/transport-stream/process-stream-buffers.d.ts +3 -3
  70. package/dist/containers/transport-stream/process-video.d.ts +2 -2
  71. package/dist/containers/webm/get-sample-from-block.d.ts +12 -2
  72. package/dist/containers/webm/get-sample-from-block.js +40 -9
  73. package/dist/containers/webm/parse-ebml.js +28 -10
  74. package/dist/containers/webm/seek/fetch-web-cues.d.ts +3 -1
  75. package/dist/containers/webm/seek/fetch-web-cues.js +3 -1
  76. package/dist/containers/webm/state-for-processing.d.ts +2 -2
  77. package/dist/controller/media-parser-controller.d.ts +1 -1
  78. package/dist/controller/media-parser-controller.js +6 -2
  79. package/dist/controller/seek-signal.d.ts +1 -5
  80. package/dist/download-and-parse-media.js +1 -1
  81. package/dist/esm/index.mjs +1400 -611
  82. package/dist/esm/node.mjs +23 -3
  83. package/dist/esm/server-worker.mjs +8 -1
  84. package/dist/esm/universal.mjs +168 -15
  85. package/dist/esm/web.mjs +145 -13
  86. package/dist/esm/worker-server-entry.mjs +1467 -635
  87. package/dist/esm/worker-web-entry.mjs +1439 -634
  88. package/dist/esm/worker.mjs +8 -1
  89. package/dist/get-audio-codec.js +3 -0
  90. package/dist/get-duration.js +2 -1
  91. package/dist/get-fps.js +2 -1
  92. package/dist/get-sample-positions-from-mp4.js +10 -5
  93. package/dist/get-sample-positions.js +4 -4
  94. package/dist/get-seeking-byte.d.ts +5 -3
  95. package/dist/get-seeking-byte.js +19 -10
  96. package/dist/get-seeking-hints.d.ts +3 -3
  97. package/dist/get-seeking-hints.js +18 -13
  98. package/dist/get-tracks.d.ts +9 -1
  99. package/dist/get-tracks.js +13 -6
  100. package/dist/index.d.ts +21 -5
  101. package/dist/init-video.js +3 -2
  102. package/dist/internal-parse-media.js +13 -4
  103. package/dist/iterator/buffer-iterator.js +5 -3
  104. package/dist/metadata/metadata-from-iso.js +2 -1
  105. package/dist/options.d.ts +6 -1
  106. package/dist/parse-loop.js +22 -6
  107. package/dist/parse-media-on-worker-entry.js +1 -0
  108. package/dist/parse-media.js +1 -1
  109. package/dist/parse-result.d.ts +2 -2
  110. package/dist/perform-seek.d.ts +3 -1
  111. package/dist/perform-seek.js +3 -1
  112. package/dist/readers/fetch/get-body-and-reader.js +17 -2
  113. package/dist/readers/from-fetch.d.ts +17 -1
  114. package/dist/readers/from-fetch.js +68 -13
  115. package/dist/readers/from-node.js +24 -2
  116. package/dist/readers/from-web-file.js +3 -0
  117. package/dist/readers/reader.d.ts +19 -2
  118. package/dist/readers/universal.js +9 -0
  119. package/dist/readers/web.js +6 -0
  120. package/dist/register-track.d.ts +3 -3
  121. package/dist/seek-backwards.d.ts +3 -1
  122. package/dist/seek-backwards.js +4 -1
  123. package/dist/seek-forwards.d.ts +3 -1
  124. package/dist/seek-forwards.js +3 -1
  125. package/dist/seeking-hints.d.ts +4 -1
  126. package/dist/set-seeking-hints.js +4 -0
  127. package/dist/skip.d.ts +5 -0
  128. package/dist/skip.js +6 -1
  129. package/dist/state/can-skip-tracks.d.ts +1 -0
  130. package/dist/state/can-skip-tracks.js +10 -6
  131. package/dist/state/iso-base-media/cached-sample-positions.d.ts +15 -1
  132. package/dist/state/iso-base-media/cached-sample-positions.js +9 -4
  133. package/dist/state/iso-base-media/iso-state.d.ts +5 -1
  134. package/dist/state/iso-base-media/iso-state.js +2 -1
  135. package/dist/state/iso-base-media/lazy-mfra-load.d.ts +3 -1
  136. package/dist/state/iso-base-media/lazy-mfra-load.js +2 -1
  137. package/dist/state/keyframes.js +1 -0
  138. package/dist/state/m3u-state.d.ts +15 -4
  139. package/dist/state/m3u-state.js +20 -0
  140. package/dist/state/matroska/lazy-cues-fetch.d.ts +3 -1
  141. package/dist/state/matroska/lazy-cues-fetch.js +2 -1
  142. package/dist/state/matroska/webm.d.ts +3 -1
  143. package/dist/state/matroska/webm.js +2 -1
  144. package/dist/state/parser-state.d.ts +29 -13
  145. package/dist/state/parser-state.js +19 -5
  146. package/dist/state/riff/lazy-idx1-fetch.d.ts +3 -1
  147. package/dist/state/riff/lazy-idx1-fetch.js +2 -1
  148. package/dist/state/riff.d.ts +3 -1
  149. package/dist/state/riff.js +2 -1
  150. package/dist/state/sample-callbacks.d.ts +3 -2
  151. package/dist/state/sample-callbacks.js +3 -3
  152. package/dist/version.d.ts +1 -1
  153. package/dist/version.js +1 -1
  154. package/dist/work-on-seek-request.d.ts +6 -3
  155. package/dist/work-on-seek-request.js +13 -13
  156. package/dist/worker/forward-controller-to-worker.js +1 -1
  157. package/dist/worker/serialize-error.js +26 -3
  158. package/dist/worker/worker-types.d.ts +7 -1
  159. package/dist/worker-server.js +2 -2
  160. package/package.json +3 -3
@@ -98,6 +98,40 @@ class MediaParserAbortError extends Error {
98
98
  }
99
99
  }
100
100
 
101
+ // src/log.ts
102
+ var logLevels = ["trace", "verbose", "info", "warn", "error"];
103
+ var getNumberForLogLevel = (level) => {
104
+ return logLevels.indexOf(level);
105
+ };
106
+ var isEqualOrBelowLogLevel = (currentLevel, level) => {
107
+ return getNumberForLogLevel(currentLevel) <= getNumberForLogLevel(level);
108
+ };
109
+ var Log = {
110
+ trace: (logLevel, ...args) => {
111
+ if (isEqualOrBelowLogLevel(logLevel, "trace")) {
112
+ return console.log(...args);
113
+ }
114
+ },
115
+ verbose: (logLevel, ...args) => {
116
+ if (isEqualOrBelowLogLevel(logLevel, "verbose")) {
117
+ return console.log(...args);
118
+ }
119
+ },
120
+ info: (logLevel, ...args) => {
121
+ if (isEqualOrBelowLogLevel(logLevel, "info")) {
122
+ return console.log(...args);
123
+ }
124
+ },
125
+ warn: (logLevel, ...args) => {
126
+ if (isEqualOrBelowLogLevel(logLevel, "warn")) {
127
+ return console.warn(...args);
128
+ }
129
+ },
130
+ error: (...args) => {
131
+ return console.error(...args);
132
+ }
133
+ };
134
+
101
135
  // src/readers/fetch/get-body-and-reader.ts
102
136
  var getLengthAndReader = async ({
103
137
  canLiveWithoutContentLength,
@@ -110,10 +144,22 @@ var getLengthAndReader = async ({
110
144
  if (requestedWithoutRange || canLiveWithoutContentLength && contentLength === null) {
111
145
  const buffer = await res.arrayBuffer();
112
146
  const encoded = new Uint8Array(buffer);
147
+ let streamCancelled = false;
113
148
  const stream = new ReadableStream({
114
149
  start(controller) {
115
- controller.enqueue(encoded);
116
- controller.close();
150
+ if (ownController.signal.aborted) {
151
+ return;
152
+ }
153
+ if (streamCancelled) {
154
+ return;
155
+ }
156
+ try {
157
+ controller.enqueue(encoded);
158
+ controller.close();
159
+ } catch {}
160
+ },
161
+ cancel() {
162
+ streamCancelled = true;
117
163
  }
118
164
  });
119
165
  return {
@@ -189,14 +235,11 @@ var validateContentRangeAndDetectIfSupported = ({
189
235
  }
190
236
  return { supportsContentRange: true };
191
237
  };
192
- var fetchReadContent = async ({
193
- src,
238
+ var makeFetchRequest = async ({
194
239
  range,
240
+ src,
195
241
  controller
196
242
  }) => {
197
- if (typeof src !== "string" && src instanceof URL === false) {
198
- throw new Error("src must be a string when using `fetchReader`");
199
- }
200
243
  const resolvedUrl = resolveUrl(src);
201
244
  const resolvedUrlString = resolvedUrl.toString();
202
245
  if (!resolvedUrlString.startsWith("https://") && !resolvedUrlString.startsWith("blob:") && !resolvedUrlString.startsWith("http://")) {
@@ -225,21 +268,83 @@ var fetchReadContent = async ({
225
268
  parsedContentRange,
226
269
  statusCode: res.status
227
270
  });
228
- controller._internals.signal.addEventListener("abort", () => {
229
- ownController.abort(new MediaParserAbortError("Aborted by user"));
230
- }, { once: true });
271
+ if (controller) {
272
+ controller._internals.signal.addEventListener("abort", () => {
273
+ ownController.abort(new MediaParserAbortError("Aborted by user"));
274
+ }, { once: true });
275
+ }
231
276
  if (res.status.toString().startsWith("4") || res.status.toString().startsWith("5")) {
232
- throw new Error(`Server returned status code ${res.status} for ${src} and range ${requestedRange}`);
277
+ throw new Error(`Server returned status code ${res.status} for ${resolvedUrl} and range ${requestedRange}`);
233
278
  }
234
279
  const contentDisposition = res.headers.get("content-disposition");
235
280
  const name = contentDisposition?.match(/filename="([^"]+)"/)?.[1];
236
- const fallbackName = src.toString().split("/").pop();
237
281
  const { contentLength, needsContentRange, reader } = await getLengthAndReader({
238
282
  canLiveWithoutContentLength,
239
283
  res,
240
284
  ownController,
241
285
  requestedWithoutRange: requestWithoutRange
242
286
  });
287
+ const contentType = res.headers.get("content-type");
288
+ return {
289
+ contentLength,
290
+ needsContentRange,
291
+ reader,
292
+ name,
293
+ contentType,
294
+ supportsContentRange
295
+ };
296
+ };
297
+ var cacheKey = ({
298
+ src,
299
+ range
300
+ }) => {
301
+ return `${src}-${JSON.stringify(range)}`;
302
+ };
303
+ var makeFetchRequestOrGetCached = ({
304
+ range,
305
+ src,
306
+ controller,
307
+ logLevel,
308
+ prefetchCache
309
+ }) => {
310
+ const key = cacheKey({ src, range });
311
+ const cached = prefetchCache.get(key);
312
+ if (cached) {
313
+ Log.verbose(logLevel, `Reading from preload cache for ${key}`);
314
+ return cached;
315
+ }
316
+ Log.verbose(logLevel, `Fetching ${key}`);
317
+ const result = makeFetchRequest({ range, src, controller });
318
+ prefetchCache.set(key, result);
319
+ return result;
320
+ };
321
+ var fetchReadContent = async ({
322
+ src,
323
+ range,
324
+ controller,
325
+ logLevel,
326
+ prefetchCache
327
+ }) => {
328
+ if (typeof src !== "string" && src instanceof URL === false) {
329
+ throw new Error("src must be a string when using `fetchReader`");
330
+ }
331
+ const fallbackName = src.toString().split("/").pop();
332
+ const {
333
+ reader,
334
+ contentLength,
335
+ needsContentRange,
336
+ name,
337
+ supportsContentRange,
338
+ contentType
339
+ } = await makeFetchRequestOrGetCached({
340
+ range,
341
+ src,
342
+ controller,
343
+ logLevel,
344
+ prefetchCache
345
+ });
346
+ const key = cacheKey({ src, range });
347
+ prefetchCache.delete(key);
243
348
  if (controller) {
244
349
  controller._internals.signal.addEventListener("abort", () => {
245
350
  reader.reader.cancel().catch(() => {});
@@ -248,12 +353,33 @@ var fetchReadContent = async ({
248
353
  return {
249
354
  reader,
250
355
  contentLength,
251
- contentType: res.headers.get("content-type"),
356
+ contentType,
252
357
  name: name ?? fallbackName,
253
358
  supportsContentRange,
254
359
  needsContentRange
255
360
  };
256
361
  };
362
+ var fetchPreload = ({
363
+ src,
364
+ range,
365
+ logLevel,
366
+ prefetchCache
367
+ }) => {
368
+ if (typeof src !== "string" && src instanceof URL === false) {
369
+ throw new Error("src must be a string when using `fetchReader`");
370
+ }
371
+ const key = cacheKey({ src, range });
372
+ if (prefetchCache.has(key)) {
373
+ return prefetchCache.get(key);
374
+ }
375
+ makeFetchRequestOrGetCached({
376
+ range,
377
+ src,
378
+ controller: null,
379
+ logLevel,
380
+ prefetchCache
381
+ });
382
+ };
257
383
  var fetchReadWholeAsText = async (src) => {
258
384
  if (typeof src !== "string" && src instanceof URL === false) {
259
385
  throw new Error("src must be a string when using `fetchReader`");
@@ -340,13 +466,20 @@ var webReader = {
340
466
  return webFileReadWholeAsText(src);
341
467
  }
342
468
  return fetchReadWholeAsText(src);
469
+ },
470
+ preload: ({ range, src, logLevel, prefetchCache }) => {
471
+ if (src instanceof Blob) {
472
+ return;
473
+ }
474
+ return fetchPreload({ range, src, logLevel, prefetchCache });
343
475
  }
344
476
  };
345
477
 
346
478
  // src/containers/m3u/select-stream.ts
347
479
  var selectAssociatedPlaylists = async ({
348
480
  playlists,
349
- fn
481
+ fn,
482
+ skipAudioTracks
350
483
  }) => {
351
484
  if (playlists.length < 1) {
352
485
  return Promise.resolve([]);
@@ -355,12 +488,17 @@ var selectAssociatedPlaylists = async ({
355
488
  if (!Array.isArray(streams)) {
356
489
  throw new Error("Expected an array of associated playlists");
357
490
  }
491
+ const selectedStreams = [];
358
492
  for (const stream of streams) {
493
+ if (stream.isAudio && skipAudioTracks) {
494
+ continue;
495
+ }
359
496
  if (!playlists.find((playlist) => playlist.src === stream.src)) {
360
497
  throw new Error(`The associated playlist ${JSON.stringify(streams)} cannot be selected because it was not in the list of selectable playlists`);
361
498
  }
499
+ selectedStreams.push(stream);
362
500
  }
363
- return streams;
501
+ return selectedStreams;
364
502
  };
365
503
  var defaultSelectM3uAssociatedPlaylists = ({ associatedPlaylists }) => {
366
504
  if (associatedPlaylists.length === 1) {
@@ -532,7 +670,11 @@ var mediaParserController = () => {
532
670
  const performedSeeksSignal = performedSeeksStats();
533
671
  const checkForAbortAndPause = async () => {
534
672
  if (abortController.signal.aborted) {
535
- throw new MediaParserAbortError("Aborted");
673
+ const err = new MediaParserAbortError("Aborted");
674
+ if (abortController.signal.reason) {
675
+ err.cause = abortController.signal.reason;
676
+ }
677
+ throw err;
536
678
  }
537
679
  await pauseSignal.waitUntilResume();
538
680
  };
@@ -554,7 +696,7 @@ var mediaParserController = () => {
554
696
  abortController.abort(reason);
555
697
  emitter.dispatchAbort(reason);
556
698
  },
557
- _experimentalSeek: seekSignal.seek,
699
+ seek: seekSignal.seek,
558
700
  pause: pauseSignal.pause,
559
701
  resume: pauseSignal.resume,
560
702
  addEventListener: emitter.addEventListener,
@@ -608,7 +750,8 @@ var getM3uStreams = ({
608
750
  language: audioTrack.language,
609
751
  name: audioTrack.name,
610
752
  src: readerInterface.createAdjacentFileSource(audioTrack.uri, originalSrc),
611
- id: associatedPlaylists.length
753
+ id: associatedPlaylists.length,
754
+ isAudio: true
612
755
  });
613
756
  }
614
757
  }
@@ -969,7 +1112,7 @@ var getFpsFromIsoMaseMedia = (state) => {
969
1112
  const moovBox = getMoovBoxFromState({
970
1113
  structureState: state.structure,
971
1114
  isoState: state.iso,
972
- mp4HeaderSegment: state.mp4HeaderSegment,
1115
+ mp4HeaderSegment: state.m3uPlaylistContext?.mp4HeaderSegment ?? null,
973
1116
  mayUsePrecomputed: true
974
1117
  });
975
1118
  if (!moovBox) {
@@ -2128,40 +2271,6 @@ var detectFileType = (data) => {
2128
2271
  return { type: "unknown" };
2129
2272
  };
2130
2273
 
2131
- // src/log.ts
2132
- var logLevels = ["trace", "verbose", "info", "warn", "error"];
2133
- var getNumberForLogLevel = (level) => {
2134
- return logLevels.indexOf(level);
2135
- };
2136
- var isEqualOrBelowLogLevel = (currentLevel, level) => {
2137
- return getNumberForLogLevel(currentLevel) <= getNumberForLogLevel(level);
2138
- };
2139
- var Log = {
2140
- trace: (logLevel, ...args) => {
2141
- if (isEqualOrBelowLogLevel(logLevel, "trace")) {
2142
- return console.log(...args);
2143
- }
2144
- },
2145
- verbose: (logLevel, ...args) => {
2146
- if (isEqualOrBelowLogLevel(logLevel, "verbose")) {
2147
- return console.log(...args);
2148
- }
2149
- },
2150
- info: (logLevel, ...args) => {
2151
- if (isEqualOrBelowLogLevel(logLevel, "info")) {
2152
- return console.log(...args);
2153
- }
2154
- },
2155
- warn: (logLevel, ...args) => {
2156
- if (isEqualOrBelowLogLevel(logLevel, "warn")) {
2157
- return console.warn(...args);
2158
- }
2159
- },
2160
- error: (...args) => {
2161
- return console.error(...args);
2162
- }
2163
- };
2164
-
2165
2274
  // src/iterator/buffer-manager.ts
2166
2275
  var bufferManager = ({
2167
2276
  initialData,
@@ -2339,7 +2448,7 @@ var getArrayBufferIterator = (initialData, maxBytes) => {
2339
2448
  const six = getUint8();
2340
2449
  const seven = getUint8();
2341
2450
  const eight = getUint8();
2342
- return eight << 56 | seven << 48 | six << 40 | five << 32 | four << 24 | three << 16 | two << 8 | one;
2451
+ return (eight << 56 | seven << 48 | six << 40 | five << 32 | four << 24 | three << 16 | two << 8 | one) >>> 0;
2343
2452
  }
2344
2453
  function byteArrayToBigInt(byteArray) {
2345
2454
  let result = BigInt(0);
@@ -2361,7 +2470,8 @@ var getArrayBufferIterator = (initialData, maxBytes) => {
2361
2470
  return Number(bigInt);
2362
2471
  };
2363
2472
  const getFourByteNumber = () => {
2364
- return getUint8() << 24 | getUint8() << 16 | getUint8() << 8 | getUint8();
2473
+ const unsigned = getUint8() << 24 | getUint8() << 16 | getUint8() << 8 | getUint8();
2474
+ return unsigned >>> 0;
2365
2475
  };
2366
2476
  const getPaddedFourByteNumber = () => {
2367
2477
  let lastInt = 128;
@@ -4027,7 +4137,7 @@ var isoBaseMediaHasTracks = (state, mayUsePrecomputed) => {
4027
4137
  return Boolean(getMoovBoxFromState({
4028
4138
  structureState: state.structure,
4029
4139
  isoState: state.iso,
4030
- mp4HeaderSegment: state.mp4HeaderSegment,
4140
+ mp4HeaderSegment: state.m3uPlaylistContext?.mp4HeaderSegment ?? null,
4031
4141
  mayUsePrecomputed
4032
4142
  }));
4033
4143
  };
@@ -4112,11 +4222,16 @@ var getTracksFromMoovBox = (moovBox) => {
4112
4222
  otherTracks
4113
4223
  };
4114
4224
  };
4115
- var getTracksFromIsoBaseMedia = (state, mayUsePrecomputed) => {
4225
+ var getTracksFromIsoBaseMedia = ({
4226
+ mayUsePrecomputed,
4227
+ structure,
4228
+ isoState,
4229
+ m3uPlaylistContext
4230
+ }) => {
4116
4231
  const moovBox = getMoovBoxFromState({
4117
- structureState: state.structure,
4118
- isoState: state.iso,
4119
- mp4HeaderSegment: state.mp4HeaderSegment,
4232
+ structureState: structure,
4233
+ isoState,
4234
+ mp4HeaderSegment: m3uPlaylistContext?.mp4HeaderSegment ?? null,
4120
4235
  mayUsePrecomputed
4121
4236
  });
4122
4237
  if (!moovBox) {
@@ -4145,7 +4260,12 @@ var getTracks = (state, mayUsePrecomputed) => {
4145
4260
  return getCategorizedTracksFromMatroska(state);
4146
4261
  }
4147
4262
  if (structure.type === "iso-base-media") {
4148
- return getTracksFromIsoBaseMedia(state, mayUsePrecomputed);
4263
+ return getTracksFromIsoBaseMedia({
4264
+ isoState: state.iso,
4265
+ m3uPlaylistContext: state.m3uPlaylistContext,
4266
+ structure: state.structure,
4267
+ mayUsePrecomputed
4268
+ });
4149
4269
  }
4150
4270
  if (structure.type === "riff") {
4151
4271
  return getTracksFromAvi(structure, state);
@@ -4351,6 +4471,9 @@ var getAudioCodecFromAudioCodecInfo = (codec) => {
4351
4471
  if (codec.format === "ac-3") {
4352
4472
  return "ac3";
4353
4473
  }
4474
+ if (codec.format === "Opus") {
4475
+ return "opus";
4476
+ }
4354
4477
  if (codec.format === "mp4a") {
4355
4478
  if (codec.primarySpecificator === 64) {
4356
4479
  return "aac";
@@ -4533,11 +4656,15 @@ var collectSamplePositionsFromMoofBoxes = ({
4533
4656
  tkhdBox
4534
4657
  }) => {
4535
4658
  const isComplete = tfraBoxes.length > 0 && tfraBoxes.every((t) => t.entries.length === moofBoxes.length);
4536
- const samplePositions = moofBoxes.map((m) => {
4537
- return getSamplesFromMoof({
4538
- moofBox: m,
4539
- trackId: tkhdBox.trackId
4540
- });
4659
+ const samplePositions = moofBoxes.map((m, index) => {
4660
+ const isLastFragment = index === moofBoxes.length - 1 && isComplete;
4661
+ return {
4662
+ isLastFragment,
4663
+ samples: getSamplesFromMoof({
4664
+ moofBox: m,
4665
+ trackId: tkhdBox.trackId
4666
+ })
4667
+ };
4541
4668
  });
4542
4669
  return { samplePositions, isComplete };
4543
4670
  };
@@ -4570,14 +4697,14 @@ var getSamplePositions = ({
4570
4697
  const samples = [];
4571
4698
  let samplesPerChunk = 1;
4572
4699
  for (let i = 0;i < chunks.length; i++) {
4573
- const hasEntry = stscBox.entries.find((entry) => entry.firstChunk === i + 1);
4574
- if (hasEntry) {
4575
- samplesPerChunk = hasEntry.samplesPerChunk;
4700
+ const hasEntry = stscBox.entries.get(i + 1);
4701
+ if (hasEntry !== undefined) {
4702
+ samplesPerChunk = hasEntry;
4576
4703
  }
4577
4704
  let offsetInThisChunk = 0;
4578
4705
  for (let j = 0;j < samplesPerChunk; j++) {
4579
4706
  const size = stszBox.countType === "fixed" ? stszBox.sampleSize : stszBox.entries[samples.length];
4580
- const isKeyframe = stssBox ? stssBox.sampleNumber.includes(samples.length + 1) : true;
4707
+ const isKeyframe = stssBox ? stssBox.sampleNumber.has(samples.length + 1) : true;
4581
4708
  const delta = sttsDeltas[samples.length];
4582
4709
  const ctsOffset = cttsEntries[samples.length];
4583
4710
  const cts = dts + ctsOffset;
@@ -4621,11 +4748,16 @@ var getGroupedSamplesPositionsFromMp4 = ({
4621
4748
  }
4622
4749
  const samples = [];
4623
4750
  let timestamp = 0;
4751
+ const stscKeys = Array.from(stscBox.entries.keys());
4624
4752
  for (let i = 0;i < stcoBox.entries.length; i++) {
4625
4753
  const entry = stcoBox.entries[i];
4626
4754
  const chunk = i + 1;
4627
- const stscEntry = stscBox.entries.findLast((e) => e.firstChunk <= chunk);
4628
- if (!stscEntry) {
4755
+ const stscEntry = stscKeys.findLast((e) => e <= chunk);
4756
+ if (stscEntry === undefined) {
4757
+ throw new Error("should not be");
4758
+ }
4759
+ const samplesPerChunk = stscBox.entries.get(stscEntry);
4760
+ if (samplesPerChunk === undefined) {
4629
4761
  throw new Error("should not be");
4630
4762
  }
4631
4763
  samples.push({
@@ -4633,13 +4765,13 @@ var getGroupedSamplesPositionsFromMp4 = ({
4633
4765
  cts: timestamp,
4634
4766
  dts: timestamp,
4635
4767
  offset: Number(entry),
4636
- size: stszBox.sampleSize * stscEntry.samplesPerChunk,
4637
- duration: stscEntry.samplesPerChunk,
4768
+ size: stszBox.sampleSize * samplesPerChunk,
4769
+ duration: samplesPerChunk,
4638
4770
  isKeyframe: true,
4639
4771
  bigEndian,
4640
4772
  chunkSize: stszBox.sampleSize
4641
4773
  });
4642
- timestamp += stscEntry.samplesPerChunk;
4774
+ timestamp += samplesPerChunk;
4643
4775
  }
4644
4776
  return samples;
4645
4777
  };
@@ -4716,7 +4848,7 @@ var getSamplePositionsFromTrack = ({
4716
4848
  tkhdBox
4717
4849
  });
4718
4850
  return {
4719
- samplePositions: samplePositions.flat(1),
4851
+ samplePositions: samplePositions.map((s) => s.samples).flat(1),
4720
4852
  isComplete
4721
4853
  };
4722
4854
  }
@@ -4950,7 +5082,7 @@ var getDurationFromIsoBaseMedia = (parserState) => {
4950
5082
  const moovBox = getMoovBoxFromState({
4951
5083
  structureState: parserState.structure,
4952
5084
  isoState: parserState.iso,
4953
- mp4HeaderSegment: parserState.mp4HeaderSegment,
5085
+ mp4HeaderSegment: parserState.m3uPlaylistContext?.mp4HeaderSegment ?? null,
4954
5086
  mayUsePrecomputed: true
4955
5087
  });
4956
5088
  if (!moovBox) {
@@ -5056,7 +5188,12 @@ var hasHdr = (state) => {
5056
5188
 
5057
5189
  // src/containers/iso-base-media/get-keyframes.ts
5058
5190
  var getKeyframesFromIsoBaseMedia = (state) => {
5059
- const { videoTracks } = getTracksFromIsoBaseMedia(state, true);
5191
+ const { videoTracks } = getTracksFromIsoBaseMedia({
5192
+ isoState: state.iso,
5193
+ m3uPlaylistContext: state.m3uPlaylistContext,
5194
+ structure: state.structure,
5195
+ mayUsePrecomputed: true
5196
+ });
5060
5197
  const structure = state.structure.getIsoStructure();
5061
5198
  const moofBoxes = getMoofBoxes(structure.boxes);
5062
5199
  const tfraBoxes = getTfraBoxes(structure);
@@ -5210,7 +5347,7 @@ var getMetadataFromIsoBase = (state) => {
5210
5347
  const moov = getMoovBoxFromState({
5211
5348
  structureState: state.structure,
5212
5349
  isoState: state.iso,
5213
- mp4HeaderSegment: state.mp4HeaderSegment,
5350
+ mp4HeaderSegment: state.m3uPlaylistContext?.mp4HeaderSegment ?? null,
5214
5351
  mayUsePrecomputed: true
5215
5352
  });
5216
5353
  if (!moov) {
@@ -5476,6 +5613,75 @@ var getSeekingByteForFlac = ({
5476
5613
  return null;
5477
5614
  };
5478
5615
 
5616
+ // src/containers/iso-base-media/find-keyframe-before-time.ts
5617
+ var findKeyframeBeforeTime = ({
5618
+ samplePositions,
5619
+ time,
5620
+ timescale,
5621
+ mediaSections,
5622
+ logLevel
5623
+ }) => {
5624
+ let videoByte = 0;
5625
+ let videoSample = null;
5626
+ for (const sample of samplePositions) {
5627
+ const ctsInSeconds = sample.cts / timescale;
5628
+ const dtsInSeconds = sample.dts / timescale;
5629
+ if (!sample.isKeyframe) {
5630
+ continue;
5631
+ }
5632
+ if (!(ctsInSeconds <= time || dtsInSeconds <= time)) {
5633
+ continue;
5634
+ }
5635
+ if (videoByte <= sample.offset) {
5636
+ videoByte = sample.offset;
5637
+ videoSample = sample;
5638
+ }
5639
+ }
5640
+ if (!videoSample) {
5641
+ throw new Error("No sample found");
5642
+ }
5643
+ const mediaSection = mediaSections.find((section) => videoSample.offset >= section.start && videoSample.offset < section.start + section.size);
5644
+ if (!mediaSection) {
5645
+ 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);
5646
+ return null;
5647
+ }
5648
+ return videoSample.offset;
5649
+ };
5650
+
5651
+ // src/containers/iso-base-media/find-track-to-seek.ts
5652
+ var findAnyTrackWithSamplePositions = (allTracks, struc) => {
5653
+ for (const track of allTracks) {
5654
+ if (track.type === "video" || track.type === "audio") {
5655
+ const { samplePositions } = getSamplePositionsFromTrack({
5656
+ trakBox: track.trakBox,
5657
+ moofBoxes: getMoofBoxes(struc.boxes),
5658
+ tfraBoxes: getTfraBoxes(struc)
5659
+ });
5660
+ if (samplePositions.length === 0) {
5661
+ continue;
5662
+ }
5663
+ return { track, samplePositions };
5664
+ }
5665
+ }
5666
+ return null;
5667
+ };
5668
+ var findTrackToSeek = (allTracks, structure) => {
5669
+ const firstVideoTrack = allTracks.find((t) => t.type === "video");
5670
+ const struc = structure.getIsoStructure();
5671
+ if (!firstVideoTrack) {
5672
+ return findAnyTrackWithSamplePositions(allTracks, struc);
5673
+ }
5674
+ const { samplePositions } = getSamplePositionsFromTrack({
5675
+ trakBox: firstVideoTrack.trakBox,
5676
+ moofBoxes: getMoofBoxes(struc.boxes),
5677
+ tfraBoxes: getTfraBoxes(struc)
5678
+ });
5679
+ if (samplePositions.length === 0) {
5680
+ return findAnyTrackWithSamplePositions(allTracks, struc);
5681
+ }
5682
+ return { track: firstVideoTrack, samplePositions };
5683
+ };
5684
+
5479
5685
  // src/state/video-section.ts
5480
5686
  var isByteInMediaSection = ({
5481
5687
  position,
@@ -5544,42 +5750,13 @@ var mediaSectionState = () => {
5544
5750
  };
5545
5751
  };
5546
5752
 
5547
- // src/containers/iso-base-media/find-keyframe-before-time.ts
5548
- var findKeyframeBeforeTime = ({
5549
- samplePositions,
5550
- time,
5551
- timescale,
5552
- mediaSections,
5553
- logLevel
5554
- }) => {
5555
- let byte = 0;
5556
- let sam = null;
5557
- for (const sample of samplePositions) {
5558
- const ctsInSeconds = sample.cts / timescale;
5559
- const dtsInSeconds = sample.dts / timescale;
5560
- if ((ctsInSeconds <= time || dtsInSeconds <= time) && byte <= sample.offset && sample.isKeyframe) {
5561
- byte = sample.offset;
5562
- sam = sample;
5563
- }
5564
- }
5565
- if (!sam) {
5566
- throw new Error("No sample found");
5567
- }
5568
- const mediaSection = mediaSections.find((section) => sam.offset >= section.start && sam.offset < section.start + section.size);
5569
- if (!mediaSection) {
5570
- 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);
5571
- return null;
5572
- }
5573
- return sam.offset;
5574
- };
5575
-
5576
5753
  // src/containers/iso-base-media/get-sample-position-bounds.ts
5577
5754
  var getSamplePositionBounds = (samplePositions, timescale) => {
5578
5755
  let min = Infinity;
5579
5756
  let max = -Infinity;
5580
5757
  for (const samplePosition of samplePositions) {
5581
5758
  const timestampMin = Math.min(samplePosition.cts, samplePosition.dts);
5582
- const timestampMax = Math.max(samplePosition.cts, samplePosition.dts);
5759
+ const timestampMax = Math.max(samplePosition.cts, samplePosition.dts) + (samplePosition.duration ?? 0);
5583
5760
  if (timestampMin < min) {
5584
5761
  min = timestampMin;
5585
5762
  }
@@ -5594,10 +5771,10 @@ var getSamplePositionBounds = (samplePositions, timescale) => {
5594
5771
  var findBestSegmentFromTfra = ({
5595
5772
  mfra,
5596
5773
  time,
5597
- firstVideoTrack,
5774
+ firstTrack,
5598
5775
  timescale
5599
5776
  }) => {
5600
- const tfra = mfra.find((b) => b.type === "tfra-box" && b.trackId === firstVideoTrack.trackId);
5777
+ const tfra = mfra.find((b) => b.type === "tfra-box" && b.trackId === firstTrack.trackId);
5601
5778
  if (!tfra) {
5602
5779
  return null;
5603
5780
  }
@@ -5618,130 +5795,191 @@ var findBestSegmentFromTfra = ({
5618
5795
  };
5619
5796
  };
5620
5797
 
5621
- // src/containers/iso-base-media/get-seeking-byte.ts
5622
- var getSeekingByteFromIsoBaseMedia = async ({
5798
+ // src/containers/iso-base-media/get-seeking-byte-from-fragmented-mp4.ts
5799
+ var getSeekingByteFromFragmentedMp4 = async ({
5623
5800
  info,
5624
5801
  time,
5625
5802
  logLevel,
5626
5803
  currentPosition,
5627
5804
  isoState,
5628
- mp4HeaderSegment,
5629
- structure
5805
+ allTracks,
5806
+ isLastChunkInPlaylist
5630
5807
  }) => {
5631
- const tracks2 = getTracksFromMoovBox(info.moovBox);
5632
- const allTracks = [
5633
- ...tracks2.videoTracks,
5634
- ...tracks2.audioTracks,
5635
- ...tracks2.otherTracks
5636
- ];
5637
- const hasMoov = Boolean(getMoovBoxFromState({
5638
- mp4HeaderSegment,
5639
- structureState: structure,
5640
- isoState,
5641
- mayUsePrecomputed: false
5642
- }));
5643
- if (!hasMoov) {
5644
- Log.trace(logLevel, "No moov box found, must wait");
5645
- return {
5646
- type: "valid-but-must-wait"
5647
- };
5648
- }
5649
5808
  const firstVideoTrack = allTracks.find((t) => t.type === "video");
5650
- if (!firstVideoTrack) {
5651
- throw new Error("No video track found");
5809
+ const firstTrack = firstVideoTrack ?? allTracks.find((t) => t.type === "audio");
5810
+ if (!firstTrack) {
5811
+ throw new Error("no video and no audio tracks");
5652
5812
  }
5653
- const { timescale } = firstVideoTrack;
5654
- if (info.moofBoxes.length > 0) {
5655
- const tkhdBox = getTkhdBox(firstVideoTrack.trakBox);
5656
- if (!tkhdBox) {
5657
- throw new Error("Expected tkhd box in trak box");
5658
- }
5659
- const { samplePositions: samplePositionsArray } = collectSamplePositionsFromMoofBoxes({
5660
- moofBoxes: info.moofBoxes,
5661
- tfraBoxes: info.tfraBoxes,
5662
- tkhdBox
5663
- });
5664
- Log.trace(logLevel, "Fragmented MP4 - Checking if we have seeking info for this time range");
5665
- for (const positions of samplePositionsArray) {
5666
- const { min, max } = getSamplePositionBounds(positions, timescale);
5667
- if (min <= time && time <= max) {
5668
- Log.trace(logLevel, `Fragmented MP4 - Found that we have seeking info for this time range: ${min} <= ${time} <= ${max}`);
5669
- const kf = findKeyframeBeforeTime({
5670
- samplePositions: positions,
5671
- time,
5672
- timescale,
5673
- logLevel,
5674
- mediaSections: info.mediaSections
5675
- });
5676
- if (kf) {
5677
- return {
5678
- type: "do-seek",
5679
- byte: kf
5680
- };
5681
- }
5682
- }
5683
- }
5684
- const atom = await (info.mfraAlreadyLoaded ? Promise.resolve(info.mfraAlreadyLoaded) : isoState.mfra.triggerLoad());
5685
- if (atom) {
5686
- const moofOffset = findBestSegmentFromTfra({
5687
- mfra: atom,
5813
+ const tkhdBox = getTkhdBox(firstTrack.trakBox);
5814
+ if (!tkhdBox) {
5815
+ throw new Error("Expected tkhd box in trak box");
5816
+ }
5817
+ const { samplePositions: samplePositionsArray } = collectSamplePositionsFromMoofBoxes({
5818
+ moofBoxes: info.moofBoxes,
5819
+ tfraBoxes: info.tfraBoxes,
5820
+ tkhdBox
5821
+ });
5822
+ Log.trace(logLevel, "Fragmented MP4 - Checking if we have seeking info for this time range");
5823
+ for (const positions of samplePositionsArray) {
5824
+ const { min, max } = getSamplePositionBounds(positions.samples, firstTrack.timescale);
5825
+ if (min <= time && (positions.isLastFragment || isLastChunkInPlaylist || time <= max)) {
5826
+ Log.trace(logLevel, `Fragmented MP4 - Found that we have seeking info for this time range: ${min} <= ${time} <= ${max}`);
5827
+ const kf = findKeyframeBeforeTime({
5828
+ samplePositions: positions.samples,
5688
5829
  time,
5689
- firstVideoTrack,
5690
- timescale
5830
+ timescale: firstTrack.timescale,
5831
+ logLevel,
5832
+ mediaSections: info.mediaSections
5691
5833
  });
5692
- if (moofOffset !== null && !(moofOffset.start <= currentPosition && currentPosition < moofOffset.end)) {
5693
- Log.verbose(logLevel, `Fragmented MP4 - Found based on mfra information that we should seek to: ${moofOffset.start} ${moofOffset.end}`);
5834
+ if (kf) {
5694
5835
  return {
5695
- type: "intermediary-seek",
5696
- byte: moofOffset.start
5836
+ type: "do-seek",
5837
+ byte: kf
5697
5838
  };
5698
5839
  }
5699
5840
  }
5700
- Log.trace(logLevel, "Fragmented MP4 - No seeking info found for this time range.");
5701
- if (isByteInMediaSection({
5702
- position: currentPosition,
5703
- mediaSections: info.mediaSections
5704
- }) !== "in-section") {
5705
- return {
5706
- type: "valid-but-must-wait"
5707
- };
5708
- }
5709
- Log.trace(logLevel, "Fragmented MP4 - Inside the wrong video section, skipping to the end of the section");
5710
- const mediaSection = getCurrentMediaSection({
5711
- offset: currentPosition,
5712
- mediaSections: info.mediaSections
5841
+ }
5842
+ const atom = await (info.mfraAlreadyLoaded ? Promise.resolve(info.mfraAlreadyLoaded) : isoState.mfra.triggerLoad());
5843
+ if (atom) {
5844
+ const moofOffset = findBestSegmentFromTfra({
5845
+ mfra: atom,
5846
+ time,
5847
+ firstTrack,
5848
+ timescale: firstTrack.timescale
5713
5849
  });
5714
- if (!mediaSection) {
5715
- throw new Error("No video section defined");
5850
+ if (moofOffset !== null && !(moofOffset.start <= currentPosition && currentPosition < moofOffset.end)) {
5851
+ Log.verbose(logLevel, `Fragmented MP4 - Found based on mfra information that we should seek to: ${moofOffset.start} ${moofOffset.end}`);
5852
+ return {
5853
+ type: "intermediary-seek",
5854
+ byte: moofOffset.start
5855
+ };
5716
5856
  }
5857
+ }
5858
+ Log.trace(logLevel, "Fragmented MP4 - No seeking info found for this time range.");
5859
+ if (isByteInMediaSection({
5860
+ position: currentPosition,
5861
+ mediaSections: info.mediaSections
5862
+ }) !== "in-section") {
5717
5863
  return {
5718
- type: "intermediary-seek",
5719
- byte: mediaSection.start + mediaSection.size
5864
+ type: "valid-but-must-wait"
5720
5865
  };
5721
5866
  }
5722
- const { samplePositions, isComplete } = getSamplePositionsFromTrack({
5723
- trakBox: firstVideoTrack.trakBox,
5724
- moofBoxes: info.moofBoxes,
5725
- tfraBoxes: info.tfraBoxes
5867
+ Log.trace(logLevel, "Fragmented MP4 - Inside the wrong video section, skipping to the end of the section");
5868
+ const mediaSection = getCurrentMediaSection({
5869
+ offset: currentPosition,
5870
+ mediaSections: info.mediaSections
5871
+ });
5872
+ if (!mediaSection) {
5873
+ throw new Error("No video section defined");
5874
+ }
5875
+ return {
5876
+ type: "intermediary-seek",
5877
+ byte: mediaSection.start + mediaSection.size
5878
+ };
5879
+ };
5880
+
5881
+ // src/containers/iso-base-media/get-seeking-byte.ts
5882
+ var getSeekingByteFromIsoBaseMedia = ({
5883
+ info,
5884
+ time,
5885
+ logLevel,
5886
+ currentPosition,
5887
+ isoState,
5888
+ m3uPlaylistContext,
5889
+ structure
5890
+ }) => {
5891
+ const tracks2 = getTracksFromIsoBaseMedia({
5892
+ isoState,
5893
+ m3uPlaylistContext,
5894
+ structure,
5895
+ mayUsePrecomputed: false
5726
5896
  });
5727
- if (!isComplete) {
5728
- throw new Error("Incomplete sample positions");
5897
+ const allTracks = [
5898
+ ...tracks2.videoTracks,
5899
+ ...tracks2.audioTracks,
5900
+ ...tracks2.otherTracks
5901
+ ];
5902
+ const hasMoov = Boolean(getMoovBoxFromState({
5903
+ structureState: structure,
5904
+ isoState,
5905
+ mayUsePrecomputed: false,
5906
+ mp4HeaderSegment: m3uPlaylistContext?.mp4HeaderSegment ?? null
5907
+ }));
5908
+ if (!hasMoov) {
5909
+ Log.trace(logLevel, "No moov box found, must wait");
5910
+ return Promise.resolve({
5911
+ type: "valid-but-must-wait"
5912
+ });
5913
+ }
5914
+ if (info.moofBoxes.length > 0) {
5915
+ return getSeekingByteFromFragmentedMp4({
5916
+ info,
5917
+ time,
5918
+ logLevel,
5919
+ currentPosition,
5920
+ isoState,
5921
+ allTracks,
5922
+ isLastChunkInPlaylist: m3uPlaylistContext?.isLastChunkInPlaylist ?? false
5923
+ });
5729
5924
  }
5925
+ const trackWithSamplePositions = findTrackToSeek(allTracks, structure);
5926
+ if (!trackWithSamplePositions) {
5927
+ return Promise.resolve({
5928
+ type: "valid-but-must-wait"
5929
+ });
5930
+ }
5931
+ const { track, samplePositions } = trackWithSamplePositions;
5730
5932
  const keyframe = findKeyframeBeforeTime({
5731
5933
  samplePositions,
5732
5934
  time,
5733
- timescale,
5935
+ timescale: track.timescale,
5734
5936
  logLevel,
5735
5937
  mediaSections: info.mediaSections
5736
5938
  });
5737
5939
  if (keyframe) {
5738
- return {
5940
+ return Promise.resolve({
5739
5941
  type: "do-seek",
5740
5942
  byte: keyframe
5741
- };
5943
+ });
5742
5944
  }
5743
- return {
5945
+ return Promise.resolve({
5744
5946
  type: "invalid"
5947
+ });
5948
+ };
5949
+
5950
+ // src/containers/m3u/get-seeking-byte.ts
5951
+ var clearM3uStateInPrepareForSeek = ({
5952
+ m3uState,
5953
+ logLevel
5954
+ }) => {
5955
+ const selectedPlaylists = m3uState.getSelectedPlaylists();
5956
+ for (const playlistUrl of selectedPlaylists) {
5957
+ const streamRun = m3uState.getM3uStreamRun(playlistUrl);
5958
+ if (streamRun) {
5959
+ streamRun.abort();
5960
+ }
5961
+ Log.trace(logLevel, "Clearing M3U stream run for", playlistUrl);
5962
+ m3uState.setM3uStreamRun(playlistUrl, null);
5963
+ }
5964
+ m3uState.clearAllChunksProcessed();
5965
+ m3uState.sampleSorter.clearSamples();
5966
+ };
5967
+ var getSeekingByteForM3u8 = ({
5968
+ time,
5969
+ currentPosition,
5970
+ m3uState,
5971
+ logLevel
5972
+ }) => {
5973
+ clearM3uStateInPrepareForSeek({ m3uState, logLevel });
5974
+ const selectedPlaylists = m3uState.getSelectedPlaylists();
5975
+ for (const playlistUrl of selectedPlaylists) {
5976
+ m3uState.setSeekToSecondsToProcess(playlistUrl, {
5977
+ targetTime: time
5978
+ });
5979
+ }
5980
+ return {
5981
+ type: "do-seek",
5982
+ byte: currentPosition
5745
5983
  };
5746
5984
  };
5747
5985
 
@@ -6678,9 +6916,10 @@ var getSeekingByte = ({
6678
6916
  transportStream,
6679
6917
  webmState,
6680
6918
  mediaSection,
6681
- mp4HeaderSegment,
6919
+ m3uPlaylistContext,
6682
6920
  structure,
6683
- riffState
6921
+ riffState,
6922
+ m3uState
6684
6923
  }) => {
6685
6924
  if (info.type === "iso-base-media-seeking-hints") {
6686
6925
  return getSeekingByteFromIsoBaseMedia({
@@ -6689,8 +6928,8 @@ var getSeekingByte = ({
6689
6928
  logLevel,
6690
6929
  currentPosition,
6691
6930
  isoState,
6692
- mp4HeaderSegment,
6693
- structure
6931
+ structure,
6932
+ m3uPlaylistContext
6694
6933
  });
6695
6934
  }
6696
6935
  if (info.type === "wav-seeking-hints") {
@@ -6755,6 +6994,14 @@ var getSeekingByte = ({
6755
6994
  seekingHints: info
6756
6995
  }));
6757
6996
  }
6997
+ if (info.type === "m3u8-seeking-hints") {
6998
+ return Promise.resolve(getSeekingByteForM3u8({
6999
+ time,
7000
+ currentPosition,
7001
+ m3uState,
7002
+ logLevel
7003
+ }));
7004
+ }
6758
7005
  throw new Error(`Unknown seeking info type: ${info}`);
6759
7006
  };
6760
7007
 
@@ -6843,6 +7090,13 @@ var getSeekingHintsFromMp4 = ({
6843
7090
  };
6844
7091
  var setSeekingHintsForMp4 = ({}) => {};
6845
7092
 
7093
+ // src/containers/m3u/seeking-hints.ts
7094
+ var getSeekingHintsForM3u = () => {
7095
+ return {
7096
+ type: "m3u8-seeking-hints"
7097
+ };
7098
+ };
7099
+
6846
7100
  // src/containers/mp3/seeking-hints.ts
6847
7101
  var getSeekingHintsForMp3 = ({
6848
7102
  mp3State,
@@ -6989,7 +7243,7 @@ var setSeekingHintsForWebm = ({
6989
7243
  // src/get-seeking-hints.ts
6990
7244
  var getSeekingHints = ({
6991
7245
  structureState,
6992
- mp4HeaderSegment,
7246
+ m3uPlaylistContext,
6993
7247
  mediaSectionState: mediaSectionState2,
6994
7248
  isoState,
6995
7249
  transportStream,
@@ -7011,7 +7265,7 @@ var getSeekingHints = ({
7011
7265
  return getSeekingHintsFromMp4({
7012
7266
  structureState,
7013
7267
  isoState,
7014
- mp4HeaderSegment,
7268
+ mp4HeaderSegment: m3uPlaylistContext?.mp4HeaderSegment ?? null,
7015
7269
  mediaSectionState: mediaSectionState2
7016
7270
  });
7017
7271
  }
@@ -7054,7 +7308,10 @@ var getSeekingHints = ({
7054
7308
  samplesObserved
7055
7309
  });
7056
7310
  }
7057
- throw new Error(`Seeking is not supported for this format: ${structure.type}`);
7311
+ if (structure.type === "m3u") {
7312
+ return getSeekingHintsForM3u();
7313
+ }
7314
+ throw new Error(`Seeking is not supported for this format: ${structure}`);
7058
7315
  };
7059
7316
 
7060
7317
  // src/seek-backwards.ts
@@ -7065,10 +7322,12 @@ var seekBackwards = async ({
7065
7322
  src,
7066
7323
  controller,
7067
7324
  logLevel,
7068
- currentReader
7325
+ currentReader,
7326
+ prefetchCache
7069
7327
  }) => {
7070
7328
  const howManyBytesWeCanGoBack = iterator.counter.getDiscardedOffset();
7071
7329
  if (iterator.counter.getOffset() - howManyBytesWeCanGoBack <= seekTo) {
7330
+ Log.verbose(logLevel, `Seeking back to ${seekTo}`);
7072
7331
  iterator.skipTo(seekTo);
7073
7332
  return;
7074
7333
  }
@@ -7077,7 +7336,9 @@ var seekBackwards = async ({
7077
7336
  const { reader: newReader } = await readerInterface.read({
7078
7337
  src,
7079
7338
  range: seekTo,
7080
- controller
7339
+ controller,
7340
+ logLevel,
7341
+ prefetchCache
7081
7342
  });
7082
7343
  iterator.replaceData(new Uint8Array([]), seekTo);
7083
7344
  Log.verbose(logLevel, `Re-reading took ${Date.now() - time}ms. New position: ${iterator.counter.getOffset()}`);
@@ -7147,7 +7408,8 @@ var seekForward = async ({
7147
7408
  readerInterface,
7148
7409
  src,
7149
7410
  controller,
7150
- discardReadBytes
7411
+ discardReadBytes,
7412
+ prefetchCache
7151
7413
  }) => {
7152
7414
  if (userInitiated) {
7153
7415
  disallowForwardSeekIfSamplesAreNeeded({
@@ -7168,7 +7430,9 @@ var seekForward = async ({
7168
7430
  const { reader: newReader } = await readerInterface.read({
7169
7431
  src,
7170
7432
  range: seekTo,
7171
- controller
7433
+ controller,
7434
+ logLevel,
7435
+ prefetchCache
7172
7436
  });
7173
7437
  iterator.skipTo(seekTo);
7174
7438
  await discardReadBytes(true);
@@ -7191,7 +7455,8 @@ var performSeek = async ({
7191
7455
  readerInterface,
7192
7456
  src,
7193
7457
  discardReadBytes,
7194
- fields
7458
+ fields,
7459
+ prefetchCache
7195
7460
  }) => {
7196
7461
  const byteInMediaSection = isByteInMediaSection({
7197
7462
  position: seekTo,
@@ -7240,7 +7505,8 @@ var performSeek = async ({
7240
7505
  readerInterface,
7241
7506
  src,
7242
7507
  controller,
7243
- discardReadBytes
7508
+ discardReadBytes,
7509
+ prefetchCache
7244
7510
  });
7245
7511
  } else {
7246
7512
  await seekBackwards({
@@ -7250,7 +7516,8 @@ var performSeek = async ({
7250
7516
  logLevel,
7251
7517
  currentReader,
7252
7518
  readerInterface,
7253
- src
7519
+ src,
7520
+ prefetchCache
7254
7521
  });
7255
7522
  }
7256
7523
  await controller._internals.checkForAbortAndPause();
@@ -7263,7 +7530,7 @@ var turnSeekIntoByte = async ({
7263
7530
  logLevel,
7264
7531
  iterator,
7265
7532
  structureState,
7266
- mp4HeaderSegment,
7533
+ m3uPlaylistContext,
7267
7534
  isoState,
7268
7535
  transportStream,
7269
7536
  tracksState,
@@ -7274,7 +7541,8 @@ var turnSeekIntoByte = async ({
7274
7541
  riffState,
7275
7542
  mp3State,
7276
7543
  contentLength,
7277
- aacState
7544
+ aacState,
7545
+ m3uState
7278
7546
  }) => {
7279
7547
  const mediaSections = mediaSectionState2.getMediaSections();
7280
7548
  if (mediaSections.length === 0) {
@@ -7291,7 +7559,6 @@ var turnSeekIntoByte = async ({
7291
7559
  riffState,
7292
7560
  samplesObserved,
7293
7561
  structureState,
7294
- mp4HeaderSegment,
7295
7562
  mediaSectionState: mediaSectionState2,
7296
7563
  isoState,
7297
7564
  transportStream,
@@ -7301,7 +7568,8 @@ var turnSeekIntoByte = async ({
7301
7568
  flacState,
7302
7569
  mp3State,
7303
7570
  contentLength,
7304
- aacState
7571
+ aacState,
7572
+ m3uPlaylistContext
7305
7573
  });
7306
7574
  if (!seekingHints) {
7307
7575
  Log.trace(logLevel, "No seeking info, cannot seek yet");
@@ -7318,18 +7586,13 @@ var turnSeekIntoByte = async ({
7318
7586
  transportStream,
7319
7587
  webmState,
7320
7588
  mediaSection: mediaSectionState2,
7321
- mp4HeaderSegment,
7589
+ m3uPlaylistContext,
7322
7590
  structure: structureState,
7323
- riffState
7591
+ riffState,
7592
+ m3uState
7324
7593
  });
7325
7594
  return seekingByte;
7326
7595
  }
7327
- if (seek2.type === "byte") {
7328
- return {
7329
- type: "do-seek",
7330
- byte: seek2.byte
7331
- };
7332
- }
7333
7596
  throw new Error(`Cannot process seek request for ${seek2}: ${JSON.stringify(seek2)}`);
7334
7597
  };
7335
7598
  var getWorkOnSeekRequestOptions = (state) => {
@@ -7343,7 +7606,7 @@ var getWorkOnSeekRequestOptions = (state) => {
7343
7606
  contentLength: state.contentLength,
7344
7607
  readerInterface: state.readerInterface,
7345
7608
  mediaSection: state.mediaSection,
7346
- mp4HeaderSegment: state.mp4HeaderSegment,
7609
+ m3uPlaylistContext: state.m3uPlaylistContext,
7347
7610
  mode: state.mode,
7348
7611
  seekInfiniteLoop: state.seekInfiniteLoop,
7349
7612
  currentReader: state.currentReader,
@@ -7357,7 +7620,9 @@ var getWorkOnSeekRequestOptions = (state) => {
7357
7620
  samplesObserved: state.samplesObserved,
7358
7621
  riffState: state.riff,
7359
7622
  mp3State: state.mp3,
7360
- aacState: state.aac
7623
+ aacState: state.aac,
7624
+ m3uState: state.m3u,
7625
+ prefetchCache: state.prefetchCache
7361
7626
  };
7362
7627
  };
7363
7628
  var workOnSeekRequest = async (options) => {
@@ -7365,7 +7630,7 @@ var workOnSeekRequest = async (options) => {
7365
7630
  logLevel,
7366
7631
  controller,
7367
7632
  mediaSection,
7368
- mp4HeaderSegment,
7633
+ m3uPlaylistContext,
7369
7634
  isoState,
7370
7635
  iterator,
7371
7636
  structureState,
@@ -7385,20 +7650,22 @@ var workOnSeekRequest = async (options) => {
7385
7650
  samplesObserved,
7386
7651
  riffState,
7387
7652
  mp3State,
7388
- aacState
7653
+ aacState,
7654
+ prefetchCache,
7655
+ m3uState
7389
7656
  } = options;
7390
7657
  const seek2 = controller._internals.seekSignal.getSeek();
7391
7658
  if (!seek2) {
7392
7659
  return;
7393
7660
  }
7394
- Log.trace(logLevel, `Has seek request: ${JSON.stringify(seek2)}`);
7661
+ Log.trace(logLevel, `Has seek request for ${src}: ${JSON.stringify(seek2)}`);
7395
7662
  const resolution = await turnSeekIntoByte({
7396
7663
  seek: seek2,
7397
7664
  mediaSectionState: mediaSection,
7398
7665
  logLevel,
7399
7666
  iterator,
7400
7667
  structureState,
7401
- mp4HeaderSegment,
7668
+ m3uPlaylistContext,
7402
7669
  isoState,
7403
7670
  transportStream,
7404
7671
  tracksState,
@@ -7409,7 +7676,8 @@ var workOnSeekRequest = async (options) => {
7409
7676
  riffState,
7410
7677
  mp3State,
7411
7678
  contentLength,
7412
- aacState
7679
+ aacState,
7680
+ m3uState
7413
7681
  });
7414
7682
  Log.trace(logLevel, `Seek action: ${JSON.stringify(resolution)}`);
7415
7683
  if (resolution.type === "intermediary-seek") {
@@ -7427,7 +7695,8 @@ var workOnSeekRequest = async (options) => {
7427
7695
  readerInterface,
7428
7696
  src,
7429
7697
  discardReadBytes,
7430
- fields
7698
+ fields,
7699
+ prefetchCache
7431
7700
  });
7432
7701
  return;
7433
7702
  }
@@ -7446,7 +7715,8 @@ var workOnSeekRequest = async (options) => {
7446
7715
  readerInterface,
7447
7716
  src,
7448
7717
  discardReadBytes,
7449
- fields
7718
+ fields,
7719
+ prefetchCache
7450
7720
  });
7451
7721
  const { hasChanged } = controller._internals.seekSignal.clearSeekIfStillSame(seek2);
7452
7722
  if (hasChanged) {
@@ -8072,6 +8342,10 @@ var makeSkip = (skipTo) => ({
8072
8342
  action: "skip",
8073
8343
  skipTo
8074
8344
  });
8345
+ var makeFetchMoreData = (bytesNeeded) => ({
8346
+ action: "fetch-more-data",
8347
+ bytesNeeded
8348
+ });
8075
8349
 
8076
8350
  // src/containers/flac/get-block-size.ts
8077
8351
  var getBlockSize = (iterator) => {
@@ -8526,11 +8800,12 @@ var calculateFlatSamples = (state) => {
8526
8800
  samplePosition
8527
8801
  };
8528
8802
  });
8529
- }).flat(1);
8803
+ });
8530
8804
  return flatSamples;
8531
8805
  };
8532
8806
  var cachedSamplePositionsState = () => {
8533
8807
  const cachedForMdatStart = {};
8808
+ const jumpMarksForMdatStart = {};
8534
8809
  return {
8535
8810
  getSamples: (mdatStart) => {
8536
8811
  if (cachedForMdatStart[mdatStart]) {
@@ -8540,6 +8815,12 @@ var cachedSamplePositionsState = () => {
8540
8815
  },
8541
8816
  setSamples: (mdatStart, samples) => {
8542
8817
  cachedForMdatStart[mdatStart] = samples;
8818
+ },
8819
+ setJumpMarks: (mdatStart, marks) => {
8820
+ jumpMarksForMdatStart[mdatStart] = marks;
8821
+ },
8822
+ getJumpMarks: (mdatStart) => {
8823
+ return jumpMarksForMdatStart[mdatStart];
8543
8824
  }
8544
8825
  };
8545
8826
  };
@@ -8569,17 +8850,21 @@ var makeCanSkipTracksState = ({
8569
8850
  hasVideoTrackHandlers,
8570
8851
  structure
8571
8852
  }) => {
8853
+ const doFieldsNeedTracks = () => {
8854
+ const keys = Object.keys(fields ?? {});
8855
+ const selectedKeys = keys.filter((k) => fields[k]);
8856
+ return selectedKeys.some((k) => needsTracksForField({
8857
+ field: k,
8858
+ structure: structure.getStructureOrNull()
8859
+ }));
8860
+ };
8572
8861
  return {
8862
+ doFieldsNeedTracks,
8573
8863
  canSkipTracks: () => {
8574
8864
  if (hasAudioTrackHandlers || hasVideoTrackHandlers) {
8575
8865
  return false;
8576
8866
  }
8577
- const keys = Object.keys(fields ?? {});
8578
- const selectedKeys = keys.filter((k) => fields[k]);
8579
- return !selectedKeys.some((k) => needsTracksForField({
8580
- field: k,
8581
- structure: structure.getStructureOrNull()
8582
- }));
8867
+ return !doFieldsNeedTracks();
8583
8868
  }
8584
8869
  };
8585
8870
  };
@@ -8870,10 +9155,10 @@ var getIsoBaseMediaChildren = async ({
8870
9155
  onlyIfMdatAtomExpected: null,
8871
9156
  contentLength
8872
9157
  });
8873
- if (!parsed) {
9158
+ if (parsed.type !== "box") {
8874
9159
  throw new Error("Expected box");
8875
9160
  }
8876
- boxes.push(parsed);
9161
+ boxes.push(parsed.box);
8877
9162
  }
8878
9163
  if (iterator.counter.getOffset() > size + initial) {
8879
9164
  throw new Error(`read too many bytes - size: ${size}, read: ${iterator.counter.getOffset() - initial}. initial offset: ${initial}`);
@@ -9573,7 +9858,7 @@ var parseStsc = ({
9573
9858
  }
9574
9859
  const flags = iterator.getSlice(3);
9575
9860
  const entryCount = iterator.getUint32();
9576
- const entries = [];
9861
+ const entries = new Map;
9577
9862
  for (let i = 0;i < entryCount; i++) {
9578
9863
  const firstChunk = iterator.getUint32();
9579
9864
  const samplesPerChunk = iterator.getUint32();
@@ -9581,10 +9866,7 @@ var parseStsc = ({
9581
9866
  if (sampleDescriptionIndex !== 1) {
9582
9867
  throw new Error(`Expected sampleDescriptionIndex to be 1, but got ${sampleDescriptionIndex}`);
9583
9868
  }
9584
- entries.push({
9585
- firstChunk,
9586
- samplesPerChunk
9587
- });
9869
+ entries.set(firstChunk, samplesPerChunk);
9588
9870
  }
9589
9871
  return {
9590
9872
  type: "stsc-box",
@@ -9655,7 +9937,8 @@ var audioTags = [
9655
9937
  1836253269,
9656
9938
  ".mp3",
9657
9939
  "mp4a",
9658
- "ac-3"
9940
+ "ac-3",
9941
+ "Opus"
9659
9942
  ];
9660
9943
  var processIsoFormatBox = async ({
9661
9944
  iterator,
@@ -9928,9 +10211,9 @@ var parseStss = ({
9928
10211
  }
9929
10212
  const flags = iterator.getSlice(3);
9930
10213
  const sampleCount = iterator.getUint32();
9931
- const sampleNumber = [];
10214
+ const sampleNumber = new Set;
9932
10215
  for (let i = 0;i < sampleCount; i++) {
9933
- sampleNumber.push(iterator.getUint32());
10216
+ sampleNumber.add(iterator.getUint32());
9934
10217
  }
9935
10218
  const bytesRemainingInBox = boxSize - (iterator.counter.getOffset() - offset);
9936
10219
  if (bytesRemainingInBox > 0) {
@@ -10246,8 +10529,11 @@ var processBox = async ({
10246
10529
  const boxSizeRaw = iterator.getFourByteNumber();
10247
10530
  if (boxSizeRaw === 0) {
10248
10531
  return {
10249
- type: "void-box",
10250
- boxSize: 0
10532
+ type: "box",
10533
+ box: {
10534
+ type: "void-box",
10535
+ boxSize: 0
10536
+ }
10251
10537
  };
10252
10538
  }
10253
10539
  if (boxSizeRaw === 1 && iterator.bytesRemaining() < 12 || iterator.bytesRemaining() < 4) {
@@ -10261,120 +10547,177 @@ var processBox = async ({
10261
10547
  const headerLength = iterator.counter.getOffset() - startOff;
10262
10548
  if (boxType === "mdat") {
10263
10549
  if (!onlyIfMdatAtomExpected) {
10264
- return null;
10550
+ return { type: "nothing" };
10265
10551
  }
10266
10552
  const { mediaSectionState: mediaSectionState2 } = onlyIfMdatAtomExpected;
10267
10553
  mediaSectionState2.addMediaSection({
10268
10554
  size: boxSize - headerLength,
10269
10555
  start: iterator.counter.getOffset()
10270
10556
  });
10271
- return null;
10557
+ return { type: "nothing" };
10272
10558
  }
10273
10559
  if (bytesRemaining < boxSize) {
10274
10560
  returnToCheckpoint();
10275
- return null;
10561
+ return {
10562
+ type: "fetch-more-data",
10563
+ bytesNeeded: makeFetchMoreData(boxSize - bytesRemaining)
10564
+ };
10276
10565
  }
10277
10566
  if (boxType === "ftyp") {
10278
- return parseFtyp({ iterator, size: boxSize, offset: fileOffset });
10567
+ return {
10568
+ type: "box",
10569
+ box: await parseFtyp({ iterator, size: boxSize, offset: fileOffset })
10570
+ };
10279
10571
  }
10280
10572
  if (boxType === "colr") {
10281
- return parseColorParameterBox({
10282
- iterator,
10283
- size: boxSize
10284
- });
10573
+ return {
10574
+ type: "box",
10575
+ box: await parseColorParameterBox({
10576
+ iterator,
10577
+ size: boxSize
10578
+ })
10579
+ };
10285
10580
  }
10286
10581
  if (boxType === "mvhd") {
10287
- return parseMvhd({ iterator, offset: fileOffset, size: boxSize });
10582
+ return {
10583
+ type: "box",
10584
+ box: await parseMvhd({ iterator, offset: fileOffset, size: boxSize })
10585
+ };
10288
10586
  }
10289
10587
  if (boxType === "tkhd") {
10290
- return parseTkhd({ iterator, offset: fileOffset, size: boxSize });
10588
+ return {
10589
+ type: "box",
10590
+ box: await parseTkhd({ iterator, offset: fileOffset, size: boxSize })
10591
+ };
10291
10592
  }
10292
10593
  if (boxType === "trun") {
10293
- return parseTrun({ iterator, offset: fileOffset, size: boxSize });
10594
+ return {
10595
+ type: "box",
10596
+ box: await parseTrun({ iterator, offset: fileOffset, size: boxSize })
10597
+ };
10294
10598
  }
10295
10599
  if (boxType === "tfdt") {
10296
- return parseTfdt({ iterator, size: boxSize, offset: fileOffset });
10600
+ return {
10601
+ type: "box",
10602
+ box: await parseTfdt({ iterator, size: boxSize, offset: fileOffset })
10603
+ };
10297
10604
  }
10298
10605
  if (boxType === "stsd") {
10299
- return parseStsd({
10300
- offset: fileOffset,
10301
- size: boxSize,
10302
- iterator,
10303
- logLevel,
10304
- contentLength
10305
- });
10606
+ return {
10607
+ type: "box",
10608
+ box: await parseStsd({
10609
+ offset: fileOffset,
10610
+ size: boxSize,
10611
+ iterator,
10612
+ logLevel,
10613
+ contentLength
10614
+ })
10615
+ };
10306
10616
  }
10307
10617
  if (boxType === "stsz") {
10308
- return parseStsz({
10309
- iterator,
10310
- offset: fileOffset,
10311
- size: boxSize
10312
- });
10618
+ return {
10619
+ type: "box",
10620
+ box: await parseStsz({
10621
+ iterator,
10622
+ offset: fileOffset,
10623
+ size: boxSize
10624
+ })
10625
+ };
10313
10626
  }
10314
10627
  if (boxType === "stco" || boxType === "co64") {
10315
- return parseStco({
10316
- iterator,
10317
- offset: fileOffset,
10318
- size: boxSize,
10319
- mode64Bit: boxType === "co64"
10320
- });
10628
+ return {
10629
+ type: "box",
10630
+ box: await parseStco({
10631
+ iterator,
10632
+ offset: fileOffset,
10633
+ size: boxSize,
10634
+ mode64Bit: boxType === "co64"
10635
+ })
10636
+ };
10321
10637
  }
10322
10638
  if (boxType === "pasp") {
10323
- return parsePasp({
10324
- iterator,
10325
- offset: fileOffset,
10326
- size: boxSize
10327
- });
10639
+ return {
10640
+ type: "box",
10641
+ box: await parsePasp({
10642
+ iterator,
10643
+ offset: fileOffset,
10644
+ size: boxSize
10645
+ })
10646
+ };
10328
10647
  }
10329
10648
  if (boxType === "stss") {
10330
- return parseStss({
10331
- iterator,
10332
- offset: fileOffset,
10333
- boxSize
10334
- });
10649
+ return {
10650
+ type: "box",
10651
+ box: await parseStss({
10652
+ iterator,
10653
+ offset: fileOffset,
10654
+ boxSize
10655
+ })
10656
+ };
10335
10657
  }
10336
10658
  if (boxType === "ctts") {
10337
- return parseCtts({
10338
- iterator,
10339
- offset: fileOffset,
10340
- size: boxSize
10341
- });
10659
+ return {
10660
+ type: "box",
10661
+ box: await parseCtts({
10662
+ iterator,
10663
+ offset: fileOffset,
10664
+ size: boxSize
10665
+ })
10666
+ };
10342
10667
  }
10343
10668
  if (boxType === "stsc") {
10344
- return parseStsc({
10345
- iterator,
10346
- offset: fileOffset,
10347
- size: boxSize
10348
- });
10669
+ return {
10670
+ type: "box",
10671
+ box: await parseStsc({
10672
+ iterator,
10673
+ offset: fileOffset,
10674
+ size: boxSize
10675
+ })
10676
+ };
10349
10677
  }
10350
10678
  if (boxType === "mebx") {
10351
- return parseMebx({
10352
- offset: fileOffset,
10353
- size: boxSize,
10354
- iterator,
10355
- logLevel,
10356
- contentLength
10357
- });
10679
+ return {
10680
+ type: "box",
10681
+ box: await parseMebx({
10682
+ offset: fileOffset,
10683
+ size: boxSize,
10684
+ iterator,
10685
+ logLevel,
10686
+ contentLength
10687
+ })
10688
+ };
10358
10689
  }
10359
10690
  if (boxType === "hdlr") {
10360
- return parseHdlr({ iterator, size: boxSize, offset: fileOffset });
10691
+ return {
10692
+ type: "box",
10693
+ box: await parseHdlr({ iterator, size: boxSize, offset: fileOffset })
10694
+ };
10361
10695
  }
10362
10696
  if (boxType === "keys") {
10363
- return parseKeys({ iterator, size: boxSize, offset: fileOffset });
10697
+ return {
10698
+ type: "box",
10699
+ box: await parseKeys({ iterator, size: boxSize, offset: fileOffset })
10700
+ };
10364
10701
  }
10365
10702
  if (boxType === "ilst") {
10366
- return parseIlstBox({
10367
- iterator,
10368
- offset: fileOffset,
10369
- size: boxSize
10370
- });
10703
+ return {
10704
+ type: "box",
10705
+ box: await parseIlstBox({
10706
+ iterator,
10707
+ offset: fileOffset,
10708
+ size: boxSize
10709
+ })
10710
+ };
10371
10711
  }
10372
10712
  if (boxType === "tfra") {
10373
- return parseTfraBox({
10374
- iterator,
10375
- offset: fileOffset,
10376
- size: boxSize
10377
- });
10713
+ return {
10714
+ type: "box",
10715
+ box: await parseTfraBox({
10716
+ iterator,
10717
+ offset: fileOffset,
10718
+ size: boxSize
10719
+ })
10720
+ };
10378
10721
  }
10379
10722
  if (boxType === "moov") {
10380
10723
  if (!onlyIfMoovAtomExpected) {
@@ -10383,12 +10726,12 @@ var processBox = async ({
10383
10726
  const { tracks: tracks2, isoState } = onlyIfMoovAtomExpected;
10384
10727
  if (tracks2.hasAllTracks()) {
10385
10728
  iterator.discard(boxSize - 8);
10386
- return null;
10729
+ return { type: "nothing" };
10387
10730
  }
10388
10731
  if (isoState && isoState.moov.getMoovBoxAndPrecomputed() && !isoState.moov.getMoovBoxAndPrecomputed()?.precomputed) {
10389
10732
  Log.verbose(logLevel, "Moov box already parsed, skipping");
10390
10733
  iterator.discard(boxSize - 8);
10391
- return null;
10734
+ return { type: "nothing" };
10392
10735
  }
10393
10736
  const box = await parseMoov({
10394
10737
  offset: fileOffset,
@@ -10399,7 +10742,7 @@ var processBox = async ({
10399
10742
  contentLength
10400
10743
  });
10401
10744
  tracks2.setIsDone(logLevel);
10402
- return box;
10745
+ return { type: "box", box };
10403
10746
  }
10404
10747
  if (boxType === "trak") {
10405
10748
  if (!onlyIfMoovAtomExpected) {
@@ -10434,54 +10777,75 @@ var processBox = async ({
10434
10777
  onAudioTrack
10435
10778
  });
10436
10779
  }
10437
- return box;
10780
+ return { type: "box", box };
10438
10781
  }
10439
10782
  if (boxType === "stts") {
10440
- return parseStts({
10441
- data: iterator,
10442
- size: boxSize,
10443
- fileOffset
10444
- });
10783
+ return {
10784
+ type: "box",
10785
+ box: await parseStts({
10786
+ data: iterator,
10787
+ size: boxSize,
10788
+ fileOffset
10789
+ })
10790
+ };
10445
10791
  }
10446
10792
  if (boxType === "avcC") {
10447
- return parseAvcc({
10448
- data: iterator,
10449
- size: boxSize
10450
- });
10793
+ return {
10794
+ type: "box",
10795
+ box: await parseAvcc({
10796
+ data: iterator,
10797
+ size: boxSize
10798
+ })
10799
+ };
10451
10800
  }
10452
10801
  if (boxType === "av1C") {
10453
- return parseAv1C({
10454
- data: iterator,
10455
- size: boxSize
10456
- });
10802
+ return {
10803
+ type: "box",
10804
+ box: await parseAv1C({
10805
+ data: iterator,
10806
+ size: boxSize
10807
+ })
10808
+ };
10457
10809
  }
10458
10810
  if (boxType === "hvcC") {
10459
- return parseHvcc({
10460
- data: iterator,
10461
- size: boxSize,
10462
- offset: fileOffset
10463
- });
10811
+ return {
10812
+ type: "box",
10813
+ box: await parseHvcc({
10814
+ data: iterator,
10815
+ size: boxSize,
10816
+ offset: fileOffset
10817
+ })
10818
+ };
10464
10819
  }
10465
10820
  if (boxType === "tfhd") {
10466
- return getTfhd({
10467
- iterator,
10468
- offset: fileOffset,
10469
- size: boxSize
10470
- });
10821
+ return {
10822
+ type: "box",
10823
+ box: await getTfhd({
10824
+ iterator,
10825
+ offset: fileOffset,
10826
+ size: boxSize
10827
+ })
10828
+ };
10471
10829
  }
10472
10830
  if (boxType === "mdhd") {
10473
- return parseMdhd({
10474
- data: iterator,
10475
- size: boxSize,
10476
- fileOffset
10477
- });
10831
+ return {
10832
+ type: "box",
10833
+ box: await parseMdhd({
10834
+ data: iterator,
10835
+ size: boxSize,
10836
+ fileOffset
10837
+ })
10838
+ };
10478
10839
  }
10479
10840
  if (boxType === "esds") {
10480
- return parseEsds({
10481
- data: iterator,
10482
- size: boxSize,
10483
- fileOffset
10484
- });
10841
+ return {
10842
+ type: "box",
10843
+ box: await parseEsds({
10844
+ data: iterator,
10845
+ size: boxSize,
10846
+ fileOffset
10847
+ })
10848
+ };
10485
10849
  }
10486
10850
  if (boxType === "moof") {
10487
10851
  onlyIfMoovAtomExpected?.isoState?.mfra.triggerLoad();
@@ -10495,20 +10859,26 @@ var processBox = async ({
10495
10859
  contentLength
10496
10860
  });
10497
10861
  return {
10498
- type: "regular-box",
10499
- boxType,
10500
- boxSize,
10501
- children,
10502
- offset: fileOffset
10862
+ type: "box",
10863
+ box: {
10864
+ type: "regular-box",
10865
+ boxType,
10866
+ boxSize,
10867
+ children,
10868
+ offset: fileOffset
10869
+ }
10503
10870
  };
10504
10871
  }
10505
10872
  iterator.discard(boxSize - 8);
10506
10873
  return {
10507
- type: "regular-box",
10508
- boxType,
10509
- boxSize,
10510
- children: [],
10511
- offset: fileOffset
10874
+ type: "box",
10875
+ box: {
10876
+ type: "regular-box",
10877
+ boxType,
10878
+ boxSize,
10879
+ children: [],
10880
+ offset: fileOffset
10881
+ }
10512
10882
  };
10513
10883
  };
10514
10884
 
@@ -10517,7 +10887,7 @@ var getMoovAtom = async ({
10517
10887
  endOfMdat,
10518
10888
  state
10519
10889
  }) => {
10520
- const headerSegment = state.mp4HeaderSegment;
10890
+ const headerSegment = state.m3uPlaylistContext?.mp4HeaderSegment;
10521
10891
  if (headerSegment) {
10522
10892
  const segment = getMoovFromFromIsoStructure(headerSegment);
10523
10893
  if (!segment) {
@@ -10530,7 +10900,9 @@ var getMoovAtom = async ({
10530
10900
  const { reader } = await state.readerInterface.read({
10531
10901
  src: state.src,
10532
10902
  range: endOfMdat,
10533
- controller: state.controller
10903
+ controller: state.controller,
10904
+ logLevel: state.logLevel,
10905
+ prefetchCache: state.prefetchCache
10534
10906
  });
10535
10907
  const onAudioTrack = state.onAudioTrack ? async ({ track, container }) => {
10536
10908
  await registerAudioTrack({
@@ -10587,8 +10959,8 @@ var getMoovAtom = async ({
10587
10959
  onlyIfMdatAtomExpected: null,
10588
10960
  contentLength: state.contentLength - endOfMdat
10589
10961
  });
10590
- if (box) {
10591
- boxes.push(box);
10962
+ if (box.type === "box") {
10963
+ boxes.push(box.box);
10592
10964
  }
10593
10965
  if (iterator.counter.getOffset() + endOfMdat > state.contentLength) {
10594
10966
  throw new Error("Read past end of file");
@@ -10605,6 +10977,121 @@ var getMoovAtom = async ({
10605
10977
  return moov;
10606
10978
  };
10607
10979
 
10980
+ // src/containers/iso-base-media/mdat/calculate-jump-marks.ts
10981
+ var MAX_SPREAD_IN_SECONDS = 8;
10982
+ var getKey = (samplePositionTrack) => {
10983
+ return `${samplePositionTrack.track.trackId}-${samplePositionTrack.samplePosition.dts}`;
10984
+ };
10985
+ var findBestJump = ({
10986
+ allSamplesSortedByOffset,
10987
+ visited,
10988
+ progresses
10989
+ }) => {
10990
+ const minProgress = Math.min(...Object.values(progresses));
10991
+ const trackNumberWithLowestProgress = Object.entries(progresses).find(([, progress]) => progress === minProgress)?.[0];
10992
+ const firstSampleAboveMinProgress = allSamplesSortedByOffset.findIndex((sample) => sample.track.trackId === Number(trackNumberWithLowestProgress) && !visited.has(getKey(sample)));
10993
+ return firstSampleAboveMinProgress;
10994
+ };
10995
+ var calculateJumpMarks = (samplePositionTracks, endOfMdat) => {
10996
+ const progresses = {};
10997
+ for (const track of samplePositionTracks) {
10998
+ progresses[track[0].track.trackId] = 0;
10999
+ }
11000
+ const jumpMarks = [];
11001
+ const allSamplesSortedByOffset = samplePositionTracks.flat(1).sort((a, b) => a.samplePosition.offset - b.samplePosition.offset);
11002
+ let indexToVisit = 0;
11003
+ const visited = new Set;
11004
+ let rollOverToProcess = false;
11005
+ const increaseIndex = () => {
11006
+ indexToVisit++;
11007
+ if (indexToVisit >= allSamplesSortedByOffset.length) {
11008
+ rollOverToProcess = true;
11009
+ indexToVisit = 0;
11010
+ }
11011
+ };
11012
+ let lastVisitedSample = null;
11013
+ const addJumpMark = ({
11014
+ firstSampleAboveMinProgress
11015
+ }) => {
11016
+ if (!lastVisitedSample) {
11017
+ throw new Error("no last visited sample");
11018
+ }
11019
+ const jumpMark = {
11020
+ afterSampleWithOffset: lastVisitedSample.samplePosition.offset,
11021
+ jumpToOffset: allSamplesSortedByOffset[firstSampleAboveMinProgress].samplePosition.offset
11022
+ };
11023
+ indexToVisit = firstSampleAboveMinProgress;
11024
+ jumpMarks.push(jumpMark);
11025
+ };
11026
+ const addFinalJumpIfNecessary = () => {
11027
+ if (indexToVisit === allSamplesSortedByOffset.length - 1) {
11028
+ return;
11029
+ }
11030
+ jumpMarks.push({
11031
+ afterSampleWithOffset: allSamplesSortedByOffset[indexToVisit].samplePosition.offset,
11032
+ jumpToOffset: endOfMdat
11033
+ });
11034
+ };
11035
+ const considerJump = () => {
11036
+ const firstSampleAboveMinProgress = findBestJump({
11037
+ allSamplesSortedByOffset,
11038
+ visited,
11039
+ progresses
11040
+ });
11041
+ if (firstSampleAboveMinProgress > -1 && firstSampleAboveMinProgress !== indexToVisit + 1) {
11042
+ addJumpMark({ firstSampleAboveMinProgress });
11043
+ indexToVisit = firstSampleAboveMinProgress;
11044
+ } else {
11045
+ while (true) {
11046
+ increaseIndex();
11047
+ if (!visited.has(getKey(allSamplesSortedByOffset[indexToVisit]))) {
11048
+ break;
11049
+ }
11050
+ }
11051
+ }
11052
+ };
11053
+ while (true) {
11054
+ const currentSamplePosition = allSamplesSortedByOffset[indexToVisit];
11055
+ const sampleKey = getKey(currentSamplePosition);
11056
+ if (visited.has(sampleKey)) {
11057
+ considerJump();
11058
+ continue;
11059
+ }
11060
+ visited.add(sampleKey);
11061
+ if (rollOverToProcess) {
11062
+ if (!lastVisitedSample) {
11063
+ throw new Error("no last visited sample");
11064
+ }
11065
+ jumpMarks.push({
11066
+ afterSampleWithOffset: lastVisitedSample.samplePosition.offset,
11067
+ jumpToOffset: currentSamplePosition.samplePosition.offset
11068
+ });
11069
+ rollOverToProcess = false;
11070
+ }
11071
+ lastVisitedSample = currentSamplePosition;
11072
+ if (visited.size === allSamplesSortedByOffset.length) {
11073
+ addFinalJumpIfNecessary();
11074
+ break;
11075
+ }
11076
+ const timestamp = currentSamplePosition.samplePosition.dts / currentSamplePosition.track.timescale;
11077
+ progresses[currentSamplePosition.track.trackId] = timestamp;
11078
+ const progressValues = Object.values(progresses);
11079
+ const maxProgress = Math.max(...progressValues);
11080
+ const minProgress = Math.min(...progressValues);
11081
+ const spread = maxProgress - minProgress;
11082
+ if (visited.size === allSamplesSortedByOffset.length) {
11083
+ addFinalJumpIfNecessary();
11084
+ break;
11085
+ }
11086
+ if (spread > MAX_SPREAD_IN_SECONDS) {
11087
+ considerJump();
11088
+ } else {
11089
+ increaseIndex();
11090
+ }
11091
+ }
11092
+ return jumpMarks;
11093
+ };
11094
+
10608
11095
  // src/containers/iso-base-media/mdat/postprocess-bytes.ts
10609
11096
  var postprocessBytes = ({
10610
11097
  bytes,
@@ -10654,9 +11141,13 @@ var parseMdatSection = async (state) => {
10654
11141
  return parseMdatSection(state);
10655
11142
  }
10656
11143
  if (!state.iso.flatSamples.getSamples(mediaSection.start)) {
10657
- state.iso.flatSamples.setSamples(mediaSection.start, calculateFlatSamples(state));
11144
+ const flattedSamples = calculateFlatSamples(state);
11145
+ const calcedJumpMarks = calculateJumpMarks(flattedSamples, endOfMdat);
11146
+ state.iso.flatSamples.setJumpMarks(mediaSection.start, calcedJumpMarks);
11147
+ state.iso.flatSamples.setSamples(mediaSection.start, flattedSamples.flat(1));
10658
11148
  }
10659
11149
  const flatSamples = state.iso.flatSamples.getSamples(mediaSection.start);
11150
+ const jumpMarks = state.iso.flatSamples.getJumpMarks(mediaSection.start);
10660
11151
  const { iterator } = state;
10661
11152
  const samplesWithIndex = flatSamples.find((sample) => {
10662
11153
  return sample.samplePosition.offset === iterator.counter.getOffset();
@@ -10669,8 +11160,11 @@ var parseMdatSection = async (state) => {
10669
11160
  }
10670
11161
  return makeSkip(endOfMdat);
10671
11162
  }
11163
+ if (samplesWithIndex.samplePosition.offset + samplesWithIndex.samplePosition.size > state.contentLength) {
11164
+ return makeSkip(endOfMdat);
11165
+ }
10672
11166
  if (iterator.bytesRemaining() < samplesWithIndex.samplePosition.size) {
10673
- return null;
11167
+ return makeFetchMoreData(samplesWithIndex.samplePosition.size - iterator.bytesRemaining());
10674
11168
  }
10675
11169
  const { cts, dts, duration: duration2, isKeyframe, offset, bigEndian, chunkSize } = samplesWithIndex.samplePosition;
10676
11170
  const bytes = postprocessBytes({
@@ -10718,6 +11212,10 @@ var parseMdatSection = async (state) => {
10718
11212
  });
10719
11213
  await state.callbacks.onVideoSample(samplesWithIndex.track.trackId, videoSample);
10720
11214
  }
11215
+ const jump = jumpMarks.find((j) => j.afterSampleWithOffset === offset);
11216
+ if (jump) {
11217
+ return makeSkip(jump.jumpToOffset);
11218
+ }
10721
11219
  return null;
10722
11220
  };
10723
11221
 
@@ -10744,8 +11242,11 @@ var parseIsoBaseMedia = async (state) => {
10744
11242
  },
10745
11243
  contentLength: state.contentLength
10746
11244
  });
10747
- if (result) {
10748
- state.structure.getIsoStructure().boxes.push(result);
11245
+ if (result.type === "fetch-more-data") {
11246
+ return result.bytesNeeded;
11247
+ }
11248
+ if (result.type === "box") {
11249
+ state.structure.getIsoStructure().boxes.push(result.box);
10749
11250
  }
10750
11251
  return null;
10751
11252
  };
@@ -10824,7 +11325,8 @@ var parseM3uMediaDirective = (str) => {
10824
11325
  groupId: map["GROUP-ID"],
10825
11326
  language: map.LANGUAGE || null,
10826
11327
  name: map.NAME || null,
10827
- uri: map.URI
11328
+ uri: map.URI,
11329
+ mediaType: map.TYPE || null
10828
11330
  };
10829
11331
  };
10830
11332
 
@@ -10984,7 +11486,9 @@ var afterManifestFetch = async ({
10984
11486
  selectM3uStreamFn,
10985
11487
  logLevel,
10986
11488
  selectAssociatedPlaylistsFn,
10987
- readerInterface
11489
+ readerInterface,
11490
+ onAudioTrack,
11491
+ canSkipTracks
10988
11492
  }) => {
10989
11493
  const independentSegments = isIndependentSegments(structure);
10990
11494
  if (!independentSegments) {
@@ -11009,9 +11513,11 @@ var afterManifestFetch = async ({
11009
11513
  type: "selected-stream",
11010
11514
  stream: selectedPlaylist
11011
11515
  });
11516
+ const skipAudioTracks = onAudioTrack === null && canSkipTracks.doFieldsNeedTracks() === false;
11012
11517
  const associatedPlaylists = await selectAssociatedPlaylists({
11013
11518
  playlists: selectedPlaylist.associatedPlaylists,
11014
- fn: selectAssociatedPlaylistsFn
11519
+ fn: selectAssociatedPlaylistsFn,
11520
+ skipAudioTracks
11015
11521
  });
11016
11522
  m3uState.setAssociatedPlaylists(associatedPlaylists);
11017
11523
  const playlistUrls = [
@@ -11116,7 +11622,7 @@ var parseMedia = (options) => {
11116
11622
  controller: options.controller ?? undefined,
11117
11623
  selectM3uStream: options.selectM3uStream ?? defaultSelectM3uStreamFn,
11118
11624
  selectM3uAssociatedPlaylists: options.selectM3uAssociatedPlaylists ?? defaultSelectM3uAssociatedPlaylists,
11119
- mp4HeaderSegment: options.mp4HeaderSegment ?? null,
11625
+ m3uPlaylistContext: options.m3uPlaylistContext ?? null,
11120
11626
  src: options.src,
11121
11627
  mode: "query",
11122
11628
  onDiscardedData: null,
@@ -11128,6 +11634,39 @@ var parseMedia = (options) => {
11128
11634
  });
11129
11635
  };
11130
11636
 
11637
+ // src/containers/m3u/first-sample-in-m3u-chunk.ts
11638
+ var considerSeekBasedOnChunk = async ({
11639
+ sample,
11640
+ parentController,
11641
+ childController,
11642
+ callback,
11643
+ m3uState,
11644
+ playlistUrl,
11645
+ subtractChunks,
11646
+ chunkIndex
11647
+ }) => {
11648
+ const pendingSeek = m3uState.getSeekToSecondsToProcess(playlistUrl);
11649
+ if (pendingSeek === null) {
11650
+ await callback(sample);
11651
+ return;
11652
+ }
11653
+ const timestamp = Math.min(sample.dts / sample.timescale, sample.cts / sample.timescale);
11654
+ if (timestamp > pendingSeek.targetTime && chunkIndex !== null && chunkIndex > 0) {
11655
+ m3uState.setNextSeekShouldSubtractChunks(playlistUrl, subtractChunks + 1);
11656
+ parentController.seek({
11657
+ type: "keyframe-before-time",
11658
+ timeInSeconds: pendingSeek.targetTime
11659
+ });
11660
+ return;
11661
+ }
11662
+ childController.seek({
11663
+ type: "keyframe-before-time",
11664
+ timeInSeconds: pendingSeek.targetTime
11665
+ });
11666
+ m3uState.setNextSeekShouldSubtractChunks(playlistUrl, 0);
11667
+ m3uState.setSeekToSecondsToProcess(playlistUrl, null);
11668
+ };
11669
+
11131
11670
  // src/containers/m3u/get-chunks.ts
11132
11671
  var getChunks = (playlist) => {
11133
11672
  const chunks = [];
@@ -11145,127 +11684,282 @@ var getChunks = (playlist) => {
11145
11684
  }
11146
11685
  chunks.push({ duration: box.value, url: nextBox.value, isHeader: false });
11147
11686
  }
11148
- continue;
11687
+ continue;
11688
+ }
11689
+ return chunks;
11690
+ };
11691
+
11692
+ // src/containers/m3u/seek/get-chunk-to-seek-to.ts
11693
+ var getChunkToSeekTo = ({
11694
+ chunks,
11695
+ seekToSecondsToProcess
11696
+ }) => {
11697
+ let duration2 = 0;
11698
+ for (let i = 0;i < chunks.length; i++) {
11699
+ if (duration2 >= seekToSecondsToProcess) {
11700
+ return Math.max(0, i - 1);
11701
+ }
11702
+ duration2 += chunks[i].duration;
11149
11703
  }
11150
- return chunks;
11704
+ return Math.max(0, chunks.length - 1);
11151
11705
  };
11152
11706
 
11153
- // src/containers/m3u/iterate-over-segment-files.ts
11154
- var iteratorOverSegmentFiles = async ({
11155
- structure,
11156
- onVideoTrack,
11157
- m3uState,
11158
- onAudioTrack,
11159
- onDoneWithTracks,
11707
+ // src/containers/m3u/process-m3u-chunk.ts
11708
+ var processM3uChunk = ({
11160
11709
  playlistUrl,
11161
- logLevel,
11162
- parentController,
11163
- onInitialProgress,
11164
- readerInterface
11710
+ state,
11711
+ structure,
11712
+ audioDone,
11713
+ videoDone
11165
11714
  }) => {
11166
- const playlist = getPlaylist(structure, playlistUrl);
11167
- const chunks = getChunks(playlist);
11168
- let resolver = onInitialProgress;
11169
- let rejector = (_e) => {};
11170
- for (const chunk of chunks) {
11171
- resolver = onInitialProgress;
11172
- rejector = (_e) => {};
11173
- const childController = mediaParserController();
11174
- const forwarded = forwardMediaParserControllerPauseResume({
11175
- childController,
11176
- parentController
11715
+ const { promise, reject, resolve } = withResolvers();
11716
+ const onGlobalAudioTrack = audioDone ? null : async (track) => {
11717
+ const existingTracks = state.callbacks.tracks.getTracks();
11718
+ let { trackId } = track;
11719
+ while (existingTracks.find((t) => t.trackId === trackId)) {
11720
+ trackId++;
11721
+ }
11722
+ const onAudioSample = await registerAudioTrack({
11723
+ container: "m3u8",
11724
+ track: {
11725
+ ...track,
11726
+ trackId
11727
+ },
11728
+ registerAudioSampleCallback: state.callbacks.registerAudioSampleCallback,
11729
+ tracks: state.callbacks.tracks,
11730
+ logLevel: state.logLevel,
11731
+ onAudioTrack: state.onAudioTrack
11177
11732
  });
11178
- const makeContinuationFn = () => {
11179
- return {
11180
- continue() {
11181
- const { promise, reject, resolve } = withResolvers();
11182
- resolver = resolve;
11183
- rejector = reject;
11184
- childController.resume();
11185
- return promise;
11186
- },
11187
- abort() {
11188
- childController.abort();
11189
- }
11190
- };
11733
+ state.m3u.sampleSorter.addToStreamWithTrack(playlistUrl);
11734
+ if (onAudioSample === null) {
11735
+ return null;
11736
+ }
11737
+ state.m3u.sampleSorter.addAudioStreamToConsider(playlistUrl, onAudioSample);
11738
+ return async (sample) => {
11739
+ await state.m3u.sampleSorter.addAudioSample(playlistUrl, sample);
11191
11740
  };
11192
- const isLastChunk = chunk === chunks[chunks.length - 1];
11193
- await childController._internals.checkForAbortAndPause();
11194
- const src = readerInterface.createAdjacentFileSource(chunk.url, playlistUrl);
11195
- try {
11196
- const mp4HeaderSegment = m3uState.getMp4HeaderSegment(playlistUrl);
11197
- const data = await parseMedia({
11198
- src,
11199
- acknowledgeRemotionLicense: true,
11200
- logLevel,
11201
- controller: childController,
11202
- progressIntervalInMs: 0,
11203
- onParseProgress: () => {
11204
- childController.pause();
11205
- resolver(makeContinuationFn());
11206
- },
11207
- fields: chunk.isHeader ? { structure: true } : undefined,
11208
- onTracks: () => {
11209
- if (!m3uState.hasEmittedDoneWithTracks(playlistUrl)) {
11210
- m3uState.setHasEmittedDoneWithTracks(playlistUrl);
11211
- onDoneWithTracks();
11212
- return null;
11741
+ };
11742
+ const onGlobalVideoTrack = videoDone ? null : async (track) => {
11743
+ const existingTracks = state.callbacks.tracks.getTracks();
11744
+ let { trackId } = track;
11745
+ while (existingTracks.find((t) => t.trackId === trackId)) {
11746
+ trackId++;
11747
+ }
11748
+ const onVideoSample = await registerVideoTrack({
11749
+ container: "m3u8",
11750
+ track: {
11751
+ ...track,
11752
+ trackId
11753
+ },
11754
+ logLevel: state.logLevel,
11755
+ onVideoTrack: state.onVideoTrack,
11756
+ registerVideoSampleCallback: state.callbacks.registerVideoSampleCallback,
11757
+ tracks: state.callbacks.tracks
11758
+ });
11759
+ state.m3u.sampleSorter.addToStreamWithTrack(playlistUrl);
11760
+ if (onVideoSample === null) {
11761
+ return null;
11762
+ }
11763
+ state.m3u.sampleSorter.addVideoStreamToConsider(playlistUrl, onVideoSample);
11764
+ return async (sample) => {
11765
+ await state.m3u.sampleSorter.addVideoSample(playlistUrl, sample);
11766
+ };
11767
+ };
11768
+ const pausableIterator = async () => {
11769
+ const playlist = getPlaylist(structure, playlistUrl);
11770
+ const chunks = getChunks(playlist);
11771
+ const seekToSecondsToProcess = state.m3u.getSeekToSecondsToProcess(playlistUrl);
11772
+ const chunksToSubtract = state.m3u.getNextSeekShouldSubtractChunks(playlistUrl);
11773
+ let chunkIndex = null;
11774
+ if (seekToSecondsToProcess !== null) {
11775
+ chunkIndex = Math.max(0, getChunkToSeekTo({
11776
+ chunks,
11777
+ seekToSecondsToProcess: seekToSecondsToProcess.targetTime
11778
+ }) - chunksToSubtract);
11779
+ }
11780
+ const currentPromise = {
11781
+ resolver: () => {
11782
+ return;
11783
+ },
11784
+ rejector: reject
11785
+ };
11786
+ const requiresHeaderToBeFetched = chunks[0].isHeader;
11787
+ for (const chunk of chunks) {
11788
+ const mp4HeaderSegment = state.m3u.getMp4HeaderSegment(playlistUrl);
11789
+ if (requiresHeaderToBeFetched && mp4HeaderSegment && chunk.isHeader) {
11790
+ continue;
11791
+ }
11792
+ if (chunkIndex !== null && chunks.indexOf(chunk) < chunkIndex && !chunk.isHeader) {
11793
+ continue;
11794
+ }
11795
+ currentPromise.resolver = (newRun) => {
11796
+ state.m3u.setM3uStreamRun(playlistUrl, newRun);
11797
+ resolve();
11798
+ };
11799
+ currentPromise.rejector = reject;
11800
+ const childController = mediaParserController();
11801
+ const forwarded = forwardMediaParserControllerPauseResume({
11802
+ childController,
11803
+ parentController: state.controller
11804
+ });
11805
+ const nextChunk = chunks[chunks.indexOf(chunk) + 1];
11806
+ if (nextChunk) {
11807
+ const nextChunkSource = state.readerInterface.createAdjacentFileSource(nextChunk.url, playlistUrl);
11808
+ state.readerInterface.preload({
11809
+ logLevel: state.logLevel,
11810
+ range: null,
11811
+ src: nextChunkSource,
11812
+ prefetchCache: state.prefetchCache
11813
+ });
11814
+ }
11815
+ const makeContinuationFn = () => {
11816
+ return {
11817
+ continue() {
11818
+ const resolver = withResolvers();
11819
+ currentPromise.resolver = resolver.resolve;
11820
+ currentPromise.rejector = resolver.reject;
11821
+ childController.resume();
11822
+ return resolver.promise;
11823
+ },
11824
+ abort() {
11825
+ childController.abort();
11213
11826
  }
11214
- },
11215
- onAudioTrack: onAudioTrack === null ? null : async ({ track }) => {
11216
- const callbackOrFalse = m3uState.hasEmittedAudioTrack(playlistUrl);
11217
- if (callbackOrFalse === false) {
11218
- const callback = await onAudioTrack(track);
11219
- if (!callback) {
11220
- m3uState.setHasEmittedAudioTrack(playlistUrl, null);
11827
+ };
11828
+ };
11829
+ const isLastChunk = chunk === chunks[chunks.length - 1];
11830
+ await childController._internals.checkForAbortAndPause();
11831
+ const src = state.readerInterface.createAdjacentFileSource(chunk.url, playlistUrl);
11832
+ try {
11833
+ const data = await parseMedia({
11834
+ src,
11835
+ acknowledgeRemotionLicense: true,
11836
+ logLevel: state.logLevel,
11837
+ controller: childController,
11838
+ progressIntervalInMs: 0,
11839
+ onParseProgress: () => {
11840
+ childController.pause();
11841
+ currentPromise.resolver(makeContinuationFn());
11842
+ },
11843
+ fields: chunk.isHeader ? { structure: true } : undefined,
11844
+ onTracks: () => {
11845
+ if (!state.m3u.hasEmittedDoneWithTracks(playlistUrl)) {
11846
+ state.m3u.setHasEmittedDoneWithTracks(playlistUrl);
11847
+ const allDone = state.m3u.setTracksDone(playlistUrl);
11848
+ if (allDone) {
11849
+ state.callbacks.tracks.setIsDone(state.logLevel);
11850
+ }
11851
+ return null;
11852
+ }
11853
+ },
11854
+ onAudioTrack: onGlobalAudioTrack === null ? null : async ({ track }) => {
11855
+ const callbackOrFalse = state.m3u.hasEmittedAudioTrack(playlistUrl);
11856
+ if (callbackOrFalse === false) {
11857
+ const callback = await onGlobalAudioTrack(track);
11858
+ if (!callback) {
11859
+ state.m3u.setHasEmittedAudioTrack(playlistUrl, null);
11860
+ return null;
11861
+ }
11862
+ state.m3u.setHasEmittedAudioTrack(playlistUrl, callback);
11863
+ return async (sample) => {
11864
+ await considerSeekBasedOnChunk({
11865
+ sample,
11866
+ callback,
11867
+ parentController: state.controller,
11868
+ childController,
11869
+ m3uState: state.m3u,
11870
+ playlistUrl,
11871
+ subtractChunks: chunksToSubtract,
11872
+ chunkIndex
11873
+ });
11874
+ };
11875
+ }
11876
+ if (callbackOrFalse === null) {
11221
11877
  return null;
11222
11878
  }
11223
- m3uState.setHasEmittedAudioTrack(playlistUrl, callback);
11224
- return (sample) => {
11225
- return callback(sample);
11879
+ return async (sample) => {
11880
+ await considerSeekBasedOnChunk({
11881
+ sample,
11882
+ m3uState: state.m3u,
11883
+ playlistUrl,
11884
+ callback: callbackOrFalse,
11885
+ parentController: state.controller,
11886
+ childController,
11887
+ subtractChunks: chunksToSubtract,
11888
+ chunkIndex
11889
+ });
11226
11890
  };
11227
- }
11228
- return callbackOrFalse;
11229
- },
11230
- onVideoTrack: onVideoTrack === null ? null : async ({ track }) => {
11231
- const callbackOrFalse = m3uState.hasEmittedVideoTrack(playlistUrl);
11232
- if (callbackOrFalse === false) {
11233
- const callback = await onVideoTrack({
11234
- ...track,
11235
- m3uStreamFormat: chunk.isHeader || mp4HeaderSegment ? "mp4" : "ts"
11236
- });
11237
- if (!callback) {
11238
- m3uState.setHasEmittedVideoTrack(playlistUrl, null);
11891
+ },
11892
+ onVideoTrack: onGlobalVideoTrack === null ? null : async ({ track }) => {
11893
+ const callbackOrFalse = state.m3u.hasEmittedVideoTrack(playlistUrl);
11894
+ if (callbackOrFalse === false) {
11895
+ const callback = await onGlobalVideoTrack({
11896
+ ...track,
11897
+ m3uStreamFormat: chunk.isHeader || mp4HeaderSegment ? "mp4" : "ts"
11898
+ });
11899
+ if (!callback) {
11900
+ state.m3u.setHasEmittedVideoTrack(playlistUrl, null);
11901
+ return null;
11902
+ }
11903
+ state.m3u.setHasEmittedVideoTrack(playlistUrl, callback);
11904
+ return async (sample) => {
11905
+ await considerSeekBasedOnChunk({
11906
+ sample,
11907
+ m3uState: state.m3u,
11908
+ playlistUrl,
11909
+ callback,
11910
+ parentController: state.controller,
11911
+ childController,
11912
+ subtractChunks: chunksToSubtract,
11913
+ chunkIndex
11914
+ });
11915
+ };
11916
+ }
11917
+ if (callbackOrFalse === null) {
11239
11918
  return null;
11240
11919
  }
11241
- m3uState.setHasEmittedVideoTrack(playlistUrl, callback);
11242
- return (sample) => {
11243
- return callback(sample);
11920
+ return async (sample) => {
11921
+ await considerSeekBasedOnChunk({
11922
+ sample,
11923
+ m3uState: state.m3u,
11924
+ playlistUrl,
11925
+ callback: callbackOrFalse,
11926
+ parentController: state.controller,
11927
+ childController,
11928
+ subtractChunks: chunksToSubtract,
11929
+ chunkIndex
11930
+ });
11244
11931
  };
11932
+ },
11933
+ reader: state.readerInterface,
11934
+ makeSamplesStartAtZero: false,
11935
+ m3uPlaylistContext: {
11936
+ mp4HeaderSegment,
11937
+ isLastChunkInPlaylist: isLastChunk
11245
11938
  }
11246
- return callbackOrFalse;
11247
- },
11248
- reader: readerInterface,
11249
- mp4HeaderSegment,
11250
- makeSamplesStartAtZero: false
11251
- });
11252
- if (chunk.isHeader) {
11253
- if (data.structure.type !== "iso-base-media") {
11254
- throw new Error("Expected an mp4 file");
11939
+ });
11940
+ if (chunk.isHeader) {
11941
+ if (data.structure.type !== "iso-base-media") {
11942
+ throw new Error("Expected an mp4 file");
11943
+ }
11944
+ state.m3u.setMp4HeaderSegment(playlistUrl, data.structure);
11255
11945
  }
11256
- m3uState.setMp4HeaderSegment(playlistUrl, data.structure);
11946
+ } catch (e) {
11947
+ currentPromise.rejector(e);
11948
+ throw e;
11949
+ }
11950
+ forwarded.cleanup();
11951
+ if (!isLastChunk) {
11952
+ childController.pause();
11953
+ currentPromise.resolver(makeContinuationFn());
11257
11954
  }
11258
- } catch (e) {
11259
- rejector(e);
11260
- throw e;
11261
- }
11262
- forwarded.cleanup();
11263
- if (!isLastChunk) {
11264
- childController.pause();
11265
- resolver(makeContinuationFn());
11266
11955
  }
11267
- }
11268
- resolver(null);
11956
+ currentPromise.resolver(null);
11957
+ };
11958
+ const run = pausableIterator();
11959
+ run.catch((err) => {
11960
+ reject(err);
11961
+ });
11962
+ return promise;
11269
11963
  };
11270
11964
 
11271
11965
  // src/containers/m3u/run-over-m3u.ts
@@ -11296,80 +11990,12 @@ var runOverM3u = async ({
11296
11990
  return;
11297
11991
  }
11298
11992
  Log.trace(logLevel, "Starting new M3U parsing process for", playlistUrl);
11299
- return new Promise((resolve, reject) => {
11300
- const run = iteratorOverSegmentFiles({
11301
- playlistUrl,
11302
- structure,
11303
- onInitialProgress: (newRun) => {
11304
- state.m3u.setM3uStreamRun(playlistUrl, newRun);
11305
- resolve();
11306
- },
11307
- logLevel: state.logLevel,
11308
- onDoneWithTracks() {
11309
- const allDone = state.m3u.setTracksDone(playlistUrl);
11310
- if (allDone) {
11311
- state.callbacks.tracks.setIsDone(state.logLevel);
11312
- }
11313
- },
11314
- onAudioTrack: audioDone ? null : async (track) => {
11315
- const existingTracks = state.callbacks.tracks.getTracks();
11316
- let { trackId } = track;
11317
- while (existingTracks.find((t) => t.trackId === trackId)) {
11318
- trackId++;
11319
- }
11320
- const onAudioSample = await registerAudioTrack({
11321
- container: "m3u8",
11322
- track: {
11323
- ...track,
11324
- trackId
11325
- },
11326
- registerAudioSampleCallback: state.callbacks.registerAudioSampleCallback,
11327
- tracks: state.callbacks.tracks,
11328
- logLevel: state.logLevel,
11329
- onAudioTrack: state.onAudioTrack
11330
- });
11331
- state.m3u.sampleSorter.addToStreamWithTrack(playlistUrl);
11332
- if (onAudioSample === null) {
11333
- return null;
11334
- }
11335
- state.m3u.sampleSorter.addAudioStreamToConsider(playlistUrl, onAudioSample);
11336
- return async (sample) => {
11337
- await state.m3u.sampleSorter.addAudioSample(playlistUrl, sample);
11338
- };
11339
- },
11340
- onVideoTrack: videoDone ? null : async (track) => {
11341
- const existingTracks = state.callbacks.tracks.getTracks();
11342
- let { trackId } = track;
11343
- while (existingTracks.find((t) => t.trackId === trackId)) {
11344
- trackId++;
11345
- }
11346
- const onVideoSample = await registerVideoTrack({
11347
- container: "m3u8",
11348
- track: {
11349
- ...track,
11350
- trackId
11351
- },
11352
- logLevel: state.logLevel,
11353
- onVideoTrack: state.onVideoTrack,
11354
- registerVideoSampleCallback: state.callbacks.registerVideoSampleCallback,
11355
- tracks: state.callbacks.tracks
11356
- });
11357
- state.m3u.sampleSorter.addToStreamWithTrack(playlistUrl);
11358
- if (onVideoSample === null) {
11359
- return null;
11360
- }
11361
- state.m3u.sampleSorter.addVideoStreamToConsider(playlistUrl, onVideoSample);
11362
- return async (sample) => {
11363
- await state.m3u.sampleSorter.addVideoSample(playlistUrl, sample);
11364
- };
11365
- },
11366
- m3uState: state.m3u,
11367
- parentController: state.controller,
11368
- readerInterface: state.readerInterface
11369
- });
11370
- run.catch((err) => {
11371
- reject(err);
11372
- });
11993
+ await processM3uChunk({
11994
+ playlistUrl,
11995
+ state,
11996
+ structure,
11997
+ audioDone,
11998
+ videoDone
11373
11999
  });
11374
12000
  };
11375
12001
 
@@ -11391,6 +12017,10 @@ var parseM3u = async ({ state }) => {
11391
12017
  if (typeof state.src !== "string" && !(state.src instanceof URL)) {
11392
12018
  throw new Error("Expected src to be a string");
11393
12019
  }
12020
+ state.mediaSection.addMediaSection({
12021
+ start: 0,
12022
+ size: state.contentLength + 1
12023
+ });
11394
12024
  await afterManifestFetch({
11395
12025
  structure,
11396
12026
  m3uState: state.m3u,
@@ -11398,7 +12028,9 @@ var parseM3u = async ({ state }) => {
11398
12028
  selectM3uStreamFn: state.selectM3uStreamFn,
11399
12029
  logLevel: state.logLevel,
11400
12030
  selectAssociatedPlaylistsFn: state.selectM3uAssociatedPlaylistsFn,
11401
- readerInterface: state.readerInterface
12031
+ readerInterface: state.readerInterface,
12032
+ onAudioTrack: state.onAudioTrack,
12033
+ canSkipTracks: state.callbacks.canSkipTracksState
11402
12034
  });
11403
12035
  return null;
11404
12036
  }
@@ -13577,23 +14209,62 @@ var parseBlockFlags = (iterator, type) => {
13577
14209
  };
13578
14210
 
13579
14211
  // src/containers/webm/get-sample-from-block.ts
13580
- var addAvcToTrackIfNecessary = ({
14212
+ var addAvcToTrackAndActivateTrackIfNecessary = async ({
13581
14213
  partialVideoSample,
13582
14214
  codec,
13583
14215
  structureState: structureState2,
13584
14216
  webmState,
13585
- trackNumber: trackNumber2
14217
+ trackNumber: trackNumber2,
14218
+ logLevel,
14219
+ callbacks,
14220
+ onVideoTrack
13586
14221
  }) => {
13587
- if (codec === "V_MPEG4/ISO/AVC" && getTracksFromMatroska({ structureState: structureState2, webmState }).missingInfo.length > 0) {
13588
- const parsed = parseAvc(partialVideoSample.data);
13589
- for (const parse of parsed) {
13590
- if (parse.type === "avc-profile") {
13591
- webmState.setAvcProfileForTrackNumber(trackNumber2, parse);
14222
+ if (codec !== "V_MPEG4/ISO/AVC") {
14223
+ return;
14224
+ }
14225
+ const missingTracks = getTracksFromMatroska({
14226
+ structureState: structureState2,
14227
+ webmState
14228
+ }).missingInfo;
14229
+ if (missingTracks.length === 0) {
14230
+ return;
14231
+ }
14232
+ const parsed = parseAvc(partialVideoSample.data);
14233
+ for (const parse of parsed) {
14234
+ if (parse.type === "avc-profile") {
14235
+ webmState.setAvcProfileForTrackNumber(trackNumber2, parse);
14236
+ const track = missingTracks.find((t) => t.trackId === trackNumber2);
14237
+ if (!track) {
14238
+ throw new Error("Could not find track " + trackNumber2);
14239
+ }
14240
+ const resolvedTracks = getTracksFromMatroska({
14241
+ structureState: structureState2,
14242
+ webmState
14243
+ }).resolved;
14244
+ const resolvedTrack = resolvedTracks.find((t) => t.trackId === trackNumber2);
14245
+ if (!resolvedTrack) {
14246
+ throw new Error("Could not find track " + trackNumber2);
13592
14247
  }
14248
+ await registerVideoTrack({
14249
+ track: resolvedTrack,
14250
+ container: "webm",
14251
+ logLevel,
14252
+ onVideoTrack,
14253
+ registerVideoSampleCallback: callbacks.registerVideoSampleCallback,
14254
+ tracks: callbacks.tracks
14255
+ });
13593
14256
  }
13594
14257
  }
13595
14258
  };
13596
- var getSampleFromBlock = (ebml, webmState, offset, structureState2) => {
14259
+ var getSampleFromBlock = async ({
14260
+ ebml,
14261
+ webmState,
14262
+ offset,
14263
+ structureState: structureState2,
14264
+ callbacks,
14265
+ logLevel,
14266
+ onVideoTrack
14267
+ }) => {
13597
14268
  const iterator = getArrayBufferIterator(ebml.value, ebml.value.length);
13598
14269
  const trackNumber2 = iterator.getVint();
13599
14270
  if (trackNumber2 === null) {
@@ -13631,12 +14302,15 @@ var getSampleFromBlock = (ebml, webmState, offset, structureState2) => {
13631
14302
  partialVideoSample
13632
14303
  };
13633
14304
  }
13634
- addAvcToTrackIfNecessary({
14305
+ await addAvcToTrackAndActivateTrackIfNecessary({
13635
14306
  codec,
13636
14307
  partialVideoSample,
13637
14308
  structureState: structureState2,
13638
14309
  webmState,
13639
- trackNumber: trackNumber2
14310
+ trackNumber: trackNumber2,
14311
+ callbacks,
14312
+ logLevel,
14313
+ onVideoTrack
13640
14314
  });
13641
14315
  const sample = {
13642
14316
  ...partialVideoSample,
@@ -13796,21 +14470,31 @@ var postprocessEbml = async ({
13796
14470
  });
13797
14471
  }
13798
14472
  if (track && track.type === "video") {
13799
- await registerVideoTrack({
13800
- track,
13801
- container: "webm",
13802
- logLevel,
13803
- onVideoTrack,
13804
- registerVideoSampleCallback: callbacks.registerVideoSampleCallback,
13805
- tracks: callbacks.tracks
13806
- });
14473
+ if (track.codec !== NO_CODEC_PRIVATE_SHOULD_BE_DERIVED_FROM_SPS) {
14474
+ await registerVideoTrack({
14475
+ track,
14476
+ container: "webm",
14477
+ logLevel,
14478
+ onVideoTrack,
14479
+ registerVideoSampleCallback: callbacks.registerVideoSampleCallback,
14480
+ tracks: callbacks.tracks
14481
+ });
14482
+ }
13807
14483
  }
13808
14484
  }
13809
14485
  if (ebml.type === "Timestamp") {
13810
14486
  webmState.setTimestampOffset(offset, ebml.value.value);
13811
14487
  }
13812
14488
  if (ebml.type === "Block" || ebml.type === "SimpleBlock") {
13813
- const sample = getSampleFromBlock(ebml, webmState, offset, structureState2);
14489
+ const sample = await getSampleFromBlock({
14490
+ ebml,
14491
+ webmState,
14492
+ offset,
14493
+ structureState: structureState2,
14494
+ callbacks,
14495
+ logLevel,
14496
+ onVideoTrack
14497
+ });
13814
14498
  if (sample.type === "video-sample") {
13815
14499
  await callbacks.onVideoSample(sample.videoSample.trackId, sample.videoSample);
13816
14500
  return {
@@ -13841,7 +14525,15 @@ var postprocessEbml = async ({
13841
14525
  throw new Error("Expected block segment");
13842
14526
  }
13843
14527
  const hasReferenceBlock = ebml.value.find((c) => c.type === "ReferenceBlock");
13844
- const sample = block2.value.length === 0 ? null : getSampleFromBlock(block2, webmState, offset, structureState2);
14528
+ const sample = block2.value.length === 0 ? null : await getSampleFromBlock({
14529
+ ebml: block2,
14530
+ webmState,
14531
+ offset,
14532
+ structureState: structureState2,
14533
+ callbacks,
14534
+ logLevel,
14535
+ onVideoTrack
14536
+ });
13845
14537
  if (sample && sample.type === "partial-video-sample") {
13846
14538
  const completeFrame = {
13847
14539
  ...sample.partialVideoSample,
@@ -14042,9 +14734,9 @@ var initVideo = async ({ state }) => {
14042
14734
  });
14043
14735
  return;
14044
14736
  }
14045
- if (state.mp4HeaderSegment) {
14737
+ if (state.m3uPlaylistContext?.mp4HeaderSegment) {
14046
14738
  Log.verbose(state.logLevel, "Detected ISO Base Media segment");
14047
- const moovAtom = getMoovFromFromIsoStructure(state.mp4HeaderSegment);
14739
+ const moovAtom = getMoovFromFromIsoStructure(state.m3uPlaylistContext.mp4HeaderSegment);
14048
14740
  if (!moovAtom) {
14049
14741
  throw new Error("No moov box found");
14050
14742
  }
@@ -14267,19 +14959,33 @@ var parseLoop = async ({
14267
14959
  try {
14268
14960
  await triggerInfoEmit(state);
14269
14961
  await state.controller._internals.checkForAbortAndPause();
14270
- const skip = await runParseIteration({
14962
+ const result = await runParseIteration({
14271
14963
  state
14272
14964
  });
14273
- if (skip !== null) {
14274
- state.increaseSkippedBytes(skip.skipTo - state.iterator.counter.getOffset());
14275
- if (skip.skipTo === state.contentLength) {
14276
- state.iterator.discard(skip.skipTo - state.iterator.counter.getOffset());
14965
+ if (result !== null && result.action === "fetch-more-data") {
14966
+ Log.verbose(state.logLevel, `Need to fetch ${result.bytesNeeded} more bytes before we can continue`);
14967
+ const startBytesRemaining = state.iterator.bytesRemaining();
14968
+ while (true) {
14969
+ const done = await fetchMoreData(state);
14970
+ if (done) {
14971
+ break;
14972
+ }
14973
+ if (state.iterator.bytesRemaining() - startBytesRemaining >= result.bytesNeeded) {
14974
+ break;
14975
+ }
14976
+ }
14977
+ continue;
14978
+ }
14979
+ if (result !== null && result.action === "skip") {
14980
+ state.increaseSkippedBytes(result.skipTo - state.iterator.counter.getOffset());
14981
+ if (result.skipTo === state.contentLength) {
14982
+ state.iterator.discard(result.skipTo - state.iterator.counter.getOffset());
14277
14983
  Log.verbose(state.logLevel, "Skipped to end of file, not fetching.");
14278
14984
  break;
14279
14985
  }
14280
14986
  const seekStart = Date.now();
14281
14987
  await performSeek({
14282
- seekTo: skip.skipTo,
14988
+ seekTo: result.skipTo,
14283
14989
  userInitiated: false,
14284
14990
  controller: state.controller,
14285
14991
  mediaSection: state.mediaSection,
@@ -14292,7 +14998,8 @@ var parseLoop = async ({
14292
14998
  readerInterface: state.readerInterface,
14293
14999
  fields: state.fields,
14294
15000
  src: state.src,
14295
- discardReadBytes: state.discardReadBytes
15001
+ discardReadBytes: state.discardReadBytes,
15002
+ prefetchCache: state.prefetchCache
14296
15003
  });
14297
15004
  state.timings.timeSeeking += Date.now() - seekStart;
14298
15005
  }
@@ -14399,6 +15106,9 @@ var setSeekingHints = ({
14399
15106
  setSeekingHintsForAac();
14400
15107
  return;
14401
15108
  }
15109
+ if (hints.type === "m3u8-seeking-hints") {
15110
+ return;
15111
+ }
14402
15112
  throw new Error(`Unknown seeking hints type: ${hints}`);
14403
15113
  };
14404
15114
 
@@ -14554,12 +15264,16 @@ var getMfraAtom = async ({
14554
15264
  contentLength,
14555
15265
  readerInterface,
14556
15266
  controller,
14557
- parentSize
15267
+ parentSize,
15268
+ logLevel,
15269
+ prefetchCache
14558
15270
  }) => {
14559
15271
  const result = await readerInterface.read({
14560
15272
  controller,
14561
15273
  range: [contentLength - parentSize, contentLength - 1],
14562
- src
15274
+ src,
15275
+ logLevel,
15276
+ prefetchCache
14563
15277
  });
14564
15278
  const iterator = getArrayBufferIterator(new Uint8Array, parentSize);
14565
15279
  while (true) {
@@ -14579,12 +15293,16 @@ var getMfroAtom = async ({
14579
15293
  src,
14580
15294
  contentLength,
14581
15295
  readerInterface,
14582
- controller
15296
+ controller,
15297
+ logLevel,
15298
+ prefetchCache
14583
15299
  }) => {
14584
15300
  const result = await readerInterface.read({
14585
15301
  controller,
14586
15302
  range: [contentLength - 16, contentLength - 1],
14587
- src
15303
+ src,
15304
+ logLevel,
15305
+ prefetchCache
14588
15306
  });
14589
15307
  const { value } = await result.reader.reader.read();
14590
15308
  if (!value) {
@@ -14619,13 +15337,16 @@ var getMfraSeekingBox = async ({
14619
15337
  controller,
14620
15338
  readerInterface,
14621
15339
  src,
14622
- logLevel
15340
+ logLevel,
15341
+ prefetchCache
14623
15342
  }) => {
14624
15343
  const parentSize = await getMfroAtom({
14625
15344
  contentLength,
14626
15345
  controller,
14627
15346
  readerInterface,
14628
- src
15347
+ src,
15348
+ logLevel,
15349
+ prefetchCache
14629
15350
  });
14630
15351
  if (!parentSize) {
14631
15352
  return null;
@@ -14635,7 +15356,9 @@ var getMfraSeekingBox = async ({
14635
15356
  controller,
14636
15357
  readerInterface,
14637
15358
  src,
14638
- parentSize
15359
+ parentSize,
15360
+ logLevel,
15361
+ prefetchCache
14639
15362
  });
14640
15363
  mfraAtom.discard(8);
14641
15364
  return getIsoBaseMediaChildren({
@@ -14653,7 +15376,8 @@ var lazyMfraLoad = ({
14653
15376
  controller,
14654
15377
  readerInterface,
14655
15378
  src,
14656
- logLevel
15379
+ logLevel,
15380
+ prefetchCache
14657
15381
  }) => {
14658
15382
  let prom = null;
14659
15383
  let result = null;
@@ -14667,7 +15391,8 @@ var lazyMfraLoad = ({
14667
15391
  controller,
14668
15392
  readerInterface,
14669
15393
  src,
14670
- logLevel
15394
+ logLevel,
15395
+ prefetchCache
14671
15396
  }).then((boxes) => {
14672
15397
  Log.verbose(logLevel, "Lazily found mfra atom.");
14673
15398
  result = boxes;
@@ -14708,7 +15433,8 @@ var isoBaseMediaState = ({
14708
15433
  controller,
14709
15434
  readerInterface,
14710
15435
  src,
14711
- logLevel
15436
+ logLevel,
15437
+ prefetchCache
14712
15438
  }) => {
14713
15439
  return {
14714
15440
  flatSamples: cachedSamplePositionsState(),
@@ -14718,7 +15444,8 @@ var isoBaseMediaState = ({
14718
15444
  controller,
14719
15445
  readerInterface,
14720
15446
  src,
14721
- logLevel
15447
+ logLevel,
15448
+ prefetchCache
14722
15449
  }),
14723
15450
  moof: precomputedMoofState(),
14724
15451
  tfra: precomputedTfraState()
@@ -14735,6 +15462,7 @@ var keyframesState = () => {
14735
15462
  keyframes.push(keyframe);
14736
15463
  };
14737
15464
  const getKeyframes2 = () => {
15465
+ keyframes.sort((a, b) => a.positionInBytes - b.positionInBytes);
14738
15466
  return keyframes;
14739
15467
  };
14740
15468
  const setFromSeekingHints = (keyframesFromHints) => {
@@ -14757,8 +15485,11 @@ var sampleSorter = ({
14757
15485
  const streamsWithTracks = [];
14758
15486
  const audioCallbacks = {};
14759
15487
  const videoCallbacks = {};
14760
- const latestSample = {};
15488
+ let latestSample = {};
14761
15489
  return {
15490
+ clearSamples: () => {
15491
+ latestSample = {};
15492
+ },
14762
15493
  addToStreamWithTrack: (src) => {
14763
15494
  streamsWithTracks.push(src);
14764
15495
  },
@@ -14831,6 +15562,8 @@ var m3uState = (logLevel) => {
14831
15562
  const hasEmittedAudioTrack = {};
14832
15563
  const hasEmittedDoneWithTracks = {};
14833
15564
  let hasFinishedManifest = false;
15565
+ const seekToSecondsToProcess = {};
15566
+ const nextSeekShouldSubtractChunks = {};
14834
15567
  let readyToIterateOverM3u = false;
14835
15568
  const allChunksProcessed = {};
14836
15569
  const m3uStreamRuns = {};
@@ -14892,6 +15625,11 @@ var m3uState = (logLevel) => {
14892
15625
  setAllChunksProcessed: (src) => {
14893
15626
  allChunksProcessed[src] = true;
14894
15627
  },
15628
+ clearAllChunksProcessed: () => {
15629
+ Object.keys(allChunksProcessed).forEach((key) => {
15630
+ delete allChunksProcessed[key];
15631
+ });
15632
+ },
14895
15633
  getAllChunksProcessedForPlaylist,
14896
15634
  getAllChunksProcessedOverall: () => {
14897
15635
  if (!selectedMainPlaylist) {
@@ -14919,6 +15657,11 @@ var m3uState = (logLevel) => {
14919
15657
  getTrackDone: (playlistUrl) => {
14920
15658
  return tracksDone[playlistUrl];
14921
15659
  },
15660
+ clearTracksDone: () => {
15661
+ Object.keys(tracksDone).forEach((key) => {
15662
+ delete tracksDone[key];
15663
+ });
15664
+ },
14922
15665
  getM3uStreamRun: (playlistUrl) => m3uStreamRuns[playlistUrl] ?? null,
14923
15666
  abortM3UStreamRuns: () => {
14924
15667
  const values = Object.values(m3uStreamRuns);
@@ -14937,7 +15680,15 @@ var m3uState = (logLevel) => {
14937
15680
  getSelectedPlaylists,
14938
15681
  sampleSorter: sampleSorter({ logLevel, getAllChunksProcessedForPlaylist }),
14939
15682
  setMp4HeaderSegment,
14940
- getMp4HeaderSegment
15683
+ getMp4HeaderSegment,
15684
+ setSeekToSecondsToProcess: (playlistUrl, m3uSeek) => {
15685
+ seekToSecondsToProcess[playlistUrl] = m3uSeek;
15686
+ },
15687
+ getSeekToSecondsToProcess: (playlistUrl) => seekToSecondsToProcess[playlistUrl] ?? null,
15688
+ setNextSeekShouldSubtractChunks: (playlistUrl, chunks) => {
15689
+ nextSeekShouldSubtractChunks[playlistUrl] = chunks;
15690
+ },
15691
+ getNextSeekShouldSubtractChunks: (playlistUrl) => nextSeekShouldSubtractChunks[playlistUrl] ?? 0
14941
15692
  };
14942
15693
  };
14943
15694
 
@@ -14986,12 +15737,15 @@ var fetchWebmCues = async ({
14986
15737
  readerInterface,
14987
15738
  controller,
14988
15739
  position,
14989
- logLevel
15740
+ logLevel,
15741
+ prefetchCache
14990
15742
  }) => {
14991
15743
  const result = await readerInterface.read({
14992
15744
  controller,
14993
15745
  range: position,
14994
- src
15746
+ src,
15747
+ logLevel,
15748
+ prefetchCache
14995
15749
  });
14996
15750
  const { value } = await result.reader.reader.read();
14997
15751
  if (!value) {
@@ -15018,7 +15772,8 @@ var lazyCuesFetch = ({
15018
15772
  controller,
15019
15773
  logLevel,
15020
15774
  readerInterface,
15021
- src
15775
+ src,
15776
+ prefetchCache
15022
15777
  }) => {
15023
15778
  let prom = null;
15024
15779
  let sOffset = null;
@@ -15040,7 +15795,8 @@ var lazyCuesFetch = ({
15040
15795
  logLevel,
15041
15796
  position,
15042
15797
  readerInterface,
15043
- src
15798
+ src,
15799
+ prefetchCache
15044
15800
  }).then((cues) => {
15045
15801
  Log.verbose(logLevel, "Cues loaded");
15046
15802
  result = cues;
@@ -15102,7 +15858,8 @@ var webmState = ({
15102
15858
  controller,
15103
15859
  logLevel,
15104
15860
  readerInterface,
15105
- src
15861
+ src,
15862
+ prefetchCache
15106
15863
  }) => {
15107
15864
  const trackEntries = {};
15108
15865
  const onTrackEntrySegment = (trackEntry2) => {
@@ -15163,7 +15920,8 @@ var webmState = ({
15163
15920
  controller,
15164
15921
  logLevel,
15165
15922
  readerInterface,
15166
- src
15923
+ src,
15924
+ prefetchCache
15167
15925
  });
15168
15926
  const getTimeStampMapForSeekingHints = () => {
15169
15927
  return timestampMap;
@@ -15244,13 +16002,16 @@ var fetchIdx1 = async ({
15244
16002
  readerInterface,
15245
16003
  controller,
15246
16004
  position,
15247
- logLevel
16005
+ logLevel,
16006
+ prefetchCache
15248
16007
  }) => {
15249
16008
  Log.verbose(logLevel, "Making request to fetch idx1 from ", src, "position", position);
15250
16009
  const result = await readerInterface.read({
15251
16010
  controller,
15252
16011
  range: position,
15253
- src
16012
+ src,
16013
+ logLevel,
16014
+ prefetchCache
15254
16015
  });
15255
16016
  const iterator = getArrayBufferIterator(new Uint8Array, Infinity);
15256
16017
  while (true) {
@@ -15281,7 +16042,8 @@ var lazyIdx1Fetch = ({
15281
16042
  controller,
15282
16043
  logLevel,
15283
16044
  readerInterface,
15284
- src
16045
+ src,
16046
+ prefetchCache
15285
16047
  }) => {
15286
16048
  let prom = null;
15287
16049
  let result = null;
@@ -15297,7 +16059,8 @@ var lazyIdx1Fetch = ({
15297
16059
  logLevel,
15298
16060
  position,
15299
16061
  readerInterface,
15300
- src
16062
+ src,
16063
+ prefetchCache
15301
16064
  }).then((entries) => {
15302
16065
  prom = null;
15303
16066
  result = entries;
@@ -15418,7 +16181,8 @@ var riffSpecificState = ({
15418
16181
  controller,
15419
16182
  logLevel,
15420
16183
  readerInterface,
15421
- src
16184
+ src,
16185
+ prefetchCache
15422
16186
  }) => {
15423
16187
  let avcProfile = null;
15424
16188
  let nextTrackIndex = 0;
@@ -15437,7 +16201,8 @@ var riffSpecificState = ({
15437
16201
  controller,
15438
16202
  logLevel,
15439
16203
  readerInterface,
15440
- src
16204
+ src,
16205
+ prefetchCache
15441
16206
  });
15442
16207
  const sampleCounter = riffSampleCounter();
15443
16208
  return {
@@ -15458,7 +16223,7 @@ var riffSpecificState = ({
15458
16223
  };
15459
16224
 
15460
16225
  // src/state/sample-callbacks.ts
15461
- var sampleCallback = ({
16226
+ var callbacksState = ({
15462
16227
  controller,
15463
16228
  hasAudioTrackHandlers,
15464
16229
  hasVideoTrackHandlers,
@@ -15765,14 +16530,15 @@ var makeParserState = ({
15765
16530
  onDiscardedData,
15766
16531
  selectM3uStreamFn,
15767
16532
  selectM3uAssociatedPlaylistsFn,
15768
- mp4HeaderSegment,
16533
+ m3uPlaylistContext,
15769
16534
  contentType,
15770
16535
  name,
15771
16536
  callbacks,
15772
16537
  fieldsInReturnValue,
15773
16538
  mimeType,
15774
16539
  initialReaderInstance,
15775
- makeSamplesStartAtZero
16540
+ makeSamplesStartAtZero,
16541
+ prefetchCache
15776
16542
  }) => {
15777
16543
  let skippedBytes = 0;
15778
16544
  const returnValue = {};
@@ -15804,22 +16570,35 @@ var makeParserState = ({
15804
16570
  callbacks
15805
16571
  });
15806
16572
  return {
15807
- riff: riffSpecificState({ controller, logLevel, readerInterface, src }),
16573
+ riff: riffSpecificState({
16574
+ controller,
16575
+ logLevel,
16576
+ readerInterface,
16577
+ src,
16578
+ prefetchCache
16579
+ }),
15808
16580
  transportStream: transportStreamState(),
15809
- webm: webmState({ controller, logLevel, readerInterface, src }),
16581
+ webm: webmState({
16582
+ controller,
16583
+ logLevel,
16584
+ readerInterface,
16585
+ src,
16586
+ prefetchCache
16587
+ }),
15810
16588
  iso: isoBaseMediaState({
15811
16589
  contentLength,
15812
16590
  controller,
15813
16591
  readerInterface,
15814
16592
  src,
15815
- logLevel
16593
+ logLevel,
16594
+ prefetchCache
15816
16595
  }),
15817
16596
  mp3,
15818
16597
  aac: aacState(),
15819
16598
  flac: flacState(),
15820
16599
  m3u: m3uState(logLevel),
15821
16600
  timings,
15822
- callbacks: sampleCallback({
16601
+ callbacks: callbacksState({
15823
16602
  controller,
15824
16603
  hasAudioTrackHandlers,
15825
16604
  hasVideoTrackHandlers,
@@ -15857,7 +16636,7 @@ var makeParserState = ({
15857
16636
  discardReadBytes,
15858
16637
  selectM3uStreamFn,
15859
16638
  selectM3uAssociatedPlaylistsFn,
15860
- mp4HeaderSegment,
16639
+ m3uPlaylistContext,
15861
16640
  contentType,
15862
16641
  name,
15863
16642
  returnValue,
@@ -15867,7 +16646,8 @@ var makeParserState = ({
15867
16646
  errored,
15868
16647
  currentReader: currentReaderState,
15869
16648
  seekInfiniteLoop,
15870
- makeSamplesStartAtZero
16649
+ makeSamplesStartAtZero,
16650
+ prefetchCache
15871
16651
  };
15872
16652
  };
15873
16653
 
@@ -15946,7 +16726,7 @@ var internalParseMedia = async function({
15946
16726
  apiName,
15947
16727
  selectM3uStream: selectM3uStreamFn,
15948
16728
  selectM3uAssociatedPlaylists: selectM3uAssociatedPlaylistsFn,
15949
- mp4HeaderSegment,
16729
+ m3uPlaylistContext,
15950
16730
  makeSamplesStartAtZero,
15951
16731
  seekingHints,
15952
16732
  ...more
@@ -15958,6 +16738,7 @@ var internalParseMedia = async function({
15958
16738
  apiName
15959
16739
  });
15960
16740
  Log.verbose(logLevel, `Reading ${typeof src === "string" ? src : src instanceof URL ? src.toString() : src instanceof File ? src.name : src.toString()}`);
16741
+ const prefetchCache = new Map;
15961
16742
  const {
15962
16743
  reader: readerInstance,
15963
16744
  contentLength,
@@ -15965,7 +16746,13 @@ var internalParseMedia = async function({
15965
16746
  contentType,
15966
16747
  supportsContentRange,
15967
16748
  needsContentRange
15968
- } = await readerInterface.read({ src, range: null, controller });
16749
+ } = await readerInterface.read({
16750
+ src,
16751
+ range: null,
16752
+ controller,
16753
+ logLevel,
16754
+ prefetchCache
16755
+ });
15969
16756
  if (contentLength === null) {
15970
16757
  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.`);
15971
16758
  }
@@ -15988,14 +16775,15 @@ var internalParseMedia = async function({
15988
16775
  onDiscardedData,
15989
16776
  selectM3uStreamFn,
15990
16777
  selectM3uAssociatedPlaylistsFn,
15991
- mp4HeaderSegment,
16778
+ m3uPlaylistContext,
15992
16779
  contentType,
15993
16780
  name,
15994
16781
  callbacks: more,
15995
16782
  fieldsInReturnValue: _fieldsInReturnValue ?? {},
15996
16783
  mimeType: contentType,
15997
16784
  initialReaderInstance: readerInstance,
15998
- makeSamplesStartAtZero
16785
+ makeSamplesStartAtZero,
16786
+ prefetchCache
15999
16787
  });
16000
16788
  if (seekingHints) {
16001
16789
  setSeekingHints({ hints: seekingHints, state });
@@ -16005,7 +16793,7 @@ var internalParseMedia = async function({
16005
16793
  keyframesState: state.keyframes,
16006
16794
  webmState: state.webm,
16007
16795
  structureState: state.structure,
16008
- mp4HeaderSegment: state.mp4HeaderSegment,
16796
+ m3uPlaylistContext: state.m3uPlaylistContext,
16009
16797
  mediaSectionState: state.mediaSection,
16010
16798
  isoState: state.iso,
16011
16799
  transportStream: state.transportStream,
@@ -16034,6 +16822,7 @@ var internalParseMedia = async function({
16034
16822
  state.iterator?.destroy();
16035
16823
  state.callbacks.tracks.ensureHasTracksAtEnd(state.fields);
16036
16824
  state.m3u.abortM3UStreamRuns();
16825
+ prefetchCache.clear();
16037
16826
  if (state.errored) {
16038
16827
  throw state.errored;
16039
16828
  }
@@ -16051,7 +16840,7 @@ var forwardMediaParserControllerToWorker = (controller) => {
16051
16840
  return;
16052
16841
  }
16053
16842
  if (message.type === "request-seek") {
16054
- controller._experimentalSeek(message.payload);
16843
+ controller.seek(message.payload);
16055
16844
  return;
16056
16845
  }
16057
16846
  if (message.type === "request-get-seeking-hints") {
@@ -16132,6 +16921,22 @@ var serializeError = ({
16132
16921
  seekingHints
16133
16922
  };
16134
16923
  }
16924
+ if (error.name === "AbortError") {
16925
+ return {
16926
+ type: "response-error",
16927
+ errorName: "AbortError",
16928
+ errorMessage: error.message,
16929
+ errorStack: error.stack ?? ""
16930
+ };
16931
+ }
16932
+ if (error.name === "NotReadableError") {
16933
+ return {
16934
+ type: "response-error",
16935
+ errorName: "NotReadableError",
16936
+ errorMessage: error.message,
16937
+ errorStack: error.stack ?? ""
16938
+ };
16939
+ }
16135
16940
  if (error.name !== "Error") {
16136
16941
  Log.warn(logLevel, `Original error was of type ${error.name} did not properly propagate`);
16137
16942
  }
@@ -16181,7 +16986,7 @@ var startParsing = async (message, reader) => {
16181
16986
  acknowledgeRemotionLicense,
16182
16987
  logLevel: userLogLevel,
16183
16988
  progressIntervalInMs,
16184
- mp4HeaderSegment,
16989
+ m3uPlaylistContext,
16185
16990
  seekingHints,
16186
16991
  makeSamplesStartAtZero
16187
16992
  } = payload;
@@ -16417,7 +17222,7 @@ var startParsing = async (message, reader) => {
16417
17222
  }
16418
17223
  return res.value;
16419
17224
  } : defaultSelectM3uStreamFn,
16420
- mp4HeaderSegment: mp4HeaderSegment ?? null,
17225
+ m3uPlaylistContext: m3uPlaylistContext ?? null,
16421
17226
  selectM3uAssociatedPlaylists: postM3uAssociatedPlaylistsSelection ? async (playlists) => {
16422
17227
  const res = await executeCallback({
16423
17228
  callbackType: "m3u-associated-playlists-selection",