@remotion/media-parser 4.0.268 → 4.0.270

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/containers/flac/get-channel-count.d.ts +1 -1
  2. package/dist/containers/iso-base-media/get-moov-atom.js +1 -0
  3. package/dist/containers/m3u/after-manifest-fetch.d.ts +5 -7
  4. package/dist/containers/m3u/after-manifest-fetch.js +29 -43
  5. package/dist/containers/m3u/fetch-m3u8-stream.d.ts +1 -2
  6. package/dist/containers/m3u/fetch-m3u8-stream.js +4 -4
  7. package/dist/containers/m3u/get-duration-from-m3u.d.ts +2 -2
  8. package/dist/containers/m3u/get-duration-from-m3u.js +8 -3
  9. package/dist/containers/m3u/get-playlist.d.ts +6 -1
  10. package/dist/containers/m3u/get-playlist.js +18 -9
  11. package/dist/containers/m3u/get-streams.d.ts +3 -2
  12. package/dist/containers/m3u/get-streams.js +10 -3
  13. package/dist/containers/m3u/m3u-child-stream.d.ts +0 -0
  14. package/dist/containers/m3u/m3u-child-stream.js +1 -0
  15. package/dist/containers/m3u/parse-directive.js +28 -1
  16. package/dist/containers/m3u/parse-m3u-manifest.js +1 -1
  17. package/dist/containers/m3u/parse-m3u.js +8 -1
  18. package/dist/containers/m3u/return-packets.d.ts +3 -2
  19. package/dist/containers/m3u/return-packets.js +83 -54
  20. package/dist/containers/m3u/run-over-m3u.d.ts +9 -0
  21. package/dist/containers/m3u/run-over-m3u.js +88 -0
  22. package/dist/containers/m3u/sample-sorter.d.ts +13 -0
  23. package/dist/containers/m3u/sample-sorter.js +64 -0
  24. package/dist/containers/m3u/select-stream.d.ts +9 -1
  25. package/dist/containers/m3u/select-stream.js +21 -1
  26. package/dist/containers/m3u/types.d.ts +15 -1
  27. package/dist/containers/wav/parse-list.js +9 -1
  28. package/dist/download-and-parse-media.js +31 -30
  29. package/dist/esm/index.mjs +542 -212
  30. package/dist/get-duration.js +1 -1
  31. package/dist/index.d.ts +30 -26
  32. package/dist/index.js +2 -1
  33. package/dist/internal-parse-media.js +4 -2
  34. package/dist/options.d.ts +2 -1
  35. package/dist/parse-media.js +2 -1
  36. package/dist/state/m3u-state.d.ts +34 -16
  37. package/dist/state/m3u-state.js +89 -25
  38. package/dist/state/parser-state.d.ts +30 -26
  39. package/dist/state/parser-state.js +3 -2
  40. package/dist/throttled-progress.js +20 -9
  41. package/dist/version.d.ts +1 -1
  42. package/dist/version.js +1 -1
  43. package/package.json +3 -3
@@ -5824,20 +5824,21 @@ var getM3uStreams = (structure, originalSrc) => {
5824
5824
  if (next.type !== "m3u-text-value") {
5825
5825
  throw new Error("Expected m3u-text-value");
5826
5826
  }
5827
- const dedicatedAudioTracks = [];
5827
+ const associatedPlaylists = [];
5828
5828
  if (str.audio) {
5829
5829
  const match = structure.boxes.filter((box) => {
5830
5830
  return box.type === "m3u-media-info" && box.groupId === str.audio;
5831
5831
  });
5832
5832
  for (const audioTrack of match) {
5833
- dedicatedAudioTracks.push({
5833
+ associatedPlaylists.push({
5834
5834
  autoselect: audioTrack.autoselect,
5835
5835
  channels: audioTrack.channels,
5836
5836
  default: audioTrack.default,
5837
5837
  groupId: audioTrack.groupId,
5838
5838
  language: audioTrack.language,
5839
5839
  name: audioTrack.name,
5840
- url: originalSrc && originalSrc.startsWith("http") ? new URL(audioTrack.uri, originalSrc).href : audioTrack.uri
5840
+ url: originalSrc && originalSrc.startsWith("http") ? new URL(audioTrack.uri, originalSrc).href : audioTrack.uri,
5841
+ id: associatedPlaylists.length
5841
5842
  });
5842
5843
  }
5843
5844
  }
@@ -5847,7 +5848,7 @@ var getM3uStreams = (structure, originalSrc) => {
5847
5848
  bandwidth: str.bandwidth,
5848
5849
  codecs: str.codecs,
5849
5850
  resolution: str.resolution,
5850
- dedicatedAudioTracks
5851
+ associatedPlaylists
5851
5852
  });
5852
5853
  }
5853
5854
  }
@@ -5857,6 +5858,11 @@ var getM3uStreams = (structure, originalSrc) => {
5857
5858
  const sorted = boxes.slice().sort((a, b) => {
5858
5859
  const aResolution = a.resolution ? a.resolution.width * a.resolution.height : 0;
5859
5860
  const bResolution = b.resolution ? b.resolution.width * b.resolution.height : 0;
5861
+ if (aResolution === bResolution) {
5862
+ const bandwidthA = a.averageBandwidth ?? a.bandwidth ?? 0;
5863
+ const bandwidthB = b.averageBandwidth ?? b.bandwidth ?? 0;
5864
+ return bandwidthB - bandwidthA;
5865
+ }
5860
5866
  return bResolution - aResolution;
5861
5867
  });
5862
5868
  return sorted.map((box, index) => ({ ...box, id: index }));
@@ -6171,19 +6177,30 @@ var getSamplePositionsFromTrack = ({
6171
6177
  };
6172
6178
 
6173
6179
  // src/containers/m3u/get-playlist.ts
6174
- var getPlaylist = (structure) => {
6180
+ var getAllPlaylists = ({
6181
+ structure,
6182
+ src
6183
+ }) => {
6175
6184
  const isIndependent = isIndependentSegments(structure);
6176
6185
  if (!isIndependent) {
6177
- return {
6178
- type: "m3u-playlist",
6179
- boxes: structure.boxes
6180
- };
6186
+ return [
6187
+ {
6188
+ type: "m3u-playlist",
6189
+ boxes: structure.boxes,
6190
+ src
6191
+ }
6192
+ ];
6181
6193
  }
6182
6194
  const playlists = structure.boxes.filter((box) => box.type === "m3u-playlist");
6183
- if (playlists.length !== 1) {
6184
- throw new Error("Expected one playlist");
6195
+ return playlists;
6196
+ };
6197
+ var getPlaylist = (structure, src) => {
6198
+ const allPlaylists = getAllPlaylists({ structure, src });
6199
+ const playlists = allPlaylists.find((box) => box.src === src);
6200
+ if (!playlists) {
6201
+ throw new Error(`Expected m3u-playlist with src ${src}`);
6185
6202
  }
6186
- return playlists[0];
6203
+ return playlists;
6187
6204
  };
6188
6205
  var getDurationFromPlaylist = (playlist) => {
6189
6206
  const duration2 = playlist.boxes.filter((box) => box.type === "m3u-extinf");
@@ -6194,9 +6211,14 @@ var getDurationFromPlaylist = (playlist) => {
6194
6211
  };
6195
6212
 
6196
6213
  // src/containers/m3u/get-duration-from-m3u.ts
6197
- var getDurationFromM3u = (structure) => {
6198
- const playlist = getPlaylist(structure);
6199
- return getDurationFromPlaylist(playlist);
6214
+ var getDurationFromM3u = (state) => {
6215
+ const playlists = getAllPlaylists({
6216
+ structure: state.getM3uStructure(),
6217
+ src: state.src
6218
+ });
6219
+ return Math.max(...playlists.map((p) => {
6220
+ return getDurationFromPlaylist(p);
6221
+ }));
6200
6222
  };
6201
6223
 
6202
6224
  // src/containers/mp3/get-frame-length.ts
@@ -6435,7 +6457,7 @@ var getDuration = (parserState) => {
6435
6457
  return getDurationFromFlac(parserState);
6436
6458
  }
6437
6459
  if (structure.type === "m3u") {
6438
- return getDurationFromM3u(parserState.getM3uStructure());
6460
+ return getDurationFromM3u(parserState);
6439
6461
  }
6440
6462
  throw new Error("Has no duration " + structure);
6441
6463
  };
@@ -8300,49 +8322,171 @@ var eventLoopState = (logLevel) => {
8300
8322
  return { eventLoopBreakIfNeeded };
8301
8323
  };
8302
8324
 
8325
+ // src/containers/m3u/sample-sorter.ts
8326
+ var sampleSorter = ({
8327
+ logLevel,
8328
+ getAllChunksProcessedForPlaylist
8329
+ }) => {
8330
+ const streamsWithTracks = [];
8331
+ const audioCallbacks = {};
8332
+ const videoCallbacks = {};
8333
+ const latestSample = {};
8334
+ return {
8335
+ addToStreamWithTrack: (src) => {
8336
+ streamsWithTracks.push(src);
8337
+ },
8338
+ addVideoStreamToConsider: (src, callback) => {
8339
+ videoCallbacks[src] = callback;
8340
+ },
8341
+ addAudioStreamToConsider: (src, callback) => {
8342
+ audioCallbacks[src] = callback;
8343
+ },
8344
+ addAudioSample: async (src, sample) => {
8345
+ const callback = audioCallbacks[src];
8346
+ if (!callback) {
8347
+ throw new Error("No callback found for audio sample");
8348
+ }
8349
+ latestSample[src] = sample.dts;
8350
+ await callback(sample);
8351
+ },
8352
+ addVideoSample: async (src, sample) => {
8353
+ const callback = videoCallbacks[src];
8354
+ if (!callback) {
8355
+ throw new Error("No callback found for audio sample");
8356
+ }
8357
+ latestSample[src] = sample.dts;
8358
+ await callback(sample);
8359
+ },
8360
+ getNextStreamToRun: (streams) => {
8361
+ for (const stream of streams) {
8362
+ if (!streamsWithTracks.includes(stream)) {
8363
+ Log.trace(logLevel, `Did not yet detect track of ${stream}, working on that`);
8364
+ return stream;
8365
+ }
8366
+ }
8367
+ let smallestDts = Infinity;
8368
+ for (const stream of streams) {
8369
+ if (getAllChunksProcessedForPlaylist(stream)) {
8370
+ continue;
8371
+ }
8372
+ if ((latestSample[stream] ?? 0) < smallestDts) {
8373
+ smallestDts = latestSample[stream] ?? 0;
8374
+ }
8375
+ }
8376
+ for (const stream of streams) {
8377
+ if ((latestSample[stream] ?? 0) === smallestDts) {
8378
+ Log.trace(logLevel, `Working on ${stream} because it has the smallest DTS`);
8379
+ return stream;
8380
+ }
8381
+ }
8382
+ throw new Error("should be done with parsing now");
8383
+ }
8384
+ };
8385
+ };
8386
+
8303
8387
  // src/state/m3u-state.ts
8304
- var m3uState = () => {
8305
- let selectedStream = null;
8306
- let hasEmittedVideoTrack = false;
8307
- let hasEmittedAudioTrack = false;
8308
- let hasEmittedDoneWithTracks = false;
8388
+ var m3uState = (logLevel) => {
8389
+ let selectedMainPlaylist = null;
8390
+ let associatedPlaylists = null;
8391
+ const hasEmittedVideoTrack = {};
8392
+ const hasEmittedAudioTrack = {};
8393
+ const hasEmittedDoneWithTracks = {};
8309
8394
  let hasFinishedManifest = false;
8310
8395
  let readyToIterateOverM3u = false;
8311
- let lastChunkProcessed = -1;
8312
- let allChunksProcessed = false;
8396
+ const allChunksProcessed = {};
8397
+ const m3uStreamRuns = {};
8398
+ const tracksDone = {};
8399
+ const getMainPlaylistUrl = () => {
8400
+ if (!selectedMainPlaylist) {
8401
+ throw new Error("No main playlist selected");
8402
+ }
8403
+ const playlistUrl = selectedMainPlaylist.type === "initial-url" ? selectedMainPlaylist.url : selectedMainPlaylist.stream.url;
8404
+ return playlistUrl;
8405
+ };
8406
+ const getSelectedPlaylists = () => {
8407
+ return [
8408
+ getMainPlaylistUrl(),
8409
+ ...(associatedPlaylists ?? []).map((p) => p.url)
8410
+ ];
8411
+ };
8412
+ const getAllChunksProcessedForPlaylist = (src) => allChunksProcessed[src];
8313
8413
  return {
8314
- setSelectedStream: (stream) => {
8315
- selectedStream = stream;
8414
+ setSelectedMainPlaylist: (stream) => {
8415
+ selectedMainPlaylist = stream;
8416
+ },
8417
+ getSelectedMainPlaylist: () => selectedMainPlaylist,
8418
+ setHasEmittedVideoTrack: (src, callback) => {
8419
+ hasEmittedVideoTrack[src] = callback;
8420
+ },
8421
+ hasEmittedVideoTrack: (src) => {
8422
+ const value = hasEmittedVideoTrack[src];
8423
+ if (value === undefined) {
8424
+ return false;
8425
+ }
8426
+ return value;
8316
8427
  },
8317
- getSelectedStream: () => selectedStream,
8318
- setHasEmittedVideoTrack: (callback) => {
8319
- hasEmittedVideoTrack = callback;
8428
+ setHasEmittedAudioTrack: (src, callback) => {
8429
+ hasEmittedAudioTrack[src] = callback;
8320
8430
  },
8321
- hasEmittedVideoTrack: () => hasEmittedVideoTrack,
8322
- setHasEmittedAudioTrack: (callback) => {
8323
- hasEmittedAudioTrack = callback;
8431
+ hasEmittedAudioTrack: (src) => {
8432
+ const value = hasEmittedAudioTrack[src];
8433
+ if (value === undefined) {
8434
+ return false;
8435
+ }
8436
+ return value;
8324
8437
  },
8325
- hasEmittedAudioTrack: () => hasEmittedAudioTrack,
8326
- setHasEmittedDoneWithTracks: () => {
8327
- hasEmittedDoneWithTracks = true;
8438
+ setHasEmittedDoneWithTracks: (src) => {
8439
+ hasEmittedDoneWithTracks[src] = true;
8328
8440
  },
8329
- hasEmittedDoneWithTracks: () => hasEmittedDoneWithTracks,
8441
+ hasEmittedDoneWithTracks: (src) => hasEmittedDoneWithTracks[src],
8330
8442
  setReadyToIterateOverM3u: () => {
8331
8443
  readyToIterateOverM3u = true;
8332
8444
  },
8333
8445
  isReadyToIterateOverM3u: () => readyToIterateOverM3u,
8334
- setLastChunkProcessed: (chunk) => {
8335
- lastChunkProcessed = chunk;
8446
+ setAllChunksProcessed: (src) => {
8447
+ allChunksProcessed[src] = true;
8336
8448
  },
8337
- getLastChunkProcessed: () => lastChunkProcessed,
8338
- getAllChunksProcessed: () => allChunksProcessed,
8339
- setAllChunksProcessed: () => {
8340
- allChunksProcessed = true;
8449
+ getAllChunksProcessedForPlaylist,
8450
+ getAllChunksProcessedOverall: () => {
8451
+ if (!selectedMainPlaylist) {
8452
+ return false;
8453
+ }
8454
+ const selectedPlaylists = getSelectedPlaylists();
8455
+ return selectedPlaylists.every((url) => allChunksProcessed[url]);
8341
8456
  },
8342
8457
  setHasFinishedManifest: () => {
8343
8458
  hasFinishedManifest = true;
8344
8459
  },
8345
- hasFinishedManifest: () => hasFinishedManifest
8460
+ hasFinishedManifest: () => hasFinishedManifest,
8461
+ setM3uStreamRun: (playlistUrl, run) => {
8462
+ if (!run) {
8463
+ delete m3uStreamRuns[playlistUrl];
8464
+ return;
8465
+ }
8466
+ m3uStreamRuns[playlistUrl] = run;
8467
+ },
8468
+ setTracksDone: (playlistUrl) => {
8469
+ tracksDone[playlistUrl] = true;
8470
+ const selectedPlaylists = getSelectedPlaylists();
8471
+ return selectedPlaylists.every((url) => tracksDone[url]);
8472
+ },
8473
+ getM3uStreamRun: (playlistUrl) => m3uStreamRuns[playlistUrl] ?? null,
8474
+ abortM3UStreamRuns: () => {
8475
+ const values = Object.values(m3uStreamRuns);
8476
+ if (values.length === 0) {
8477
+ return;
8478
+ }
8479
+ Log.trace(logLevel, `Aborting ${values.length} M3U stream runs`);
8480
+ values.forEach((run) => {
8481
+ run.abort();
8482
+ });
8483
+ },
8484
+ setAssociatedPlaylists: (playlists) => {
8485
+ associatedPlaylists = playlists;
8486
+ },
8487
+ getAssociatedPlaylists: () => associatedPlaylists,
8488
+ getSelectedPlaylists,
8489
+ sampleSorter: sampleSorter({ logLevel, getAllChunksProcessedForPlaylist })
8346
8490
  };
8347
8491
  };
8348
8492
 
@@ -8890,7 +9034,8 @@ var makeParserState = ({
8890
9034
  src,
8891
9035
  readerInterface,
8892
9036
  onDiscardedData,
8893
- selectM3uStreamFn
9037
+ selectM3uStreamFn,
9038
+ selectM3uAssociatedPlaylistsFn
8894
9039
  }) => {
8895
9040
  let skippedBytes = 0;
8896
9041
  const iterator = getArrayBufferIterator(new Uint8Array([]), contentLength);
@@ -8920,7 +9065,7 @@ var makeParserState = ({
8920
9065
  mp3Info,
8921
9066
  aac: aacState(),
8922
9067
  flac: flacState(),
8923
- m3u: m3uState(),
9068
+ m3u: m3uState(logLevel),
8924
9069
  callbacks: sampleCallback({
8925
9070
  controller,
8926
9071
  hasAudioTrackHandlers,
@@ -8955,7 +9100,8 @@ var makeParserState = ({
8955
9100
  src,
8956
9101
  readerInterface,
8957
9102
  discardReadBytes,
8958
- selectM3uStreamFn
9103
+ selectM3uStreamFn,
9104
+ selectM3uAssociatedPlaylistsFn
8959
9105
  };
8960
9106
  };
8961
9107
 
@@ -8992,7 +9138,8 @@ var getMoovAtom = async ({
8992
9138
  readerInterface: state.readerInterface,
8993
9139
  src: state.src,
8994
9140
  onDiscardedData: null,
8995
- selectM3uStreamFn: state.selectM3uStreamFn
9141
+ selectM3uStreamFn: state.selectM3uStreamFn,
9142
+ selectM3uAssociatedPlaylistsFn: state.selectM3uAssociatedPlaylistsFn
8996
9143
  });
8997
9144
  while (true) {
8998
9145
  const result = await reader.reader.read();
@@ -9189,7 +9336,7 @@ var parseM3uMediaDirective = (str) => {
9189
9336
  // src/containers/m3u/parse-directive.ts
9190
9337
  var parseM3uDirective = (str) => {
9191
9338
  const firstColon = str.indexOf(":");
9192
- const directive = firstColon === -1 ? str : str.slice(0, firstColon);
9339
+ const directive = (firstColon === -1 ? str : str.slice(0, firstColon)).trim();
9193
9340
  const value = firstColon === -1 ? null : str.slice(firstColon + 1);
9194
9341
  if (directive === "#EXT-X-VERSION") {
9195
9342
  if (!value) {
@@ -9244,6 +9391,24 @@ var parseM3uDirective = (str) => {
9244
9391
  playlistType: value
9245
9392
  };
9246
9393
  }
9394
+ if (directive === "#EXT-X-MEDIA-SEQUENCE") {
9395
+ if (!value) {
9396
+ throw new Error("#EXT-X-MEDIA-SEQUENCE directive must have a value");
9397
+ }
9398
+ return {
9399
+ type: "m3u-media-sequence",
9400
+ value: Number(value)
9401
+ };
9402
+ }
9403
+ if (directive === "#EXT-X-DISCONTINUITY-SEQUENCE") {
9404
+ if (!value) {
9405
+ throw new Error("#EXT-X-DISCONTINUITY-SEQUENCE directive must have a value");
9406
+ }
9407
+ return {
9408
+ type: "m3u-discontinuity-sequence",
9409
+ value: Number(value)
9410
+ };
9411
+ }
9247
9412
  if (directive === "#EXT-X-STREAM-INF") {
9248
9413
  if (!value) {
9249
9414
  throw new Error("EXT-X-STREAM-INF directive must have a value");
@@ -9251,6 +9416,15 @@ var parseM3uDirective = (str) => {
9251
9416
  const res = parseStreamInf(value);
9252
9417
  return res;
9253
9418
  }
9419
+ if (directive === "#EXT-X-MAP") {
9420
+ if (!value) {
9421
+ throw new Error("#EXT-X-MAP directive must have a value");
9422
+ }
9423
+ return {
9424
+ type: "m3u-map",
9425
+ value: Number(value)
9426
+ };
9427
+ }
9254
9428
  throw new Error(`Unknown directive ${directive}. Value: ${value}`);
9255
9429
  };
9256
9430
 
@@ -9275,21 +9449,134 @@ var parseM3u8Text = (line, boxes) => {
9275
9449
  };
9276
9450
 
9277
9451
  // src/containers/m3u/fetch-m3u8-stream.ts
9278
- var fetchM3u8Stream = async (stream) => {
9279
- const res = await fetch(stream.url);
9452
+ var fetchM3u8Stream = async (url) => {
9453
+ const res = await fetch(url);
9280
9454
  if (!res.ok) {
9281
- throw new Error(`Failed to fetch ${stream.url} (HTTP code: ${res.status})`);
9455
+ throw new Error(`Failed to fetch ${url} (HTTP code: ${res.status})`);
9282
9456
  }
9283
9457
  const text = await res.text();
9284
9458
  const lines = text.split(`
9285
9459
  `);
9286
9460
  const boxes = [];
9287
9461
  for (const line of lines) {
9288
- parseM3u8Text(line, boxes);
9462
+ parseM3u8Text(line.trim(), boxes);
9289
9463
  }
9290
9464
  return boxes;
9291
9465
  };
9292
9466
 
9467
+ // src/containers/m3u/select-stream.ts
9468
+ var selectAssociatedPlaylists = async ({
9469
+ playlists,
9470
+ fn
9471
+ }) => {
9472
+ if (playlists.length < 1) {
9473
+ return Promise.resolve([]);
9474
+ }
9475
+ const streams = await fn({ associatedPlaylists: playlists });
9476
+ if (!Array.isArray(streams)) {
9477
+ throw new Error("Expected an array of associated playlists");
9478
+ }
9479
+ for (const stream of streams) {
9480
+ if (!playlists.find((playlist) => playlist.url === stream.url)) {
9481
+ throw new Error(`The associated playlist ${JSON.stringify(streams)} cannot be selected because it was not in the list of selectable playlists`);
9482
+ }
9483
+ }
9484
+ return streams;
9485
+ };
9486
+ var defaultSelectM3uAssociatedPlaylists = ({ associatedPlaylists }) => {
9487
+ return Promise.resolve(associatedPlaylists.filter((playlist) => playlist.default));
9488
+ };
9489
+ var selectStream = async ({
9490
+ streams,
9491
+ fn
9492
+ }) => {
9493
+ if (streams.length < 1) {
9494
+ throw new Error("No streams found");
9495
+ }
9496
+ const selectedStreamId = await fn({ streams });
9497
+ const selectedStream = streams.find((stream) => stream.id === selectedStreamId);
9498
+ if (!selectedStream) {
9499
+ throw new Error(`No stream with the id ${selectedStreamId} found`);
9500
+ }
9501
+ return Promise.resolve(selectedStream);
9502
+ };
9503
+ var defaultSelectM3uStreamFn = ({ streams }) => {
9504
+ return Promise.resolve(streams[0].id);
9505
+ };
9506
+
9507
+ // src/containers/m3u/after-manifest-fetch.ts
9508
+ var afterManifestFetch = async ({
9509
+ structure,
9510
+ m3uState: m3uState2,
9511
+ src,
9512
+ selectM3uStreamFn,
9513
+ logLevel,
9514
+ selectAssociatedPlaylists: selectAssociatedPlaylistsFn
9515
+ }) => {
9516
+ const independentSegments = isIndependentSegments(structure);
9517
+ if (!independentSegments) {
9518
+ if (!src) {
9519
+ throw new Error("No src");
9520
+ }
9521
+ m3uState2.setSelectedMainPlaylist({
9522
+ type: "initial-url",
9523
+ url: src
9524
+ });
9525
+ return m3uState2.setReadyToIterateOverM3u();
9526
+ }
9527
+ const streams = getM3uStreams(structure, src);
9528
+ if (streams === null) {
9529
+ throw new Error("No streams found");
9530
+ }
9531
+ const selectedPlaylist = await selectStream({ streams, fn: selectM3uStreamFn });
9532
+ if (!selectedPlaylist.resolution) {
9533
+ throw new Error("Stream does not have a resolution");
9534
+ }
9535
+ m3uState2.setSelectedMainPlaylist({
9536
+ type: "selected-stream",
9537
+ stream: selectedPlaylist
9538
+ });
9539
+ const associatedPlaylists = await selectAssociatedPlaylists({
9540
+ playlists: selectedPlaylist.associatedPlaylists,
9541
+ fn: selectAssociatedPlaylistsFn
9542
+ });
9543
+ m3uState2.setAssociatedPlaylists(associatedPlaylists);
9544
+ const playlistUrls = [
9545
+ selectedPlaylist.url,
9546
+ ...associatedPlaylists.map((p) => p.url)
9547
+ ];
9548
+ const struc = await Promise.all(playlistUrls.map(async (url) => {
9549
+ Log.verbose(logLevel, `Fetching playlist ${url}`);
9550
+ const boxes = await fetchM3u8Stream(url);
9551
+ return {
9552
+ type: "m3u-playlist",
9553
+ boxes,
9554
+ src: url
9555
+ };
9556
+ }));
9557
+ structure.boxes.push(...struc);
9558
+ m3uState2.setReadyToIterateOverM3u();
9559
+ };
9560
+
9561
+ // src/containers/m3u/parse-m3u-manifest.ts
9562
+ var parseM3uManifest = ({
9563
+ iterator,
9564
+ structure,
9565
+ contentLength
9566
+ }) => {
9567
+ const start = iterator.startCheckpoint();
9568
+ const line = iterator.readUntilLineEnd();
9569
+ if (iterator.counter.getOffset() > contentLength) {
9570
+ throw new Error("Unexpected end of file");
9571
+ }
9572
+ if (line === null) {
9573
+ start.returnToCheckpoint();
9574
+ return Promise.resolve(null);
9575
+ }
9576
+ parseM3u8Text(line.trim(), structure.boxes);
9577
+ return Promise.resolve(null);
9578
+ };
9579
+
9293
9580
  // src/forward-controller.ts
9294
9581
  var forwardMediaParserController = ({
9295
9582
  parentController,
@@ -9316,25 +9603,6 @@ var forwardMediaParserController = ({
9316
9603
  };
9317
9604
  };
9318
9605
 
9319
- // src/containers/m3u/select-stream.ts
9320
- var selectStream = async ({
9321
- streams,
9322
- fn
9323
- }) => {
9324
- if (streams.length < 1) {
9325
- throw new Error("No streams found");
9326
- }
9327
- const selectedStreamId = await fn({ streams });
9328
- const selectedStream = streams.find((stream) => stream.id === selectedStreamId);
9329
- if (!selectedStream) {
9330
- throw new Error(`No stream with the id ${selectedStreamId} found`);
9331
- }
9332
- return Promise.resolve(selectedStream);
9333
- };
9334
- var defaultSelectM3uStreamFn = ({ streams }) => {
9335
- return Promise.resolve(streams[0].id);
9336
- };
9337
-
9338
9606
  // src/readers/fetch/get-body-and-reader.ts
9339
9607
  var getLengthAndReader = async ({
9340
9608
  canLiveWithoutContentLength,
@@ -9531,6 +9799,7 @@ var parseMedia = (options) => {
9531
9799
  reader: options.reader ?? fetchReader,
9532
9800
  controller: options.controller ?? undefined,
9533
9801
  selectM3uStream: options.selectM3uStream ?? defaultSelectM3uStreamFn,
9802
+ selectM3uAssociatedPlaylists: options.selectM3uAssociatedPlaylists ?? defaultSelectM3uAssociatedPlaylists,
9534
9803
  src: options.src,
9535
9804
  mode: "query",
9536
9805
  onDiscardedData: null,
@@ -9567,161 +9836,198 @@ var iteratorOverTsFiles = async ({
9567
9836
  onDoneWithTracks,
9568
9837
  playlistUrl,
9569
9838
  logLevel,
9570
- parentController
9839
+ parentController,
9840
+ onInitialProgress
9571
9841
  }) => {
9572
- const playlist = getPlaylist(structure);
9842
+ const playlist = getPlaylist(structure, playlistUrl);
9573
9843
  const chunks = getChunks(playlist);
9574
- const lastChunkProcessed = m3uState2.getLastChunkProcessed();
9575
- const chunkIndex = lastChunkProcessed + 1;
9576
- const chunk = chunks[chunkIndex];
9577
- const isLastChunk = chunkIndex === chunks.length - 1;
9578
- const src = new URL(chunk.url, playlistUrl).toString();
9844
+ let resolver = onInitialProgress;
9845
+ let rejector = (_e) => {
9846
+ };
9579
9847
  const childController = mediaParserController();
9580
9848
  const forwarded = forwardMediaParserController({
9581
9849
  childController,
9582
9850
  parentController
9583
9851
  });
9584
- await parseMedia({
9585
- src,
9586
- acknowledgeRemotionLicense: true,
9587
- logLevel,
9588
- controller: childController,
9589
- onTracks: () => {
9590
- if (!m3uState2.hasEmittedDoneWithTracks()) {
9591
- m3uState2.setHasEmittedDoneWithTracks();
9592
- onDoneWithTracks();
9593
- return null;
9594
- }
9595
- },
9596
- onAudioTrack: async ({ track }) => {
9597
- const callbackOrFalse = m3uState2.hasEmittedAudioTrack();
9598
- if (callbackOrFalse === false) {
9599
- const callback = await onAudioTrack(track);
9600
- if (!callback) {
9601
- m3uState2.setHasEmittedAudioTrack(null);
9602
- return null;
9603
- }
9604
- m3uState2.setHasEmittedAudioTrack(callback);
9605
- return (sample) => {
9606
- return callback(sample);
9607
- };
9852
+ const makeContinuationFn = () => {
9853
+ return {
9854
+ continue() {
9855
+ const { promise, reject, resolve } = Promise.withResolvers();
9856
+ resolver = resolve;
9857
+ rejector = reject;
9858
+ childController.resume();
9859
+ return promise;
9860
+ },
9861
+ abort() {
9862
+ childController.abort();
9608
9863
  }
9609
- return callbackOrFalse;
9610
- },
9611
- onVideoTrack: async ({ track }) => {
9612
- const callbackOrFalse = m3uState2.hasEmittedVideoTrack();
9613
- if (callbackOrFalse === false) {
9614
- const callback = await onVideoTrack(track);
9615
- if (!callback) {
9616
- m3uState2.setHasEmittedVideoTrack(null);
9617
- return null;
9864
+ };
9865
+ };
9866
+ for (const chunk of chunks) {
9867
+ const isLastChunk = chunk === chunks[chunks.length - 1];
9868
+ await childController._internals.checkForAbortAndPause();
9869
+ const src = new URL(chunk.url, playlistUrl).toString();
9870
+ try {
9871
+ await parseMedia({
9872
+ src,
9873
+ acknowledgeRemotionLicense: true,
9874
+ logLevel,
9875
+ controller: childController,
9876
+ progressIntervalInMs: 0,
9877
+ onParseProgress: () => {
9878
+ childController.pause();
9879
+ resolver(makeContinuationFn());
9880
+ },
9881
+ onTracks: () => {
9882
+ if (!m3uState2.hasEmittedDoneWithTracks(playlistUrl)) {
9883
+ m3uState2.setHasEmittedDoneWithTracks(playlistUrl);
9884
+ onDoneWithTracks();
9885
+ return null;
9886
+ }
9887
+ },
9888
+ onAudioTrack: async ({ track }) => {
9889
+ const callbackOrFalse = m3uState2.hasEmittedAudioTrack(playlistUrl);
9890
+ if (callbackOrFalse === false) {
9891
+ const callback = await onAudioTrack(track);
9892
+ if (!callback) {
9893
+ m3uState2.setHasEmittedAudioTrack(playlistUrl, null);
9894
+ return null;
9895
+ }
9896
+ m3uState2.setHasEmittedAudioTrack(playlistUrl, callback);
9897
+ return (sample) => {
9898
+ return callback(sample);
9899
+ };
9900
+ }
9901
+ return callbackOrFalse;
9902
+ },
9903
+ onVideoTrack: async ({ track }) => {
9904
+ const callbackOrFalse = m3uState2.hasEmittedVideoTrack(playlistUrl);
9905
+ if (callbackOrFalse === false) {
9906
+ const callback = await onVideoTrack(track);
9907
+ if (!callback) {
9908
+ m3uState2.setHasEmittedVideoTrack(playlistUrl, null);
9909
+ return null;
9910
+ }
9911
+ m3uState2.setHasEmittedVideoTrack(playlistUrl, callback);
9912
+ return (sample) => {
9913
+ return callback(sample);
9914
+ };
9915
+ }
9916
+ return callbackOrFalse;
9618
9917
  }
9619
- m3uState2.setHasEmittedVideoTrack(callback);
9620
- return (sample) => {
9621
- return callback(sample);
9622
- };
9623
- }
9624
- return callbackOrFalse;
9918
+ });
9919
+ } catch (e) {
9920
+ rejector(e);
9921
+ throw e;
9625
9922
  }
9626
- });
9627
- m3uState2.setLastChunkProcessed(chunkIndex);
9628
- if (isLastChunk) {
9629
- m3uState2.setAllChunksProcessed();
9630
- }
9631
- forwarded.cleanup();
9632
- };
9633
-
9634
- // src/containers/m3u/after-manifest-fetch.ts
9635
- var afterManifestFetch = async ({
9636
- structure,
9637
- m3uState: m3uState2,
9638
- src,
9639
- selectM3uStreamFn
9640
- }) => {
9641
- const independentSegments = isIndependentSegments(structure);
9642
- if (!independentSegments) {
9643
- if (!src) {
9644
- throw new Error("No src");
9923
+ forwarded.cleanup();
9924
+ if (!isLastChunk) {
9925
+ childController.pause();
9926
+ resolver(makeContinuationFn());
9645
9927
  }
9646
- m3uState2.setSelectedStream({
9647
- type: "initial-url",
9648
- url: src
9649
- });
9650
- return m3uState2.setReadyToIterateOverM3u();
9651
- }
9652
- const streams = getM3uStreams(structure, src);
9653
- if (streams === null) {
9654
- throw new Error("No streams found");
9655
- }
9656
- const selectedStream = await selectStream({ streams, fn: selectM3uStreamFn });
9657
- m3uState2.setSelectedStream({ type: "selected-stream", stream: selectedStream });
9658
- if (!selectedStream.resolution) {
9659
- throw new Error("Stream does not have a resolution");
9660
9928
  }
9661
- const boxes = await fetchM3u8Stream(selectedStream);
9662
- structure.boxes.push({ type: "m3u-playlist", boxes });
9663
- m3uState2.setReadyToIterateOverM3u();
9929
+ resolver(null);
9664
9930
  };
9931
+
9932
+ // src/containers/m3u/run-over-m3u.ts
9665
9933
  var runOverM3u = async ({
9666
9934
  state,
9667
- structure
9668
- }) => {
9669
- const selectedStream = state.m3u.getSelectedStream();
9670
- if (!selectedStream) {
9671
- throw new Error("No stream selected");
9672
- }
9673
- await iteratorOverTsFiles({
9674
- playlistUrl: selectedStream.type === "initial-url" ? selectedStream.url : selectedStream.stream.url,
9675
- structure,
9676
- logLevel: state.logLevel,
9677
- onDoneWithTracks() {
9678
- state.callbacks.tracks.setIsDone(state.logLevel);
9679
- },
9680
- onAudioTrack: (track) => {
9681
- return registerAudioTrack({
9682
- container: "m3u8",
9683
- state,
9684
- track
9685
- });
9686
- },
9687
- onVideoTrack: (track) => {
9688
- return registerVideoTrack({
9689
- container: "m3u8",
9690
- state,
9691
- track
9692
- });
9693
- },
9694
- m3uState: state.m3u,
9695
- parentController: state.controller
9696
- });
9697
- };
9698
-
9699
- // src/containers/m3u/parse-m3u-manifest.ts
9700
- var parseM3uManifest = ({
9701
- iterator,
9702
9935
  structure,
9703
- contentLength
9936
+ playlistUrl,
9937
+ logLevel
9704
9938
  }) => {
9705
- const start = iterator.startCheckpoint();
9706
- const line = iterator.readUntilLineEnd();
9707
- if (iterator.counter.getOffset() > contentLength) {
9708
- throw new Error("Unexpected end of file");
9709
- }
9710
- if (line === null) {
9711
- start.returnToCheckpoint();
9712
- return Promise.resolve(null);
9939
+ const existingRun = state.m3u.getM3uStreamRun(playlistUrl);
9940
+ if (existingRun) {
9941
+ Log.trace(logLevel, "Existing M3U parsing process found for", playlistUrl);
9942
+ const run = await existingRun.continue();
9943
+ state.m3u.setM3uStreamRun(playlistUrl, run);
9944
+ if (!run) {
9945
+ state.m3u.setAllChunksProcessed(playlistUrl);
9946
+ }
9947
+ return;
9713
9948
  }
9714
- parseM3u8Text(line, structure.boxes);
9715
- return Promise.resolve(null);
9949
+ Log.trace(logLevel, "Starting new M3U parsing process for", playlistUrl);
9950
+ return new Promise((resolve, reject) => {
9951
+ const run = iteratorOverTsFiles({
9952
+ playlistUrl,
9953
+ structure,
9954
+ onInitialProgress: (newRun) => {
9955
+ state.m3u.setM3uStreamRun(playlistUrl, newRun);
9956
+ resolve();
9957
+ },
9958
+ logLevel: state.logLevel,
9959
+ onDoneWithTracks() {
9960
+ const allDone = state.m3u.setTracksDone(playlistUrl);
9961
+ if (allDone) {
9962
+ state.callbacks.tracks.setIsDone(state.logLevel);
9963
+ }
9964
+ },
9965
+ onAudioTrack: async (track) => {
9966
+ const existingTracks = state.callbacks.tracks.getTracks();
9967
+ let { trackId } = track;
9968
+ while (existingTracks.find((t) => t.trackId === trackId)) {
9969
+ trackId++;
9970
+ }
9971
+ const onAudioSample = await registerAudioTrack({
9972
+ container: "m3u8",
9973
+ state,
9974
+ track: {
9975
+ ...track,
9976
+ trackId
9977
+ }
9978
+ });
9979
+ state.m3u.sampleSorter.addToStreamWithTrack(playlistUrl);
9980
+ if (onAudioSample === null) {
9981
+ return null;
9982
+ }
9983
+ state.m3u.sampleSorter.addAudioStreamToConsider(playlistUrl, onAudioSample);
9984
+ return async (sample) => {
9985
+ await state.m3u.sampleSorter.addAudioSample(playlistUrl, sample);
9986
+ };
9987
+ },
9988
+ onVideoTrack: async (track) => {
9989
+ const existingTracks = state.callbacks.tracks.getTracks();
9990
+ let { trackId } = track;
9991
+ while (existingTracks.find((t) => t.trackId === trackId)) {
9992
+ trackId++;
9993
+ }
9994
+ const onVideoSample = await registerVideoTrack({
9995
+ container: "m3u8",
9996
+ state,
9997
+ track: {
9998
+ ...track,
9999
+ trackId
10000
+ }
10001
+ });
10002
+ state.m3u.sampleSorter.addToStreamWithTrack(playlistUrl);
10003
+ if (onVideoSample === null) {
10004
+ return null;
10005
+ }
10006
+ state.m3u.sampleSorter.addVideoStreamToConsider(playlistUrl, onVideoSample);
10007
+ return async (sample) => {
10008
+ await state.m3u.sampleSorter.addVideoSample(playlistUrl, sample);
10009
+ };
10010
+ },
10011
+ m3uState: state.m3u,
10012
+ parentController: state.controller
10013
+ });
10014
+ run.catch((err) => {
10015
+ reject(err);
10016
+ });
10017
+ });
9716
10018
  };
9717
10019
 
9718
10020
  // src/containers/m3u/parse-m3u.ts
9719
10021
  var parseM3u = async ({ state }) => {
9720
10022
  const structure = state.getM3uStructure();
9721
10023
  if (state.m3u.isReadyToIterateOverM3u()) {
10024
+ const selectedPlaylists = state.m3u.getSelectedPlaylists();
10025
+ const whichPlaylistToRunOver = state.m3u.sampleSorter.getNextStreamToRun(selectedPlaylists);
9722
10026
  await runOverM3u({
9723
10027
  state,
9724
- structure
10028
+ structure,
10029
+ playlistUrl: whichPlaylistToRunOver,
10030
+ logLevel: state.logLevel
9725
10031
  });
9726
10032
  return null;
9727
10033
  }
@@ -9730,7 +10036,9 @@ var parseM3u = async ({ state }) => {
9730
10036
  structure,
9731
10037
  m3uState: state.m3u,
9732
10038
  src: typeof state.src === "string" ? state.src : null,
9733
- selectM3uStreamFn: state.selectM3uStreamFn
10039
+ selectM3uStreamFn: state.selectM3uStreamFn,
10040
+ logLevel: state.logLevel,
10041
+ selectAssociatedPlaylists: state.selectM3uAssociatedPlaylistsFn
9734
10042
  });
9735
10043
  return null;
9736
10044
  }
@@ -11411,7 +11719,12 @@ var parseList = ({
11411
11719
  throw new Error(`Only supporting LIST INFO, but got ${type}`);
11412
11720
  }
11413
11721
  const metadata = [];
11414
- while (iterator.counter.getOffset() < startOffset + ckSize) {
11722
+ const remainingBytes = () => ckSize - (iterator.counter.getOffset() - startOffset);
11723
+ while (remainingBytes() > 0) {
11724
+ if (remainingBytes() < 4) {
11725
+ iterator.discard(remainingBytes());
11726
+ break;
11727
+ }
11415
11728
  const key = iterator.getByteString(4, false);
11416
11729
  const size = iterator.getUint32Le();
11417
11730
  const value = iterator.getByteString(size, true);
@@ -11802,21 +12115,33 @@ var throttledStateUpdate = ({
11802
12115
  updateFn(currentState);
11803
12116
  lastUpdated = currentState;
11804
12117
  };
11805
- const interval = setInterval(() => {
11806
- callUpdateIfChanged();
11807
- }, everyMilliseconds);
11808
- const onAbort = () => {
11809
- clearInterval(interval);
12118
+ let cleanup = () => {
11810
12119
  };
11811
- controller._internals.signal.addEventListener("abort", onAbort, { once: true });
12120
+ if (everyMilliseconds > 0) {
12121
+ const interval = setInterval(() => {
12122
+ callUpdateIfChanged();
12123
+ }, everyMilliseconds);
12124
+ const onAbort = () => {
12125
+ clearInterval(interval);
12126
+ };
12127
+ controller._internals.signal.addEventListener("abort", onAbort, {
12128
+ once: true
12129
+ });
12130
+ cleanup = () => {
12131
+ clearInterval(interval);
12132
+ controller._internals.signal.removeEventListener("abort", onAbort);
12133
+ };
12134
+ }
11812
12135
  return {
11813
12136
  get: () => currentState,
11814
12137
  update: (fn) => {
11815
12138
  currentState = fn(currentState);
12139
+ if (everyMilliseconds === 0) {
12140
+ callUpdateIfChanged();
12141
+ }
11816
12142
  },
11817
12143
  stopAndGetLastProgress: () => {
11818
- clearInterval(interval);
11819
- controller._internals.signal.removeEventListener("abort", onAbort);
12144
+ cleanup();
11820
12145
  return currentState;
11821
12146
  }
11822
12147
  };
@@ -11839,6 +12164,7 @@ var internalParseMedia = async function({
11839
12164
  acknowledgeRemotionLicense,
11840
12165
  apiName,
11841
12166
  selectM3uStream: selectM3uStreamFn,
12167
+ selectM3uAssociatedPlaylists: selectM3uAssociatedPlaylistsFn,
11842
12168
  ...more
11843
12169
  }) {
11844
12170
  warnIfRemotionLicenseNotAcknowledged({
@@ -11890,7 +12216,8 @@ var internalParseMedia = async function({
11890
12216
  readerInterface,
11891
12217
  src,
11892
12218
  onDiscardedData,
11893
- selectM3uStreamFn
12219
+ selectM3uStreamFn,
12220
+ selectM3uAssociatedPlaylistsFn
11894
12221
  });
11895
12222
  const { iterator } = state;
11896
12223
  let currentReader = readerInstance;
@@ -11930,7 +12257,7 @@ var internalParseMedia = async function({
11930
12257
  return true;
11931
12258
  }
11932
12259
  if (state.iterator.counter.getOffset() === contentLength) {
11933
- if (state.getStructure().type === "m3u" && !state.m3u.getAllChunksProcessed()) {
12260
+ if (state.getStructure().type === "m3u" && !state.m3u.getAllChunksProcessedOverall()) {
11934
12261
  return false;
11935
12262
  }
11936
12263
  Log.verbose(logLevel, "Reached end of file");
@@ -12053,6 +12380,7 @@ var internalParseMedia = async function({
12053
12380
  currentReader.abort();
12054
12381
  iterator?.destroy();
12055
12382
  state.callbacks.tracks.ensureHasTracksAtEnd(fields);
12383
+ state.m3u.abortM3UStreamRuns();
12056
12384
  if (errored) {
12057
12385
  throw errored;
12058
12386
  }
@@ -12075,6 +12403,7 @@ var downloadAndParseMedia = async (options) => {
12075
12403
  onContainer: options.onContainer ?? null,
12076
12404
  onDimensions: options.onDimensions ?? null,
12077
12405
  selectM3uStream: options.selectM3uStream ?? defaultSelectM3uStreamFn,
12406
+ selectM3uAssociatedPlaylists: options.selectM3uAssociatedPlaylists ?? defaultSelectM3uAssociatedPlaylists,
12078
12407
  onDiscardedData: async (data) => {
12079
12408
  await content.write(data);
12080
12409
  },
@@ -12125,7 +12454,7 @@ var downloadAndParseMedia = async (options) => {
12125
12454
  return returnValue;
12126
12455
  };
12127
12456
  // src/version.ts
12128
- var VERSION = "4.0.268";
12457
+ var VERSION = "4.0.270";
12129
12458
 
12130
12459
  // src/index.ts
12131
12460
  var MediaParserInternals = {
@@ -12149,6 +12478,7 @@ export {
12149
12478
  hasBeenAborted,
12150
12479
  downloadAndParseMedia,
12151
12480
  defaultSelectM3uStreamFn,
12481
+ defaultSelectM3uAssociatedPlaylists,
12152
12482
  VERSION,
12153
12483
  MediaParserInternals,
12154
12484
  MediaParserAbortError,