@remotion/media-parser 4.0.265 → 4.0.267

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 (120) hide show
  1. package/dist/buffer-iterator.d.ts +1 -0
  2. package/dist/buffer-iterator.js +17 -0
  3. package/dist/containers/aac/parse-aac.js +1 -1
  4. package/dist/containers/avc/create-sps-pps-data.js +15 -1
  5. package/dist/containers/avc/interpret-sps.js +8 -2
  6. package/dist/containers/avc/parse-avc.js +23 -24
  7. package/dist/containers/flac/parse-streaminfo.js +1 -1
  8. package/dist/containers/iso-base-media/get-moov-atom.js +3 -2
  9. package/dist/containers/iso-base-media/process-box.js +9 -2
  10. package/dist/containers/iso-base-media/trun.js +1 -1
  11. package/dist/containers/m3u/after-manifest-fetch.d.ts +14 -0
  12. package/dist/containers/m3u/after-manifest-fetch.js +67 -0
  13. package/dist/containers/m3u/fetch-m3u8-stream.d.ts +3 -0
  14. package/dist/containers/m3u/fetch-m3u8-stream.js +18 -0
  15. package/dist/containers/m3u/get-chunks.d.ts +6 -0
  16. package/dist/containers/m3u/get-chunks.js +20 -0
  17. package/dist/containers/m3u/get-duration-from-m3u.d.ts +2 -0
  18. package/dist/containers/m3u/get-duration-from-m3u.js +9 -0
  19. package/dist/containers/m3u/get-playlist.d.ts +3 -0
  20. package/dist/containers/m3u/get-playlist.js +27 -0
  21. package/dist/containers/m3u/get-streams.d.ts +24 -0
  22. package/dist/containers/m3u/get-streams.js +80 -0
  23. package/dist/containers/m3u/parse-directive.d.ts +2 -0
  24. package/dist/containers/m3u/parse-directive.js +72 -0
  25. package/dist/containers/m3u/parse-m3u-manifest.d.ts +8 -0
  26. package/dist/containers/m3u/parse-m3u-manifest.js +18 -0
  27. package/dist/containers/m3u/parse-m3u-media-directive.d.ts +2 -0
  28. package/dist/containers/m3u/parse-m3u-media-directive.js +31 -0
  29. package/dist/containers/m3u/parse-m3u.d.ts +4 -0
  30. package/dist/containers/m3u/parse-m3u.js +35 -0
  31. package/dist/containers/m3u/parse-m3u8-text.d.ts +2 -0
  32. package/dist/containers/m3u/parse-m3u8-text.js +23 -0
  33. package/dist/containers/m3u/parse-stream-inf.d.ts +3 -0
  34. package/dist/containers/m3u/parse-stream-inf.js +62 -0
  35. package/dist/containers/m3u/return-packets.d.ts +16 -0
  36. package/dist/containers/m3u/return-packets.js +71 -0
  37. package/dist/containers/m3u/select-stream.d.ts +10 -0
  38. package/dist/containers/m3u/select-stream.js +19 -0
  39. package/dist/containers/m3u/types.d.ts +62 -0
  40. package/dist/containers/m3u/types.js +2 -0
  41. package/dist/containers/mp3/parse-mpeg-header.js +1 -1
  42. package/dist/containers/riff/expect-riff-box.js +1 -1
  43. package/dist/containers/transport-stream/boxes.d.ts +4 -1
  44. package/dist/containers/transport-stream/find-separator.d.ts +1 -2
  45. package/dist/containers/transport-stream/find-separator.js +7 -13
  46. package/dist/containers/transport-stream/handle-aac-packet.js +1 -1
  47. package/dist/containers/transport-stream/handle-avc-packet.js +1 -1
  48. package/dist/containers/transport-stream/parse-packet.js +7 -1
  49. package/dist/containers/transport-stream/parse-pat.d.ts +2 -1
  50. package/dist/containers/transport-stream/parse-pat.js +16 -1
  51. package/dist/containers/transport-stream/parse-stream-packet.js +86 -74
  52. package/dist/containers/wav/parse-fmt.js +1 -1
  53. package/dist/containers/webm/parse-ebml.js +9 -2
  54. package/dist/download-and-parse-media.js +32 -29
  55. package/dist/emit-available-info.js +13 -1
  56. package/dist/emitter.d.ts +4 -0
  57. package/dist/emitter.js +4 -0
  58. package/dist/esm/fetch.mjs +148 -0
  59. package/dist/esm/index.mjs +1321 -528
  60. package/dist/esm/node-writer.mjs +113 -0
  61. package/dist/esm/node.mjs +37 -106
  62. package/dist/esm/{from-web-file.mjs → web-file.mjs} +2 -7
  63. package/dist/fetch.d.ts +1 -0
  64. package/dist/fetch.js +17 -0
  65. package/dist/file-types/detect-file-type.d.ts +5 -1
  66. package/dist/file-types/detect-file-type.js +5 -1
  67. package/dist/file-types/index.js +3 -0
  68. package/dist/forward-controller.d.ts +7 -0
  69. package/dist/forward-controller.js +25 -0
  70. package/dist/get-container.js +3 -0
  71. package/dist/get-duration.js +35 -2
  72. package/dist/get-fields-from-callbacks.js +1 -0
  73. package/dist/get-fps.js +7 -0
  74. package/dist/get-tracks.d.ts +2 -0
  75. package/dist/get-tracks.js +29 -4
  76. package/dist/has-all-info.js +4 -0
  77. package/dist/index.d.ts +46 -9
  78. package/dist/index.js +3 -1
  79. package/dist/init-video.js +8 -0
  80. package/dist/internal-parse-media.js +11 -4
  81. package/dist/is-audio-structure.js +3 -0
  82. package/dist/make-hvc1-codec-strings.js +3 -3
  83. package/dist/media-parser-controller.js +3 -1
  84. package/dist/metadata/get-metadata.js +26 -3
  85. package/dist/node-writer.d.ts +1 -0
  86. package/dist/node-writer.js +17 -0
  87. package/dist/node.d.ts +1 -0
  88. package/dist/node.js +17 -0
  89. package/dist/options.d.ts +9 -1
  90. package/dist/parse-media.js +11 -8
  91. package/dist/parse-result.d.ts +2 -1
  92. package/dist/readers/fetch/get-body-and-reader.d.ts +8 -0
  93. package/dist/readers/fetch/get-body-and-reader.js +42 -0
  94. package/dist/readers/fetch/resolve-url.d.ts +1 -0
  95. package/dist/readers/fetch/resolve-url.js +15 -0
  96. package/dist/readers/from-fetch.js +19 -40
  97. package/dist/readers/from-node.js +1 -7
  98. package/dist/readers/from-web-file.js +1 -6
  99. package/dist/readers/reader.d.ts +1 -2
  100. package/dist/register-track.d.ts +8 -3
  101. package/dist/register-track.js +30 -17
  102. package/dist/run-parse-iteration.js +6 -1
  103. package/dist/state/can-skip-tracks.js +2 -1
  104. package/dist/state/emitted-fields.js +1 -0
  105. package/dist/state/m3u-state.d.ts +29 -0
  106. package/dist/state/m3u-state.js +48 -0
  107. package/dist/state/need-samples-for-fields.js +1 -0
  108. package/dist/state/parser-state.d.ts +36 -1
  109. package/dist/state/parser-state.js +4 -1
  110. package/dist/state/structure.d.ts +1 -0
  111. package/dist/state/structure.js +7 -0
  112. package/dist/version.d.ts +1 -1
  113. package/dist/version.js +1 -1
  114. package/dist/web-file.d.ts +1 -0
  115. package/dist/web-file.js +17 -0
  116. package/package.json +32 -32
  117. package/dist/containers/mp3/get-tracks-from-mp3.d.ts +0 -4
  118. package/dist/containers/mp3/get-tracks-from-mp3.js +0 -25
  119. package/dist/esm/from-fetch.mjs +0 -237
  120. package/dist/esm/from-node.mjs +0 -50
@@ -1,237 +1,3 @@
1
- // src/errors.ts
2
- class IsAGifError extends Error {
3
- mimeType;
4
- sizeInBytes;
5
- fileName;
6
- constructor({
7
- message,
8
- mimeType,
9
- sizeInBytes,
10
- fileName
11
- }) {
12
- super(message);
13
- this.fileName = "IsAGifError";
14
- this.mimeType = mimeType;
15
- this.sizeInBytes = sizeInBytes;
16
- this.fileName = fileName;
17
- if (Error.captureStackTrace) {
18
- Error.captureStackTrace(this, IsAGifError);
19
- }
20
- }
21
- }
22
-
23
- class IsAnImageError extends Error {
24
- imageType;
25
- dimensions;
26
- mimeType;
27
- sizeInBytes;
28
- fileName;
29
- constructor({
30
- dimensions,
31
- imageType,
32
- message,
33
- mimeType,
34
- sizeInBytes,
35
- fileName
36
- }) {
37
- super(message);
38
- this.name = "IsAnImageError";
39
- this.imageType = imageType;
40
- this.dimensions = dimensions;
41
- this.mimeType = mimeType;
42
- this.sizeInBytes = sizeInBytes;
43
- this.fileName = fileName;
44
- if (Error.captureStackTrace) {
45
- Error.captureStackTrace(this, IsAnImageError);
46
- }
47
- }
48
- }
49
-
50
- class IsAPdfError extends Error {
51
- mimeType;
52
- sizeInBytes;
53
- fileName;
54
- constructor({
55
- message,
56
- mimeType,
57
- sizeInBytes,
58
- fileName
59
- }) {
60
- super(message);
61
- this.name = "IsAPdfError";
62
- this.mimeType = mimeType;
63
- this.sizeInBytes = sizeInBytes;
64
- this.fileName = fileName;
65
- if (Error.captureStackTrace) {
66
- Error.captureStackTrace(this, IsAPdfError);
67
- }
68
- }
69
- }
70
-
71
- class IsAnUnsupportedFileTypeError extends Error {
72
- mimeType;
73
- sizeInBytes;
74
- fileName;
75
- constructor({
76
- message,
77
- mimeType,
78
- sizeInBytes,
79
- fileName
80
- }) {
81
- super(message);
82
- this.name = "IsAnUnsupportedFileTypeError";
83
- this.mimeType = mimeType;
84
- this.sizeInBytes = sizeInBytes;
85
- this.fileName = fileName;
86
- if (Error.captureStackTrace) {
87
- Error.captureStackTrace(this, IsAnUnsupportedFileTypeError);
88
- }
89
- }
90
- }
91
-
92
- class IsAnUnsupportedAudioTypeError extends Error {
93
- mimeType;
94
- sizeInBytes;
95
- fileName;
96
- audioType;
97
- constructor({
98
- message,
99
- mimeType,
100
- sizeInBytes,
101
- fileName,
102
- audioType
103
- }) {
104
- super(message);
105
- this.name = "IsAnUnsupportedAudioTypeError";
106
- this.mimeType = mimeType;
107
- this.sizeInBytes = sizeInBytes;
108
- this.fileName = fileName;
109
- this.audioType = audioType;
110
- if (Error.captureStackTrace) {
111
- Error.captureStackTrace(this, IsAnUnsupportedAudioTypeError);
112
- }
113
- }
114
- }
115
-
116
- class MediaParserAbortError extends Error {
117
- constructor(message) {
118
- super(message);
119
- this.name = "MediaParserAbortError";
120
- this.cause = undefined;
121
- }
122
- }
123
- var hasBeenAborted = (error) => {
124
- return error instanceof MediaParserAbortError;
125
- };
126
-
127
- // src/readers/from-fetch.ts
128
- function parseContentRange(input) {
129
- const matches = input.match(/^(\w+) ((\d+)-(\d+)|\*)\/(\d+|\*)$/);
130
- if (!matches)
131
- return null;
132
- const [, unit, , start, end, size] = matches;
133
- const range = {
134
- unit,
135
- start: start != null ? Number(start) : null,
136
- end: end != null ? Number(end) : null,
137
- size: size === "*" ? null : Number(size)
138
- };
139
- if (range.start === null && range.end === null && range.size === null) {
140
- return null;
141
- }
142
- return range;
143
- }
144
- var validateContentRangeAndDetectIfSupported = (actualRange, parsedContentRange, statusCode) => {
145
- if (statusCode === 206) {
146
- return { supportsContentRange: true };
147
- }
148
- if (typeof actualRange === "number" && parsedContentRange?.start !== actualRange) {
149
- if (actualRange === 0) {
150
- return { supportsContentRange: false };
151
- }
152
- throw new Error(`Range header (${actualRange}) does not match content-range header (${parsedContentRange?.start})`);
153
- }
154
- if (actualRange !== null && typeof actualRange !== "number" && (parsedContentRange?.start !== actualRange[0] || parsedContentRange?.end !== actualRange[1])) {
155
- throw new Error(`Range header (${actualRange}) does not match content-range header (${parsedContentRange?.start})`);
156
- }
157
- return { supportsContentRange: true };
158
- };
159
- var fetchReader = {
160
- read: async ({ src, range, controller }) => {
161
- if (typeof src !== "string") {
162
- throw new Error("src must be a string when using `fetchReader`");
163
- }
164
- const resolvedUrl = typeof window !== "undefined" && typeof window.location !== "undefined" ? new URL(src, window.location.origin).toString() : src;
165
- if (!resolvedUrl.startsWith("https://") && !resolvedUrl.startsWith("blob:") && !resolvedUrl.startsWith("http://")) {
166
- return Promise.reject(new Error(resolvedUrl + " is not a URL - needs to start with http:// or https:// or blob:. If you want to read a local file, pass `reader: nodeReader` to parseMedia()."));
167
- }
168
- const ownController = new AbortController;
169
- const cache = typeof navigator !== "undefined" && navigator.userAgent.includes("Cloudflare-Workers") ? undefined : "no-store";
170
- const actualRange = range === null ? 0 : range;
171
- const res = await fetch(resolvedUrl, {
172
- headers: typeof actualRange === "number" ? {
173
- Range: `bytes=${actualRange}-`
174
- } : {
175
- Range: `bytes=${`${actualRange[0]}-${actualRange[1]}`}`
176
- },
177
- signal: ownController.signal,
178
- cache
179
- });
180
- const contentRange = res.headers.get("content-range");
181
- const parsedContentRange = contentRange ? parseContentRange(contentRange) : null;
182
- const { supportsContentRange } = validateContentRangeAndDetectIfSupported(actualRange, parsedContentRange, res.status);
183
- controller._internals.signal.addEventListener("abort", () => {
184
- ownController.abort(new MediaParserAbortError("Aborted by user"));
185
- }, { once: true });
186
- if (res.status.toString().startsWith("4") || res.status.toString().startsWith("5")) {
187
- throw new Error(`Server returned status code ${res.status} for ${src} and range ${actualRange}`);
188
- }
189
- if (!res.body) {
190
- throw new Error("No body");
191
- }
192
- const length = res.headers.get("content-length");
193
- const contentLength = length === null ? null : parseInt(length, 10);
194
- const contentDisposition = res.headers.get("content-disposition");
195
- const name = contentDisposition?.match(/filename="([^"]+)"/)?.[1];
196
- const fallbackName = src.split("/").pop();
197
- const reader = res.body.getReader();
198
- if (controller) {
199
- controller._internals.signal.addEventListener("abort", () => {
200
- reader.cancel().catch(() => {
201
- });
202
- }, { once: true });
203
- }
204
- return {
205
- reader: {
206
- reader,
207
- abort: () => {
208
- ownController.abort();
209
- }
210
- },
211
- contentLength,
212
- contentType: res.headers.get("content-type"),
213
- name: name ?? fallbackName,
214
- supportsContentRange
215
- };
216
- },
217
- getLength: async (src) => {
218
- if (typeof src !== "string") {
219
- throw new Error("src must be a string when using `fetchReader`");
220
- }
221
- const res = await fetch(src, {
222
- method: "HEAD"
223
- });
224
- if (!res.body) {
225
- throw new Error("No body");
226
- }
227
- const length = res.headers.get("content-length");
228
- if (!length) {
229
- throw new Error("No content-length");
230
- }
231
- return parseInt(length, 10);
232
- }
233
- };
234
-
235
1
  // src/aac-codecprivate.ts
236
2
  var getSampleRateFromSampleFrequencyIndex = (samplingFrequencyIndex) => {
237
3
  switch (samplingFrequencyIndex) {
@@ -1135,6 +901,9 @@ var isFlac = (data) => {
1135
901
  const flacPattern = new Uint8Array([102, 76, 97, 67]);
1136
902
  return matchesPattern(flacPattern)(data.subarray(0, 4));
1137
903
  };
904
+ var isM3u = (data) => {
905
+ return new TextDecoder("utf-8").decode(data.slice(0, 7)) === "#EXTM3U";
906
+ };
1138
907
 
1139
908
  // src/file-types/bmp.ts
1140
909
  function getBmpDimensions(bmpData) {
@@ -1282,6 +1051,9 @@ var detectFileType = (data) => {
1282
1051
  if (isFlac(data)) {
1283
1052
  return { type: "flac" };
1284
1053
  }
1054
+ if (isM3u(data)) {
1055
+ return { type: "m3u" };
1056
+ }
1285
1057
  const webp = isWebp(data);
1286
1058
  if (webp) {
1287
1059
  return webp;
@@ -1395,6 +1167,21 @@ var getArrayBufferIterator = (initialData, maxBytes) => {
1395
1167
  counter.decrement(1);
1396
1168
  return new TextDecoder().decode(new Uint8Array(bytes));
1397
1169
  };
1170
+ const readUntilLineEnd = () => {
1171
+ const bytes = [];
1172
+ while (true) {
1173
+ if (bytesRemaining() === 0) {
1174
+ return null;
1175
+ }
1176
+ const byte = getUint8();
1177
+ bytes.push(byte);
1178
+ if (byte === 10) {
1179
+ break;
1180
+ }
1181
+ }
1182
+ const str = new TextDecoder().decode(new Uint8Array(bytes)).trim();
1183
+ return str;
1184
+ };
1398
1185
  const getUint8 = () => {
1399
1186
  const val = view.getUint8(counter.getDiscardedOffset());
1400
1187
  counter.increment(1);
@@ -1822,6 +1609,7 @@ var getArrayBufferIterator = (initialData, maxBytes) => {
1822
1609
  readExpGolomb,
1823
1610
  startCheckpoint,
1824
1611
  getFlacCodecNumber,
1612
+ readUntilLineEnd,
1825
1613
  getSyncSafeInt32
1826
1614
  };
1827
1615
  };
@@ -1975,6 +1763,11 @@ var combineUint8Arrays = (arrays) => {
1975
1763
  return result;
1976
1764
  };
1977
1765
 
1766
+ // src/truthy.ts
1767
+ function truthy(value) {
1768
+ return Boolean(value);
1769
+ }
1770
+
1978
1771
  // src/containers/avc/create-sps-pps-data.ts
1979
1772
  function serializeUint16(value) {
1980
1773
  const buffer = new ArrayBuffer(2);
@@ -1996,8 +1789,9 @@ var createSpsPpsData = (avc1Profile) => {
1996
1789
  avc1Profile.sps.sps,
1997
1790
  new Uint8Array([1]),
1998
1791
  serializeUint16(avc1Profile.pps.pps.length),
1999
- avc1Profile.pps.pps
2000
- ]);
1792
+ avc1Profile.pps.pps,
1793
+ [66, 77, 88].some((b) => avc1Profile.sps.spsData.profile === b) ? null : new Uint8Array([253, 248, 248, 0])
1794
+ ].filter(truthy));
2001
1795
  };
2002
1796
 
2003
1797
  // src/add-avc-profile-to-track.ts
@@ -2013,29 +1807,45 @@ var addAvcProfileToTrack = (track, avc1Profile) => {
2013
1807
  };
2014
1808
 
2015
1809
  // src/register-track.ts
2016
- var registerTrack = async ({
1810
+ var registerVideoTrack = async ({
2017
1811
  state,
2018
1812
  track,
2019
1813
  container
2020
1814
  }) => {
2021
1815
  if (state.callbacks.tracks.getTracks().find((t) => t.trackId === track.trackId)) {
2022
1816
  Log.trace(state.logLevel, `Track ${track.trackId} already registered, skipping`);
2023
- return;
1817
+ return null;
2024
1818
  }
2025
- if (track.type === "video") {
2026
- state.callbacks.tracks.addTrack(track);
2027
- if (state.onVideoTrack) {
2028
- const callback = await state.onVideoTrack({ track, container });
2029
- await state.callbacks.registerVideoSampleCallback(track.trackId, callback ?? null);
2030
- }
1819
+ if (track.type !== "video") {
1820
+ throw new Error("Expected video track");
2031
1821
  }
2032
- if (track.type === "audio") {
2033
- state.callbacks.tracks.addTrack(track);
2034
- if (state.onAudioTrack) {
2035
- const callback = await state.onAudioTrack({ track, container });
2036
- await state.callbacks.registerAudioSampleCallback(track.trackId, callback ?? null);
2037
- }
1822
+ state.callbacks.tracks.addTrack(track);
1823
+ if (!state.onVideoTrack) {
1824
+ return null;
1825
+ }
1826
+ const callback = await state.onVideoTrack({ track, container });
1827
+ await state.callbacks.registerVideoSampleCallback(track.trackId, callback ?? null);
1828
+ return callback;
1829
+ };
1830
+ var registerAudioTrack = async ({
1831
+ state,
1832
+ track,
1833
+ container
1834
+ }) => {
1835
+ if (state.callbacks.tracks.getTracks().find((t) => t.trackId === track.trackId)) {
1836
+ Log.trace(state.logLevel, `Track ${track.trackId} already registered, skipping`);
1837
+ return null;
2038
1838
  }
1839
+ if (track.type !== "audio") {
1840
+ throw new Error("Expected audio track");
1841
+ }
1842
+ state.callbacks.tracks.addTrack(track);
1843
+ if (!state.onAudioTrack) {
1844
+ return null;
1845
+ }
1846
+ const callback = await state.onAudioTrack({ track, container });
1847
+ await state.callbacks.registerAudioSampleCallback(track.trackId, callback ?? null);
1848
+ return callback;
2039
1849
  };
2040
1850
  var registerVideoTrackWhenProfileIsAvailable = ({
2041
1851
  state,
@@ -2043,7 +1853,7 @@ var registerVideoTrackWhenProfileIsAvailable = ({
2043
1853
  container
2044
1854
  }) => {
2045
1855
  state.riff.registerOnAvcProfileCallback(async (profile) => {
2046
- await registerTrack({
1856
+ await registerVideoTrack({
2047
1857
  state,
2048
1858
  track: addAvcProfileToTrack(track, profile),
2049
1859
  container
@@ -2392,6 +2202,9 @@ var isAudioStructure = (structure) => {
2392
2202
  if (structure.type === "riff") {
2393
2203
  return false;
2394
2204
  }
2205
+ if (structure.type === "m3u") {
2206
+ return false;
2207
+ }
2395
2208
  throw new Error(`Unhandled structure type: ${structure}`);
2396
2209
  };
2397
2210
 
@@ -2496,6 +2309,9 @@ var getFps = (state) => {
2496
2309
  if (segments.type === "transport-stream") {
2497
2310
  return null;
2498
2311
  }
2312
+ if (segments.type === "m3u") {
2313
+ return null;
2314
+ }
2499
2315
  if (segments.type === "mp3" || segments.type === "wav" || segments.type === "flac" || segments.type === "aac") {
2500
2316
  return null;
2501
2317
  }
@@ -2519,20 +2335,10 @@ var hasFps = (state) => {
2519
2335
  if (structure.type === "transport-stream") {
2520
2336
  return true;
2521
2337
  }
2522
- return hasFpsSuitedForSlowFps(state);
2523
- };
2524
-
2525
- // src/containers/mp3/get-tracks-from-mp3.ts
2526
- var getTracksFromMp3OrWavOrAac = (parserState) => {
2527
- const tracks2 = parserState.callbacks.tracks.getTracks();
2528
- if (tracks2.length === 0) {
2529
- throw new Error("No tracks found");
2338
+ if (structure.type === "m3u") {
2339
+ return true;
2530
2340
  }
2531
- return {
2532
- audioTracks: tracks2.filter((t) => t.type === "audio"),
2533
- otherTracks: [],
2534
- videoTracks: []
2535
- };
2341
+ return hasFpsSuitedForSlowFps(state);
2536
2342
  };
2537
2343
 
2538
2344
  // src/containers/riff/timescale.ts
@@ -2638,11 +2444,6 @@ var hasAllTracksFromAvi = (state) => {
2638
2444
  }
2639
2445
  };
2640
2446
 
2641
- // src/truthy.ts
2642
- function truthy(value) {
2643
- return Boolean(value);
2644
- }
2645
-
2646
2447
  // src/containers/transport-stream/traversal.ts
2647
2448
  var findProgramAssociationTableOrThrow = (structure) => {
2648
2449
  const box = structure.boxes.find((b) => b.type === "transport-stream-pat-box");
@@ -2704,8 +2505,8 @@ var getHvc1CodecString = (data) => {
2704
2505
  const generalProfileSpaceTierFlagAndIdc = data.getUint8();
2705
2506
  let generalProfileCompatibility = data.getUint32();
2706
2507
  const generalProfileSpace = generalProfileSpaceTierFlagAndIdc >> 6;
2707
- const generalTierFlag = generalProfileSpaceTierFlagAndIdc >> 5;
2708
- const generalProfileIdc = generalProfileSpaceTierFlagAndIdc >> 0;
2508
+ const generalTierFlag = (generalProfileSpaceTierFlagAndIdc & 32) >> 5;
2509
+ const generalProfileIdc = generalProfileSpaceTierFlagAndIdc & 31;
2709
2510
  const generalConstraintIndicator = data.getSlice(6);
2710
2511
  const generalLevelIdc = data.getUint8();
2711
2512
  let profileId = 0;
@@ -2726,7 +2527,7 @@ var getHvc1CodecString = (data) => {
2726
2527
  hasByte = true;
2727
2528
  }
2728
2529
  }
2729
- return `${profileSpaceChar}${generalProfileIdc.toString(16)}.${profileId.toString(16)}.${generalTierChar}${generalLevelIdc}.${generalConstraintString}`;
2530
+ return `${profileSpaceChar}${generalProfileIdc.toString(16)}.${profileId.toString(16)}.${generalTierChar}${generalLevelIdc}${generalConstraintString ? "." : ""}${generalConstraintString}`;
2730
2531
  };
2731
2532
 
2732
2533
  // src/containers/webm/av1-codec-private.ts
@@ -3387,6 +3188,9 @@ var getHasTracks = (state) => {
3387
3188
  if (structure.type === "flac") {
3388
3189
  return state.callbacks.tracks.hasAllTracks();
3389
3190
  }
3191
+ if (structure.type === "m3u") {
3192
+ return state.callbacks.tracks.hasAllTracks();
3193
+ }
3390
3194
  throw new Error("Unknown container " + structure);
3391
3195
  };
3392
3196
  var getTracksFromMa = (segments, state) => {
@@ -3445,6 +3249,17 @@ var getTracksFromIsoBaseMedia = (state) => {
3445
3249
  otherTracks
3446
3250
  };
3447
3251
  };
3252
+ var defaultGetTracks = (parserState) => {
3253
+ const tracks2 = parserState.callbacks.tracks.getTracks();
3254
+ if (tracks2.length === 0) {
3255
+ throw new Error("No tracks found");
3256
+ }
3257
+ return {
3258
+ audioTracks: tracks2.filter((t) => t.type === "audio"),
3259
+ otherTracks: [],
3260
+ videoTracks: tracks2.filter((t) => t.type === "video")
3261
+ };
3262
+ };
3448
3263
  var getTracks = (state) => {
3449
3264
  const structure = state.getStructure();
3450
3265
  if (structure.type === "matroska") {
@@ -3459,8 +3274,8 @@ var getTracks = (state) => {
3459
3274
  if (structure.type === "transport-stream") {
3460
3275
  return getTracksFromTransportStream(state);
3461
3276
  }
3462
- if (structure.type === "mp3" || structure.type === "wav" || structure.type === "flac" || structure.type === "aac") {
3463
- return getTracksFromMp3OrWavOrAac(state);
3277
+ if (structure.type === "mp3" || structure.type === "wav" || structure.type === "flac" || structure.type === "aac" || structure.type === "m3u") {
3278
+ return defaultGetTracks(state);
3464
3279
  }
3465
3280
  throw new Error(`Unknown container${structure}`);
3466
3281
  };
@@ -4873,7 +4688,7 @@ var parseTrun = ({
4873
4688
  size
4874
4689
  }) => {
4875
4690
  const version = iterator.getUint8();
4876
- if (version !== 0) {
4691
+ if (version !== 0 && version !== 1) {
4877
4692
  throw new Error(`Unsupported TRUN version ${version}`);
4878
4693
  }
4879
4694
  const flags = iterator.getUint24();
@@ -5057,8 +4872,15 @@ var processBox = async (state) => {
5057
4872
  state
5058
4873
  });
5059
4874
  const transformedTrack = makeBaseMediaTrack(box);
5060
- if (transformedTrack) {
5061
- await registerTrack({
4875
+ if (transformedTrack && transformedTrack.type === "video") {
4876
+ await registerVideoTrack({
4877
+ state,
4878
+ track: transformedTrack,
4879
+ container: "mp4"
4880
+ });
4881
+ }
4882
+ if (transformedTrack && transformedTrack.type === "audio") {
4883
+ await registerAudioTrack({
5062
4884
  state,
5063
4885
  track: transformedTrack,
5064
4886
  container: "mp4"
@@ -5639,8 +5461,15 @@ var postprocessEbml = async ({
5639
5461
  track: ebml,
5640
5462
  timescale: state.webm.getTimescale()
5641
5463
  });
5642
- if (track) {
5643
- await registerTrack({
5464
+ if (track && track.type === "audio") {
5465
+ await registerAudioTrack({
5466
+ state,
5467
+ track,
5468
+ container: "webm"
5469
+ });
5470
+ }
5471
+ if (track && track.type === "video") {
5472
+ await registerVideoTrack({
5644
5473
  state,
5645
5474
  track,
5646
5475
  container: "webm"
@@ -5699,6 +5528,73 @@ var postprocessEbml = async ({
5699
5528
  return ebml;
5700
5529
  };
5701
5530
 
5531
+ // src/containers/m3u/get-streams.ts
5532
+ var isIndependentSegments = (structure) => {
5533
+ if (structure === null || structure.type !== "m3u") {
5534
+ return false;
5535
+ }
5536
+ return structure.boxes.some((box) => box.type === "m3u-independent-segments" || box.type === "m3u-stream-info");
5537
+ };
5538
+ var getM3uStreams = (structure, originalSrc) => {
5539
+ if (structure === null || structure.type !== "m3u") {
5540
+ return null;
5541
+ }
5542
+ const boxes = [];
5543
+ for (let i = 0;i < structure.boxes.length; i++) {
5544
+ const str = structure.boxes[i];
5545
+ if (str.type === "m3u-stream-info") {
5546
+ const next = structure.boxes[i + 1];
5547
+ if (next.type !== "m3u-text-value") {
5548
+ throw new Error("Expected m3u-text-value");
5549
+ }
5550
+ const dedicatedAudioTracks = [];
5551
+ if (str.audio) {
5552
+ const match = structure.boxes.filter((box) => {
5553
+ return box.type === "m3u-media-info" && box.groupId === str.audio;
5554
+ });
5555
+ for (const audioTrack of match) {
5556
+ dedicatedAudioTracks.push({
5557
+ autoselect: audioTrack.autoselect,
5558
+ channels: audioTrack.channels,
5559
+ default: audioTrack.default,
5560
+ groupId: audioTrack.groupId,
5561
+ language: audioTrack.language,
5562
+ name: audioTrack.name,
5563
+ url: originalSrc && originalSrc.startsWith("http") ? new URL(audioTrack.uri, originalSrc).href : audioTrack.uri
5564
+ });
5565
+ }
5566
+ }
5567
+ boxes.push({
5568
+ url: originalSrc && originalSrc.startsWith("http") ? new URL(next.value, originalSrc).href : next.value,
5569
+ averageBandwidth: str.averageBandwidth,
5570
+ bandwidth: str.bandwidth,
5571
+ codecs: str.codecs,
5572
+ resolution: str.resolution,
5573
+ dedicatedAudioTracks
5574
+ });
5575
+ }
5576
+ }
5577
+ if (boxes.length === 0) {
5578
+ return null;
5579
+ }
5580
+ const sorted = boxes.slice().sort((a, b) => {
5581
+ const aResolution = a.resolution ? a.resolution.width * a.resolution.height : 0;
5582
+ const bResolution = b.resolution ? b.resolution.width * b.resolution.height : 0;
5583
+ return bResolution - aResolution;
5584
+ });
5585
+ return sorted.map((box, index) => ({ ...box, id: index }));
5586
+ };
5587
+ var m3uHasStreams = (state) => {
5588
+ const structure = state.getStructureOrNull();
5589
+ if (!structure) {
5590
+ return false;
5591
+ }
5592
+ if (structure.type !== "m3u") {
5593
+ return true;
5594
+ }
5595
+ return state.m3u.hasFinishedManifest();
5596
+ };
5597
+
5702
5598
  // src/get-container.ts
5703
5599
  var getContainer = (segments) => {
5704
5600
  if (segments.type === "iso-base-media") {
@@ -5728,6 +5624,9 @@ var getContainer = (segments) => {
5728
5624
  if (segments.type === "aac") {
5729
5625
  return "aac";
5730
5626
  }
5627
+ if (segments.type === "m3u") {
5628
+ return "m3u8";
5629
+ }
5731
5630
  throw new Error("Unknown container " + segments);
5732
5631
  };
5733
5632
  var hasContainer = (boxes) => {
@@ -5994,6 +5893,35 @@ var getSamplePositionsFromTrack = ({
5994
5893
  return samplePositions;
5995
5894
  };
5996
5895
 
5896
+ // src/containers/m3u/get-playlist.ts
5897
+ var getPlaylist = (structure) => {
5898
+ const isIndependent = isIndependentSegments(structure);
5899
+ if (!isIndependent) {
5900
+ return {
5901
+ type: "m3u-playlist",
5902
+ boxes: structure.boxes
5903
+ };
5904
+ }
5905
+ const playlists = structure.boxes.filter((box) => box.type === "m3u-playlist");
5906
+ if (playlists.length !== 1) {
5907
+ throw new Error("Expected one playlist");
5908
+ }
5909
+ return playlists[0];
5910
+ };
5911
+ var getDurationFromPlaylist = (playlist) => {
5912
+ const duration2 = playlist.boxes.filter((box) => box.type === "m3u-extinf");
5913
+ if (duration2.length === 0) {
5914
+ throw new Error("Expected duration in m3u playlist");
5915
+ }
5916
+ return duration2.reduce((acc, d) => acc + d.value, 0);
5917
+ };
5918
+
5919
+ // src/containers/m3u/get-duration-from-m3u.ts
5920
+ var getDurationFromM3u = (structure) => {
5921
+ const playlist = getPlaylist(structure);
5922
+ return getDurationFromPlaylist(playlist);
5923
+ };
5924
+
5997
5925
  // src/containers/mp3/get-frame-length.ts
5998
5926
  var getUnroundedMpegFrameLength = ({
5999
5927
  samplesPerFrame,
@@ -6146,6 +6074,27 @@ var getDurationFromMatroska = (segments) => {
6146
6074
  }
6147
6075
  return duration2.value.value / timestampScale2.value.value * 1000;
6148
6076
  };
6077
+ var isoHasDuration = (parserState) => {
6078
+ const structure = parserState.getIsoStructure();
6079
+ const moovBox = getMoovBox(parserState);
6080
+ if (!moovBox) {
6081
+ return false;
6082
+ }
6083
+ const mvhdBox = getMvhdBox(moovBox);
6084
+ if (!mvhdBox) {
6085
+ return false;
6086
+ }
6087
+ if (mvhdBox.type !== "mvhd-box") {
6088
+ throw new Error("Expected mvhd-box");
6089
+ }
6090
+ if (mvhdBox.durationInSeconds > 0) {
6091
+ return true;
6092
+ }
6093
+ const moofBoxes = getMoofBoxes(structure.boxes);
6094
+ const hasMvex = moovBox.children.some((b) => b.type === "regular-box" && b.boxType === "mvex");
6095
+ const isFragmented = moofBoxes.length > 0 || hasMvex;
6096
+ return !isFragmented;
6097
+ };
6149
6098
  var getDurationFromIsoBaseMedia = (parserState) => {
6150
6099
  const structure = parserState.getIsoStructure();
6151
6100
  const moovBox = getMoovBox(parserState);
@@ -6207,14 +6156,24 @@ var getDuration = (parserState) => {
6207
6156
  if (structure.type === "flac") {
6208
6157
  return getDurationFromFlac(parserState);
6209
6158
  }
6159
+ if (structure.type === "m3u") {
6160
+ return getDurationFromM3u(parserState.getM3uStructure());
6161
+ }
6210
6162
  throw new Error("Has no duration " + structure);
6211
6163
  };
6212
6164
  var hasDuration = (parserState) => {
6165
+ const structure = parserState.getStructureOrNull();
6166
+ if (structure === null) {
6167
+ return false;
6168
+ }
6169
+ if (structure.type === "iso-base-media") {
6170
+ return isoHasDuration(parserState);
6171
+ }
6213
6172
  return getHasTracks(parserState);
6214
6173
  };
6215
6174
  var hasSlowDuration = (parserState) => {
6216
6175
  try {
6217
- return getDuration(parserState) !== null;
6176
+ return hasDuration(parserState) && getDuration(parserState) !== null;
6218
6177
  } catch {
6219
6178
  return false;
6220
6179
  }
@@ -6485,7 +6444,7 @@ var getMetadata = (state) => {
6485
6444
  if (structure.type === "riff") {
6486
6445
  return getMetadataFromRiff(structure);
6487
6446
  }
6488
- if (structure.type === "transport-stream") {
6447
+ if (structure.type === "transport-stream" || structure.type === "m3u") {
6489
6448
  return [];
6490
6449
  }
6491
6450
  if (structure.type === "mp3") {
@@ -6504,7 +6463,10 @@ var getMetadata = (state) => {
6504
6463
  if (structure.type === "flac") {
6505
6464
  return getMetadataFromFlac(structure) ?? [];
6506
6465
  }
6507
- return getMetadataFromIsoBase(state);
6466
+ if (structure.type === "iso-base-media") {
6467
+ return getMetadataFromIsoBase(state);
6468
+ }
6469
+ throw new Error("Unknown container " + structure);
6508
6470
  };
6509
6471
  var hasMetadata = (structure) => {
6510
6472
  if (structure.type === "mp3") {
@@ -6513,7 +6475,25 @@ var hasMetadata = (structure) => {
6513
6475
  if (structure.type === "wav") {
6514
6476
  return getMetadataFromWav(structure) !== null;
6515
6477
  }
6516
- return false;
6478
+ if (structure.type === "m3u" || structure.type === "transport-stream") {
6479
+ return true;
6480
+ }
6481
+ if (structure.type === "flac") {
6482
+ return getMetadataFromFlac(structure) !== null;
6483
+ }
6484
+ if (structure.type === "iso-base-media") {
6485
+ return false;
6486
+ }
6487
+ if (structure.type === "matroska") {
6488
+ return false;
6489
+ }
6490
+ if (structure.type === "riff") {
6491
+ return false;
6492
+ }
6493
+ if (structure.type === "aac") {
6494
+ return true;
6495
+ }
6496
+ throw new Error("Unknown container " + structure);
6517
6497
  };
6518
6498
 
6519
6499
  // src/get-location.ts
@@ -6891,6 +6871,17 @@ var emitAvailableInfo = async ({
6891
6871
  }
6892
6872
  continue;
6893
6873
  }
6874
+ if (key === "m3uStreams") {
6875
+ if (!emittedFields.m3uStreams && hasInfo.m3uStreams) {
6876
+ const streams = getM3uStreams(state.getStructureOrNull(), typeof state.src === "string" ? state.src : null);
6877
+ await callbacks.onM3uStreams?.(streams);
6878
+ if (fieldsInReturnValue.m3uStreams) {
6879
+ returnValue.m3uStreams = streams;
6880
+ }
6881
+ emittedFields.m3uStreams = true;
6882
+ }
6883
+ continue;
6884
+ }
6894
6885
  throw new Error(`Unhandled key: ${key}`);
6895
6886
  }
6896
6887
  };
@@ -6928,6 +6919,7 @@ var getFieldsFromCallback = ({
6928
6919
  sampleRate: Boolean(callbacks.onSampleRate),
6929
6920
  slowAudioBitrate: Boolean(callbacks.onSlowAudioBitrate),
6930
6921
  slowVideoBitrate: Boolean(callbacks.onSlowVideoBitrate),
6922
+ m3uStreams: Boolean(callbacks.onM3uStreams),
6931
6923
  ...fields
6932
6924
  };
6933
6925
  return newFields;
@@ -6961,7 +6953,8 @@ var needsSamples = {
6961
6953
  numberOfAudioChannels: false,
6962
6954
  sampleRate: false,
6963
6955
  slowAudioBitrate: true,
6964
- slowVideoBitrate: true
6956
+ slowVideoBitrate: true,
6957
+ m3uStreams: false
6965
6958
  };
6966
6959
  var needsToIterateOverSamples = ({
6967
6960
  fields,
@@ -7052,6 +7045,9 @@ var getAvailableInfo = ({
7052
7045
  if (key === "sampleRate") {
7053
7046
  return hasSampleRate(state);
7054
7047
  }
7048
+ if (key === "m3uStreams") {
7049
+ return m3uHasStreams(state);
7050
+ }
7055
7051
  throw new Error(`Unknown key: ${key}`);
7056
7052
  });
7057
7053
  const entries = [];
@@ -7080,7 +7076,8 @@ var hasAllInfo = ({
7080
7076
  class MediaParserEmitter {
7081
7077
  listeners = {
7082
7078
  pause: [],
7083
- resume: []
7079
+ resume: [],
7080
+ abort: []
7084
7081
  };
7085
7082
  addEventListener = (name, callback) => {
7086
7083
  this.listeners[name].push(callback);
@@ -7099,7 +7096,136 @@ class MediaParserEmitter {
7099
7096
  dispatchResume = () => {
7100
7097
  this.dispatchEvent("resume", undefined);
7101
7098
  };
7099
+ dispatchAbort = (reason) => {
7100
+ this.dispatchEvent("abort", { reason });
7101
+ };
7102
+ }
7103
+
7104
+ // src/errors.ts
7105
+ class IsAGifError extends Error {
7106
+ mimeType;
7107
+ sizeInBytes;
7108
+ fileName;
7109
+ constructor({
7110
+ message,
7111
+ mimeType,
7112
+ sizeInBytes,
7113
+ fileName
7114
+ }) {
7115
+ super(message);
7116
+ this.fileName = "IsAGifError";
7117
+ this.mimeType = mimeType;
7118
+ this.sizeInBytes = sizeInBytes;
7119
+ this.fileName = fileName;
7120
+ if (Error.captureStackTrace) {
7121
+ Error.captureStackTrace(this, IsAGifError);
7122
+ }
7123
+ }
7124
+ }
7125
+
7126
+ class IsAnImageError extends Error {
7127
+ imageType;
7128
+ dimensions;
7129
+ mimeType;
7130
+ sizeInBytes;
7131
+ fileName;
7132
+ constructor({
7133
+ dimensions,
7134
+ imageType,
7135
+ message,
7136
+ mimeType,
7137
+ sizeInBytes,
7138
+ fileName
7139
+ }) {
7140
+ super(message);
7141
+ this.name = "IsAnImageError";
7142
+ this.imageType = imageType;
7143
+ this.dimensions = dimensions;
7144
+ this.mimeType = mimeType;
7145
+ this.sizeInBytes = sizeInBytes;
7146
+ this.fileName = fileName;
7147
+ if (Error.captureStackTrace) {
7148
+ Error.captureStackTrace(this, IsAnImageError);
7149
+ }
7150
+ }
7151
+ }
7152
+
7153
+ class IsAPdfError extends Error {
7154
+ mimeType;
7155
+ sizeInBytes;
7156
+ fileName;
7157
+ constructor({
7158
+ message,
7159
+ mimeType,
7160
+ sizeInBytes,
7161
+ fileName
7162
+ }) {
7163
+ super(message);
7164
+ this.name = "IsAPdfError";
7165
+ this.mimeType = mimeType;
7166
+ this.sizeInBytes = sizeInBytes;
7167
+ this.fileName = fileName;
7168
+ if (Error.captureStackTrace) {
7169
+ Error.captureStackTrace(this, IsAPdfError);
7170
+ }
7171
+ }
7172
+ }
7173
+
7174
+ class IsAnUnsupportedFileTypeError extends Error {
7175
+ mimeType;
7176
+ sizeInBytes;
7177
+ fileName;
7178
+ constructor({
7179
+ message,
7180
+ mimeType,
7181
+ sizeInBytes,
7182
+ fileName
7183
+ }) {
7184
+ super(message);
7185
+ this.name = "IsAnUnsupportedFileTypeError";
7186
+ this.mimeType = mimeType;
7187
+ this.sizeInBytes = sizeInBytes;
7188
+ this.fileName = fileName;
7189
+ if (Error.captureStackTrace) {
7190
+ Error.captureStackTrace(this, IsAnUnsupportedFileTypeError);
7191
+ }
7192
+ }
7193
+ }
7194
+
7195
+ class IsAnUnsupportedAudioTypeError extends Error {
7196
+ mimeType;
7197
+ sizeInBytes;
7198
+ fileName;
7199
+ audioType;
7200
+ constructor({
7201
+ message,
7202
+ mimeType,
7203
+ sizeInBytes,
7204
+ fileName,
7205
+ audioType
7206
+ }) {
7207
+ super(message);
7208
+ this.name = "IsAnUnsupportedAudioTypeError";
7209
+ this.mimeType = mimeType;
7210
+ this.sizeInBytes = sizeInBytes;
7211
+ this.fileName = fileName;
7212
+ this.audioType = audioType;
7213
+ if (Error.captureStackTrace) {
7214
+ Error.captureStackTrace(this, IsAnUnsupportedAudioTypeError);
7215
+ }
7216
+ }
7217
+ }
7218
+
7219
+ class MediaParserAbortError extends Error {
7220
+ constructor(message) {
7221
+ super(message);
7222
+ this.name = "MediaParserAbortError";
7223
+ this.cause = undefined;
7224
+ }
7102
7225
  }
7226
+ var hasBeenAborted = (error) => {
7227
+ return error instanceof MediaParserAbortError;
7228
+ };
7103
7229
 
7104
7230
  // src/pause-signal.ts
7105
7231
  var makePauseSignal = (emitter) => {
@@ -7143,13 +7269,14 @@ var mediaParserController = () => {
7143
7269
  const pauseSignal = makePauseSignal(emitter);
7144
7270
  const checkForAbortAndPause = async () => {
7145
7271
  if (abortController.signal.aborted) {
7146
- throw new Error("Aborted");
7272
+ throw new MediaParserAbortError("Aborted");
7147
7273
  }
7148
7274
  await pauseSignal.waitUntilResume();
7149
7275
  };
7150
7276
  return {
7151
7277
  abort: (reason) => {
7152
7278
  abortController.abort(reason);
7279
+ emitter.dispatchAbort(reason);
7153
7280
  },
7154
7281
  pause: pauseSignal.pause,
7155
7282
  resume: pauseSignal.resume,
@@ -7279,7 +7406,7 @@ var parseAac = async (state) => {
7279
7406
  iterator.counter.decrement(iterator.counter.getOffset() - startOffset);
7280
7407
  const data = iterator.getSlice(frameLength);
7281
7408
  if (state.callbacks.tracks.getTracks().length === 0) {
7282
- await registerTrack({
7409
+ await registerAudioTrack({
7283
7410
  state,
7284
7411
  container: "aac",
7285
7412
  track: {
@@ -7656,7 +7783,7 @@ var parseStreamInfo = async ({
7656
7783
  totalSamples
7657
7784
  };
7658
7785
  state.getFlacStructure().boxes.push(flacStreamInfo);
7659
- await registerTrack({
7786
+ await registerAudioTrack({
7660
7787
  container: "flac",
7661
7788
  state,
7662
7789
  track: {
@@ -7814,7 +7941,8 @@ var emittedState = () => {
7814
7941
  numberOfAudioChannels: false,
7815
7942
  sampleRate: false,
7816
7943
  slowAudioBitrate: false,
7817
- slowVideoBitrate: false
7944
+ slowVideoBitrate: false,
7945
+ m3uStreams: false
7818
7946
  };
7819
7947
  return emittedFields;
7820
7948
  };
@@ -7889,6 +8017,52 @@ var eventLoopState = (logLevel) => {
7889
8017
  return { eventLoopBreakIfNeeded };
7890
8018
  };
7891
8019
 
8020
+ // src/state/m3u-state.ts
8021
+ var m3uState = () => {
8022
+ let selectedStream = null;
8023
+ let hasEmittedVideoTrack = false;
8024
+ let hasEmittedAudioTrack = false;
8025
+ let hasEmittedDoneWithTracks = false;
8026
+ let hasFinishedManifest = false;
8027
+ let readyToIterateOverM3u = false;
8028
+ let lastChunkProcessed = -1;
8029
+ let allChunksProcessed = false;
8030
+ return {
8031
+ setSelectedStream: (stream) => {
8032
+ selectedStream = stream;
8033
+ },
8034
+ getSelectedStream: () => selectedStream,
8035
+ setHasEmittedVideoTrack: (callback) => {
8036
+ hasEmittedVideoTrack = callback;
8037
+ },
8038
+ hasEmittedVideoTrack: () => hasEmittedVideoTrack,
8039
+ setHasEmittedAudioTrack: (callback) => {
8040
+ hasEmittedAudioTrack = callback;
8041
+ },
8042
+ hasEmittedAudioTrack: () => hasEmittedAudioTrack,
8043
+ setHasEmittedDoneWithTracks: () => {
8044
+ hasEmittedDoneWithTracks = true;
8045
+ },
8046
+ hasEmittedDoneWithTracks: () => hasEmittedDoneWithTracks,
8047
+ setReadyToIterateOverM3u: () => {
8048
+ readyToIterateOverM3u = true;
8049
+ },
8050
+ isReadyToIterateOverM3u: () => readyToIterateOverM3u,
8051
+ setLastChunkProcessed: (chunk) => {
8052
+ lastChunkProcessed = chunk;
8053
+ },
8054
+ getLastChunkProcessed: () => lastChunkProcessed,
8055
+ getAllChunksProcessed: () => allChunksProcessed,
8056
+ setAllChunksProcessed: () => {
8057
+ allChunksProcessed = true;
8058
+ },
8059
+ setHasFinishedManifest: () => {
8060
+ hasFinishedManifest = true;
8061
+ },
8062
+ hasFinishedManifest: () => hasFinishedManifest
8063
+ };
8064
+ };
8065
+
7892
8066
  // src/state/mp3.ts
7893
8067
  var makeMp3State = () => {
7894
8068
  let mp3Info = null;
@@ -7941,7 +8115,7 @@ var needsTracksForField = ({
7941
8115
  }
7942
8116
  return true;
7943
8117
  }
7944
- if (field === "audioCodec" || field === "durationInSeconds" || field === "slowDurationInSeconds" || field === "slowFps" || field === "fps" || field === "isHdr" || field === "rotation" || field === "structure" || field === "tracks" || field === "unrotatedDimensions" || field === "videoCodec" || field === "metadata" || field === "location" || field === "slowKeyframes" || field === "slowNumberOfFrames" || field === "keyframes" || field === "images" || field === "sampleRate" || field === "numberOfAudioChannels" || field === "slowAudioBitrate" || field === "slowVideoBitrate") {
8118
+ if (field === "audioCodec" || field === "durationInSeconds" || field === "slowDurationInSeconds" || field === "slowFps" || field === "fps" || field === "isHdr" || field === "rotation" || field === "structure" || field === "tracks" || field === "unrotatedDimensions" || field === "videoCodec" || field === "metadata" || field === "location" || field === "slowKeyframes" || field === "slowNumberOfFrames" || field === "keyframes" || field === "images" || field === "sampleRate" || field === "numberOfAudioChannels" || field === "slowAudioBitrate" || field === "slowVideoBitrate" || field === "m3uStreams") {
7945
8119
  return true;
7946
8120
  }
7947
8121
  if (field === "container" || field === "internalStats" || field === "mimeType" || field === "name" || field === "size") {
@@ -8224,6 +8398,13 @@ var structureState = () => {
8224
8398
  }
8225
8399
  return struc;
8226
8400
  },
8401
+ getM3uStructure: () => {
8402
+ const struc = getStructure();
8403
+ if (struc.type !== "m3u") {
8404
+ throw new Error("Invalid structure type");
8405
+ }
8406
+ return struc;
8407
+ },
8227
8408
  getRiffStructure: () => {
8228
8409
  const struc = getStructure();
8229
8410
  if (struc.type !== "riff") {
@@ -8411,7 +8592,8 @@ var makeParserState = ({
8411
8592
  mode,
8412
8593
  src,
8413
8594
  readerInterface,
8414
- onDiscardedData
8595
+ onDiscardedData,
8596
+ selectM3uStreamFn
8415
8597
  }) => {
8416
8598
  let skippedBytes = 0;
8417
8599
  const iterator = getArrayBufferIterator(new Uint8Array([]), contentLength);
@@ -8441,6 +8623,7 @@ var makeParserState = ({
8441
8623
  mp3Info,
8442
8624
  aac: aacState(),
8443
8625
  flac: flacState(),
8626
+ m3u: m3uState(),
8444
8627
  callbacks: sampleCallback({
8445
8628
  controller,
8446
8629
  hasAudioTrackHandlers,
@@ -8474,7 +8657,8 @@ var makeParserState = ({
8474
8657
  eventLoop: eventLoopState(logLevel),
8475
8658
  src,
8476
8659
  readerInterface,
8477
- discardReadBytes
8660
+ discardReadBytes,
8661
+ selectM3uStreamFn
8478
8662
  };
8479
8663
  };
8480
8664
 
@@ -8498,11 +8682,11 @@ var getMoovAtom = async ({
8498
8682
  structure: true
8499
8683
  },
8500
8684
  onAudioTrack: state.onAudioTrack ? async ({ track, container }) => {
8501
- await registerTrack({ state, track, container });
8685
+ await registerAudioTrack({ state, track, container });
8502
8686
  return null;
8503
8687
  } : null,
8504
8688
  onVideoTrack: state.onVideoTrack ? async ({ track, container }) => {
8505
- await registerTrack({ state, track, container });
8689
+ await registerVideoTrack({ state, track, container });
8506
8690
  return null;
8507
8691
  } : null,
8508
8692
  contentLength: state.contentLength,
@@ -8510,7 +8694,8 @@ var getMoovAtom = async ({
8510
8694
  mode: "query",
8511
8695
  readerInterface: state.readerInterface,
8512
8696
  src: state.src,
8513
- onDiscardedData: null
8697
+ onDiscardedData: null,
8698
+ selectM3uStreamFn: state.selectM3uStreamFn
8514
8699
  });
8515
8700
  while (true) {
8516
8701
  const result = await reader.reader.read();
@@ -8549,85 +8734,699 @@ var parseMdatSection = async (state) => {
8549
8734
  if (maySkipVideoData({ state })) {
8550
8735
  return makeSkip(endOfMdat);
8551
8736
  }
8552
- const alreadyHas = getHasTracks(state);
8553
- if (!alreadyHas) {
8554
- const moov = await getMoovAtom({
8555
- endOfMdat,
8556
- state
8737
+ const alreadyHas = getHasTracks(state);
8738
+ if (!alreadyHas) {
8739
+ const moov = await getMoovAtom({
8740
+ endOfMdat,
8741
+ state
8742
+ });
8743
+ state.iso.moov.setMoovBox(moov);
8744
+ state.callbacks.tracks.setIsDone(state.logLevel);
8745
+ state.getIsoStructure().boxes.push(moov);
8746
+ return parseMdatSection(state);
8747
+ }
8748
+ if (!state.iso.flatSamples.getSamples(videoSection.start)) {
8749
+ state.iso.flatSamples.setSamples(videoSection.start, calculateFlatSamples(state));
8750
+ }
8751
+ const flatSamples = state.iso.flatSamples.getSamples(videoSection.start);
8752
+ const { iterator } = state;
8753
+ const samplesWithIndex = flatSamples.find((sample) => {
8754
+ return sample.samplePosition.offset === iterator.counter.getOffset();
8755
+ });
8756
+ if (!samplesWithIndex) {
8757
+ const nextSample_ = flatSamples.filter((s) => s.samplePosition.offset > iterator.counter.getOffset()).sort((a, b) => a.samplePosition.offset - b.samplePosition.offset)[0];
8758
+ if (nextSample_) {
8759
+ iterator.discard(nextSample_.samplePosition.offset - iterator.counter.getOffset());
8760
+ return null;
8761
+ }
8762
+ return makeSkip(endOfMdat);
8763
+ }
8764
+ if (iterator.bytesRemaining() < samplesWithIndex.samplePosition.size) {
8765
+ return null;
8766
+ }
8767
+ const bytes = iterator.getSlice(samplesWithIndex.samplePosition.size);
8768
+ const { cts, dts, duration: duration2, isKeyframe, offset } = samplesWithIndex.samplePosition;
8769
+ if (samplesWithIndex.track.type === "audio") {
8770
+ await state.callbacks.onAudioSample(samplesWithIndex.track.trackId, convertAudioOrVideoSampleToWebCodecsTimestamps({
8771
+ data: bytes,
8772
+ timestamp: cts,
8773
+ duration: duration2,
8774
+ cts,
8775
+ dts,
8776
+ trackId: samplesWithIndex.track.trackId,
8777
+ type: isKeyframe ? "key" : "delta",
8778
+ offset,
8779
+ timescale: samplesWithIndex.track.timescale
8780
+ }, samplesWithIndex.track.timescale));
8781
+ }
8782
+ if (samplesWithIndex.track.type === "video") {
8783
+ const nalUnitType = bytes[4] & 31;
8784
+ let isRecoveryPoint = false;
8785
+ if (nalUnitType === 6) {
8786
+ const seiType = bytes[5];
8787
+ isRecoveryPoint = seiType === 6;
8788
+ }
8789
+ await state.callbacks.onVideoSample(samplesWithIndex.track.trackId, convertAudioOrVideoSampleToWebCodecsTimestamps({
8790
+ data: bytes,
8791
+ timestamp: cts,
8792
+ duration: duration2,
8793
+ cts,
8794
+ dts,
8795
+ trackId: samplesWithIndex.track.trackId,
8796
+ type: isKeyframe && !isRecoveryPoint ? "key" : "delta",
8797
+ offset,
8798
+ timescale: samplesWithIndex.track.timescale
8799
+ }, samplesWithIndex.track.timescale));
8800
+ }
8801
+ return null;
8802
+ };
8803
+
8804
+ // src/containers/iso-base-media/parse-boxes.ts
8805
+ var parseIsoBaseMedia = async (state) => {
8806
+ const videoSectionState2 = state.videoSection.isInVideoSectionState(state.iterator);
8807
+ if (videoSectionState2 === "in-section") {
8808
+ const skipTo = await parseMdatSection(state);
8809
+ return skipTo;
8810
+ }
8811
+ const result = await processBox(state);
8812
+ if (result) {
8813
+ state.getIsoStructure().boxes.push(result);
8814
+ }
8815
+ return null;
8816
+ };
8817
+
8818
+ // src/containers/m3u/parse-stream-inf.ts
8819
+ function splitRespectingQuotes(input) {
8820
+ const result = [];
8821
+ let currentPart = "";
8822
+ let insideQuote = false;
8823
+ for (let i = 0;i < input.length; i++) {
8824
+ const char = input[i];
8825
+ if (char === '"') {
8826
+ insideQuote = !insideQuote;
8827
+ currentPart += char;
8828
+ } else if (char === "," && !insideQuote) {
8829
+ result.push(currentPart);
8830
+ currentPart = "";
8831
+ } else {
8832
+ currentPart += char;
8833
+ }
8834
+ }
8835
+ if (currentPart) {
8836
+ result.push(currentPart);
8837
+ }
8838
+ return result;
8839
+ }
8840
+ var parseStreamInf = (str) => {
8841
+ const quotes = splitRespectingQuotes(str);
8842
+ const map = {};
8843
+ for (const quote of quotes) {
8844
+ const firstColon = quote.indexOf("=");
8845
+ const key = firstColon === -1 ? quote : quote.slice(0, firstColon);
8846
+ const value = firstColon === -1 ? null : quote.slice(firstColon + 1);
8847
+ if (value === null) {
8848
+ throw new Error("Value is null");
8849
+ }
8850
+ const actualValue = value?.startsWith('"') && value?.endsWith('"') ? value.slice(1, -1) : value;
8851
+ map[key] = actualValue;
8852
+ }
8853
+ return {
8854
+ type: "m3u-stream-info",
8855
+ averageBandwidth: map["AVERAGE-BANDWIDTH"] ? parseInt(map["AVERAGE-BANDWIDTH"], 10) : null,
8856
+ bandwidth: map.BANDWIDTH ? parseInt(map.BANDWIDTH, 10) : null,
8857
+ codecs: map.CODECS ? map.CODECS.split(",") : null,
8858
+ resolution: map.RESOLUTION ? {
8859
+ width: parseInt(map.RESOLUTION.split("x")[0], 10),
8860
+ height: parseInt(map.RESOLUTION.split("x")[1], 10)
8861
+ } : null,
8862
+ audio: map.AUDIO || null
8863
+ };
8864
+ };
8865
+
8866
+ // src/containers/m3u/parse-m3u-media-directive.ts
8867
+ var parseM3uMediaDirective = (str) => {
8868
+ const quotes = splitRespectingQuotes(str);
8869
+ const map = {};
8870
+ for (const quote of quotes) {
8871
+ const firstColon = quote.indexOf("=");
8872
+ const key = firstColon === -1 ? quote : quote.slice(0, firstColon);
8873
+ const value = firstColon === -1 ? null : quote.slice(firstColon + 1);
8874
+ if (value === null) {
8875
+ throw new Error("Value is null");
8876
+ }
8877
+ const actualValue = value?.startsWith('"') && value?.endsWith('"') ? value.slice(1, -1) : value;
8878
+ map[key] = actualValue;
8879
+ }
8880
+ return {
8881
+ type: "m3u-media-info",
8882
+ autoselect: map.AUTOSELECT === "YES",
8883
+ channels: map.CHANNELS ? parseInt(map.CHANNELS, 10) : null,
8884
+ default: map.DEFAULT === "YES",
8885
+ groupId: map["GROUP-ID"],
8886
+ language: map.LANGUAGE || null,
8887
+ name: map.NAME || null,
8888
+ uri: map.URI
8889
+ };
8890
+ };
8891
+
8892
+ // src/containers/m3u/parse-directive.ts
8893
+ var parseM3uDirective = (str) => {
8894
+ const firstColon = str.indexOf(":");
8895
+ const directive = firstColon === -1 ? str : str.slice(0, firstColon);
8896
+ const value = firstColon === -1 ? null : str.slice(firstColon + 1);
8897
+ if (directive === "#EXT-X-VERSION") {
8898
+ if (!value) {
8899
+ throw new Error("EXT-X-VERSION directive must have a value");
8900
+ }
8901
+ return {
8902
+ type: "m3u-version",
8903
+ version: value
8904
+ };
8905
+ }
8906
+ if (directive === "#EXT-X-INDEPENDENT-SEGMENTS") {
8907
+ return {
8908
+ type: "m3u-independent-segments"
8909
+ };
8910
+ }
8911
+ if (directive === "#EXT-X-MEDIA") {
8912
+ if (!value) {
8913
+ throw new Error("EXT-X-MEDIA directive must have a value");
8914
+ }
8915
+ const parsed = parseM3uMediaDirective(value);
8916
+ return parsed;
8917
+ }
8918
+ if (directive === "#EXT-X-TARGETDURATION") {
8919
+ if (!value) {
8920
+ throw new Error("EXT-X-TARGETDURATION directive must have a value");
8921
+ }
8922
+ return {
8923
+ type: "m3u-target-duration",
8924
+ duration: parseFloat(value)
8925
+ };
8926
+ }
8927
+ if (directive === "#EXTINF") {
8928
+ if (!value) {
8929
+ throw new Error("EXTINF has no value");
8930
+ }
8931
+ return {
8932
+ type: "m3u-extinf",
8933
+ value: parseFloat(value)
8934
+ };
8935
+ }
8936
+ if (directive === "#EXT-X-ENDLIST") {
8937
+ return {
8938
+ type: "m3u-endlist"
8939
+ };
8940
+ }
8941
+ if (directive === "#EXT-X-PLAYLIST-TYPE") {
8942
+ if (!value) {
8943
+ throw new Error("#EXT-X-PLAYLIST-TYPE. directive must have a value");
8944
+ }
8945
+ return {
8946
+ type: "m3u-playlist-type",
8947
+ playlistType: value
8948
+ };
8949
+ }
8950
+ if (directive === "#EXT-X-STREAM-INF") {
8951
+ if (!value) {
8952
+ throw new Error("EXT-X-STREAM-INF directive must have a value");
8953
+ }
8954
+ const res = parseStreamInf(value);
8955
+ return res;
8956
+ }
8957
+ throw new Error(`Unknown directive ${directive}. Value: ${value}`);
8958
+ };
8959
+
8960
+ // src/containers/m3u/parse-m3u8-text.ts
8961
+ var parseM3u8Text = (line, boxes) => {
8962
+ if (line === "#EXTM3U") {
8963
+ boxes.push({
8964
+ type: "m3u-header"
8965
+ });
8966
+ return;
8967
+ }
8968
+ if (line.startsWith("#")) {
8969
+ boxes.push(parseM3uDirective(line));
8970
+ return;
8971
+ }
8972
+ if (line.trim()) {
8973
+ boxes.push({
8974
+ type: "m3u-text-value",
8975
+ value: line
8976
+ });
8977
+ }
8978
+ };
8979
+
8980
+ // src/containers/m3u/fetch-m3u8-stream.ts
8981
+ var fetchM3u8Stream = async (stream) => {
8982
+ const res = await fetch(stream.url);
8983
+ if (!res.ok) {
8984
+ throw new Error(`Failed to fetch ${stream.url} (HTTP code: ${res.status})`);
8985
+ }
8986
+ const text = await res.text();
8987
+ const lines = text.split(`
8988
+ `);
8989
+ const boxes = [];
8990
+ for (const line of lines) {
8991
+ parseM3u8Text(line, boxes);
8992
+ }
8993
+ return boxes;
8994
+ };
8995
+
8996
+ // src/forward-controller.ts
8997
+ var forwardMediaParserController = ({
8998
+ parentController,
8999
+ childController
9000
+ }) => {
9001
+ const onAbort = ({ detail }) => {
9002
+ childController.abort(detail.reason);
9003
+ };
9004
+ const onResume = () => {
9005
+ childController.resume();
9006
+ };
9007
+ const onPause = () => {
9008
+ childController.pause();
9009
+ };
9010
+ parentController.addEventListener("abort", onAbort);
9011
+ parentController.addEventListener("resume", onResume);
9012
+ parentController.addEventListener("pause", onPause);
9013
+ return {
9014
+ cleanup: () => {
9015
+ parentController.removeEventListener("abort", onAbort);
9016
+ parentController.removeEventListener("resume", onResume);
9017
+ parentController.removeEventListener("pause", onPause);
9018
+ }
9019
+ };
9020
+ };
9021
+
9022
+ // src/containers/m3u/select-stream.ts
9023
+ var selectStream = async ({
9024
+ streams,
9025
+ fn
9026
+ }) => {
9027
+ if (streams.length < 1) {
9028
+ throw new Error("No streams found");
9029
+ }
9030
+ const selectedStreamId = await fn({ streams });
9031
+ const selectedStream = streams.find((stream) => stream.id === selectedStreamId);
9032
+ if (!selectedStream) {
9033
+ throw new Error(`No stream with the id ${selectedStreamId} found`);
9034
+ }
9035
+ return Promise.resolve(selectedStream);
9036
+ };
9037
+ var defaultSelectM3uStreamFn = ({ streams }) => {
9038
+ return Promise.resolve(streams[0].id);
9039
+ };
9040
+
9041
+ // src/readers/fetch/get-body-and-reader.ts
9042
+ var getLengthAndReader = async (endsWithM3u8, res, ownController) => {
9043
+ if (endsWithM3u8) {
9044
+ const text = await res.text();
9045
+ const encoded = new TextEncoder().encode(text);
9046
+ const stream = new ReadableStream({
9047
+ start(controller) {
9048
+ controller.enqueue(encoded);
9049
+ controller.close();
9050
+ }
9051
+ });
9052
+ return {
9053
+ contentLength: encoded.byteLength,
9054
+ reader: {
9055
+ reader: stream.getReader(),
9056
+ abort() {
9057
+ ownController.abort();
9058
+ }
9059
+ },
9060
+ needsContentRange: false
9061
+ };
9062
+ }
9063
+ const length = res.headers.get("content-length");
9064
+ const contentLength = length === null ? null : parseInt(length, 10);
9065
+ if (!res.body) {
9066
+ throw new Error("No body");
9067
+ }
9068
+ const reader = res.body.getReader();
9069
+ return {
9070
+ reader: {
9071
+ reader,
9072
+ abort: () => {
9073
+ ownController.abort();
9074
+ }
9075
+ },
9076
+ contentLength,
9077
+ needsContentRange: true
9078
+ };
9079
+ };
9080
+
9081
+ // src/readers/fetch/resolve-url.ts
9082
+ var resolveUrl = (src) => {
9083
+ try {
9084
+ const resolvedUrl = typeof window !== "undefined" && typeof window.location !== "undefined" ? new URL(src, window.location.origin) : new URL(src);
9085
+ return resolvedUrl;
9086
+ } catch {
9087
+ return src;
9088
+ }
9089
+ };
9090
+
9091
+ // src/readers/from-fetch.ts
9092
+ function parseContentRange(input) {
9093
+ const matches = input.match(/^(\w+) ((\d+)-(\d+)|\*)\/(\d+|\*)$/);
9094
+ if (!matches)
9095
+ return null;
9096
+ const [, unit, , start, end, size] = matches;
9097
+ const range2 = {
9098
+ unit,
9099
+ start: start != null ? Number(start) : null,
9100
+ end: end != null ? Number(end) : null,
9101
+ size: size === "*" ? null : Number(size)
9102
+ };
9103
+ if (range2.start === null && range2.end === null && range2.size === null) {
9104
+ return null;
9105
+ }
9106
+ return range2;
9107
+ }
9108
+ var validateContentRangeAndDetectIfSupported = (actualRange, parsedContentRange, statusCode) => {
9109
+ if (statusCode === 206) {
9110
+ return { supportsContentRange: true };
9111
+ }
9112
+ if (typeof actualRange === "number" && parsedContentRange?.start !== actualRange) {
9113
+ if (actualRange === 0) {
9114
+ return { supportsContentRange: false };
9115
+ }
9116
+ throw new Error(`Range header (${actualRange}) does not match content-range header (${parsedContentRange?.start})`);
9117
+ }
9118
+ if (actualRange !== null && typeof actualRange !== "number" && (parsedContentRange?.start !== actualRange[0] || parsedContentRange?.end !== actualRange[1])) {
9119
+ throw new Error(`Range header (${actualRange}) does not match content-range header (${parsedContentRange?.start})`);
9120
+ }
9121
+ return { supportsContentRange: true };
9122
+ };
9123
+ var fetchReader = {
9124
+ read: async ({ src, range: range2, controller }) => {
9125
+ if (typeof src !== "string") {
9126
+ throw new Error("src must be a string when using `fetchReader`");
9127
+ }
9128
+ const resolvedUrl = resolveUrl(src);
9129
+ const resolvedUrlString = resolvedUrl.toString();
9130
+ if (!resolvedUrlString.startsWith("https://") && !resolvedUrlString.startsWith("blob:") && !resolvedUrlString.startsWith("http://")) {
9131
+ return Promise.reject(new Error(`${resolvedUrlString} is not a URL - needs to start with http:// or https:// or blob:. If you want to read a local file, pass \`reader: nodeReader\` to parseMedia().`));
9132
+ }
9133
+ const ownController = new AbortController;
9134
+ const cache = typeof navigator !== "undefined" && navigator.userAgent.includes("Cloudflare-Workers") ? undefined : "no-store";
9135
+ const actualRange = range2 === null ? 0 : range2;
9136
+ const endsWithM3u8 = (typeof resolvedUrl === "string" ? resolvedUrl : resolvedUrl.pathname).endsWith(".m3u8");
9137
+ const headers = actualRange === 0 && endsWithM3u8 ? {} : typeof actualRange === "number" ? {
9138
+ Range: `bytes=${actualRange}-`
9139
+ } : {
9140
+ Range: `bytes=${`${actualRange[0]}-${actualRange[1]}`}`
9141
+ };
9142
+ const res = await fetch(resolvedUrl, {
9143
+ headers,
9144
+ signal: ownController.signal,
9145
+ cache
9146
+ });
9147
+ const contentRange = res.headers.get("content-range");
9148
+ const parsedContentRange = contentRange ? parseContentRange(contentRange) : null;
9149
+ const { supportsContentRange } = validateContentRangeAndDetectIfSupported(actualRange, parsedContentRange, res.status);
9150
+ controller._internals.signal.addEventListener("abort", () => {
9151
+ ownController.abort(new MediaParserAbortError("Aborted by user"));
9152
+ }, { once: true });
9153
+ if (res.status.toString().startsWith("4") || res.status.toString().startsWith("5")) {
9154
+ throw new Error(`Server returned status code ${res.status} for ${src} and range ${actualRange}`);
9155
+ }
9156
+ const contentDisposition = res.headers.get("content-disposition");
9157
+ const name = contentDisposition?.match(/filename="([^"]+)"/)?.[1];
9158
+ const fallbackName = src.split("/").pop();
9159
+ const { contentLength, needsContentRange, reader } = await getLengthAndReader(endsWithM3u8, res, ownController);
9160
+ if (controller) {
9161
+ controller._internals.signal.addEventListener("abort", () => {
9162
+ reader.reader.cancel().catch(() => {
9163
+ });
9164
+ }, { once: true });
9165
+ }
9166
+ return {
9167
+ reader,
9168
+ contentLength,
9169
+ contentType: res.headers.get("content-type"),
9170
+ name: name ?? fallbackName,
9171
+ supportsContentRange,
9172
+ needsContentRange
9173
+ };
9174
+ }
9175
+ };
9176
+
9177
+ // src/parse-media.ts
9178
+ var parseMedia = (options) => {
9179
+ return internalParseMedia({
9180
+ fields: options.fields ?? null,
9181
+ logLevel: options.logLevel ?? "info",
9182
+ onAudioCodec: options.onAudioCodec ?? null,
9183
+ onAudioTrack: options.onAudioTrack ?? null,
9184
+ onContainer: options.onContainer ?? null,
9185
+ onDimensions: options.onDimensions ?? null,
9186
+ onDurationInSeconds: options.onDurationInSeconds ?? null,
9187
+ onFps: options.onFps ?? null,
9188
+ onImages: options.onImages ?? null,
9189
+ onInternalStats: options.onInternalStats ?? null,
9190
+ onIsHdr: options.onIsHdr ?? null,
9191
+ onKeyframes: options.onKeyframes ?? null,
9192
+ onLocation: options.onLocation ?? null,
9193
+ onMetadata: options.onMetadata ?? null,
9194
+ onMimeType: options.onMimeType ?? null,
9195
+ onName: options.onName ?? null,
9196
+ onNumberOfAudioChannels: options.onNumberOfAudioChannels ?? null,
9197
+ onParseProgress: options.onParseProgress ?? null,
9198
+ onRotation: options.onRotation ?? null,
9199
+ onSampleRate: options.onSampleRate ?? null,
9200
+ onSize: options.onSize ?? null,
9201
+ onSlowAudioBitrate: options.onSlowAudioBitrate ?? null,
9202
+ onSlowDurationInSeconds: options.onSlowDurationInSeconds ?? null,
9203
+ onSlowFps: options.onSlowFps ?? null,
9204
+ onSlowKeyframes: options.onSlowKeyframes ?? null,
9205
+ onSlowNumberOfFrames: options.onSlowNumberOfFrames ?? null,
9206
+ onSlowVideoBitrate: options.onSlowVideoBitrate ?? null,
9207
+ onStructure: options.onStructure ?? null,
9208
+ onM3uStreams: options.onM3uStreams ?? null,
9209
+ onTracks: options.onTracks ?? null,
9210
+ onUnrotatedDimensions: options.onUnrotatedDimensions ?? null,
9211
+ onVideoCodec: options.onVideoCodec ?? null,
9212
+ onVideoTrack: options.onVideoTrack ?? null,
9213
+ progressIntervalInMs: options.progressIntervalInMs ?? null,
9214
+ reader: options.reader ?? fetchReader,
9215
+ controller: options.controller ?? undefined,
9216
+ selectM3uStream: options.selectM3uStream ?? defaultSelectM3uStreamFn,
9217
+ src: options.src,
9218
+ mode: "query",
9219
+ onDiscardedData: null,
9220
+ onError: () => ({ action: "fail" }),
9221
+ acknowledgeRemotionLicense: Boolean(options.acknowledgeRemotionLicense),
9222
+ apiName: "parseMedia()"
9223
+ });
9224
+ };
9225
+
9226
+ // src/containers/m3u/get-chunks.ts
9227
+ var getChunks = (playlist) => {
9228
+ const chunks = [];
9229
+ for (let i = 0;i < playlist.boxes.length; i++) {
9230
+ const box = playlist.boxes[i];
9231
+ if (box.type === "m3u-extinf") {
9232
+ const nextBox = playlist.boxes[i + 1];
9233
+ i++;
9234
+ if (nextBox.type !== "m3u-text-value") {
9235
+ throw new Error("Expected m3u-text-value");
9236
+ }
9237
+ chunks.push({ duration: box.value, url: nextBox.value });
9238
+ }
9239
+ continue;
9240
+ }
9241
+ return chunks;
9242
+ };
9243
+
9244
+ // src/containers/m3u/return-packets.ts
9245
+ var iteratorOverTsFiles = async ({
9246
+ structure,
9247
+ onVideoTrack,
9248
+ m3uState: m3uState2,
9249
+ onAudioTrack,
9250
+ onDoneWithTracks,
9251
+ playlistUrl,
9252
+ logLevel,
9253
+ parentController
9254
+ }) => {
9255
+ const playlist = getPlaylist(structure);
9256
+ const chunks = getChunks(playlist);
9257
+ const lastChunkProcessed = m3uState2.getLastChunkProcessed();
9258
+ const chunkIndex = lastChunkProcessed + 1;
9259
+ const chunk = chunks[chunkIndex];
9260
+ const isLastChunk = chunkIndex === chunks.length - 1;
9261
+ const src = new URL(chunk.url, playlistUrl).toString();
9262
+ const childController = mediaParserController();
9263
+ const forwarded = forwardMediaParserController({
9264
+ childController,
9265
+ parentController
9266
+ });
9267
+ await parseMedia({
9268
+ src,
9269
+ acknowledgeRemotionLicense: true,
9270
+ logLevel,
9271
+ controller: childController,
9272
+ onTracks: () => {
9273
+ if (!m3uState2.hasEmittedDoneWithTracks()) {
9274
+ m3uState2.setHasEmittedDoneWithTracks();
9275
+ onDoneWithTracks();
9276
+ return null;
9277
+ }
9278
+ },
9279
+ onAudioTrack: async ({ track }) => {
9280
+ const callbackOrFalse = m3uState2.hasEmittedAudioTrack();
9281
+ if (callbackOrFalse === false) {
9282
+ const callback = await onAudioTrack(track);
9283
+ if (!callback) {
9284
+ m3uState2.setHasEmittedAudioTrack(null);
9285
+ return null;
9286
+ }
9287
+ m3uState2.setHasEmittedAudioTrack(callback);
9288
+ return (sample) => {
9289
+ return callback(sample);
9290
+ };
9291
+ }
9292
+ return callbackOrFalse;
9293
+ },
9294
+ onVideoTrack: async ({ track }) => {
9295
+ const callbackOrFalse = m3uState2.hasEmittedVideoTrack();
9296
+ if (callbackOrFalse === false) {
9297
+ const callback = await onVideoTrack(track);
9298
+ if (!callback) {
9299
+ m3uState2.setHasEmittedVideoTrack(null);
9300
+ return null;
9301
+ }
9302
+ m3uState2.setHasEmittedVideoTrack(callback);
9303
+ return (sample) => {
9304
+ return callback(sample);
9305
+ };
9306
+ }
9307
+ return callbackOrFalse;
9308
+ }
9309
+ });
9310
+ m3uState2.setLastChunkProcessed(chunkIndex);
9311
+ if (isLastChunk) {
9312
+ m3uState2.setAllChunksProcessed();
9313
+ }
9314
+ forwarded.cleanup();
9315
+ };
9316
+
9317
+ // src/containers/m3u/after-manifest-fetch.ts
9318
+ var afterManifestFetch = async ({
9319
+ structure,
9320
+ m3uState: m3uState2,
9321
+ src,
9322
+ selectM3uStreamFn
9323
+ }) => {
9324
+ const independentSegments = isIndependentSegments(structure);
9325
+ if (!independentSegments) {
9326
+ if (!src) {
9327
+ throw new Error("No src");
9328
+ }
9329
+ m3uState2.setSelectedStream({
9330
+ type: "initial-url",
9331
+ url: src
8557
9332
  });
8558
- state.iso.moov.setMoovBox(moov);
8559
- state.callbacks.tracks.setIsDone(state.logLevel);
8560
- state.getIsoStructure().boxes.push(moov);
8561
- return parseMdatSection(state);
9333
+ return m3uState2.setReadyToIterateOverM3u();
8562
9334
  }
8563
- if (!state.iso.flatSamples.getSamples(videoSection.start)) {
8564
- state.iso.flatSamples.setSamples(videoSection.start, calculateFlatSamples(state));
9335
+ const streams = getM3uStreams(structure, src);
9336
+ if (streams === null) {
9337
+ throw new Error("No streams found");
8565
9338
  }
8566
- const flatSamples = state.iso.flatSamples.getSamples(videoSection.start);
8567
- const { iterator } = state;
8568
- const samplesWithIndex = flatSamples.find((sample) => {
8569
- return sample.samplePosition.offset === iterator.counter.getOffset();
8570
- });
8571
- if (!samplesWithIndex) {
8572
- const nextSample_ = flatSamples.filter((s) => s.samplePosition.offset > iterator.counter.getOffset()).sort((a, b) => a.samplePosition.offset - b.samplePosition.offset)[0];
8573
- if (nextSample_) {
8574
- iterator.discard(nextSample_.samplePosition.offset - iterator.counter.getOffset());
8575
- return null;
8576
- }
8577
- return makeSkip(endOfMdat);
9339
+ const selectedStream = await selectStream({ streams, fn: selectM3uStreamFn });
9340
+ m3uState2.setSelectedStream({ type: "selected-stream", stream: selectedStream });
9341
+ if (!selectedStream.resolution) {
9342
+ throw new Error("Stream does not have a resolution");
8578
9343
  }
8579
- if (iterator.bytesRemaining() < samplesWithIndex.samplePosition.size) {
8580
- return null;
9344
+ const boxes = await fetchM3u8Stream(selectedStream);
9345
+ structure.boxes.push({ type: "m3u-playlist", boxes });
9346
+ m3uState2.setReadyToIterateOverM3u();
9347
+ };
9348
+ var runOverM3u = async ({
9349
+ state,
9350
+ structure
9351
+ }) => {
9352
+ const selectedStream = state.m3u.getSelectedStream();
9353
+ if (!selectedStream) {
9354
+ throw new Error("No stream selected");
8581
9355
  }
8582
- const bytes = iterator.getSlice(samplesWithIndex.samplePosition.size);
8583
- const { cts, dts, duration: duration2, isKeyframe, offset } = samplesWithIndex.samplePosition;
8584
- if (samplesWithIndex.track.type === "audio") {
8585
- await state.callbacks.onAudioSample(samplesWithIndex.track.trackId, convertAudioOrVideoSampleToWebCodecsTimestamps({
8586
- data: bytes,
8587
- timestamp: cts,
8588
- duration: duration2,
8589
- cts,
8590
- dts,
8591
- trackId: samplesWithIndex.track.trackId,
8592
- type: isKeyframe ? "key" : "delta",
8593
- offset,
8594
- timescale: samplesWithIndex.track.timescale
8595
- }, samplesWithIndex.track.timescale));
9356
+ await iteratorOverTsFiles({
9357
+ playlistUrl: selectedStream.type === "initial-url" ? selectedStream.url : selectedStream.stream.url,
9358
+ structure,
9359
+ logLevel: state.logLevel,
9360
+ onDoneWithTracks() {
9361
+ state.callbacks.tracks.setIsDone(state.logLevel);
9362
+ },
9363
+ onAudioTrack: (track) => {
9364
+ return registerAudioTrack({
9365
+ container: "m3u8",
9366
+ state,
9367
+ track
9368
+ });
9369
+ },
9370
+ onVideoTrack: (track) => {
9371
+ return registerVideoTrack({
9372
+ container: "m3u8",
9373
+ state,
9374
+ track
9375
+ });
9376
+ },
9377
+ m3uState: state.m3u,
9378
+ parentController: state.controller
9379
+ });
9380
+ };
9381
+
9382
+ // src/containers/m3u/parse-m3u-manifest.ts
9383
+ var parseM3uManifest = ({
9384
+ iterator,
9385
+ structure,
9386
+ contentLength
9387
+ }) => {
9388
+ const start = iterator.startCheckpoint();
9389
+ const line = iterator.readUntilLineEnd();
9390
+ if (iterator.counter.getOffset() > contentLength) {
9391
+ throw new Error("Unexpected end of file");
8596
9392
  }
8597
- if (samplesWithIndex.track.type === "video") {
8598
- const nalUnitType = bytes[4] & 31;
8599
- let isRecoveryPoint = false;
8600
- if (nalUnitType === 6) {
8601
- const seiType = bytes[5];
8602
- isRecoveryPoint = seiType === 6;
8603
- }
8604
- await state.callbacks.onVideoSample(samplesWithIndex.track.trackId, convertAudioOrVideoSampleToWebCodecsTimestamps({
8605
- data: bytes,
8606
- timestamp: cts,
8607
- duration: duration2,
8608
- cts,
8609
- dts,
8610
- trackId: samplesWithIndex.track.trackId,
8611
- type: isKeyframe && !isRecoveryPoint ? "key" : "delta",
8612
- offset,
8613
- timescale: samplesWithIndex.track.timescale
8614
- }, samplesWithIndex.track.timescale));
9393
+ if (line === null) {
9394
+ start.returnToCheckpoint();
9395
+ return Promise.resolve(null);
8615
9396
  }
8616
- return null;
9397
+ parseM3u8Text(line, structure.boxes);
9398
+ return Promise.resolve(null);
8617
9399
  };
8618
9400
 
8619
- // src/containers/iso-base-media/parse-boxes.ts
8620
- var parseIsoBaseMedia = async (state) => {
8621
- const videoSectionState2 = state.videoSection.isInVideoSectionState(state.iterator);
8622
- if (videoSectionState2 === "in-section") {
8623
- const skipTo = await parseMdatSection(state);
8624
- return skipTo;
9401
+ // src/containers/m3u/parse-m3u.ts
9402
+ var parseM3u = async ({ state }) => {
9403
+ const structure = state.getM3uStructure();
9404
+ if (state.m3u.isReadyToIterateOverM3u()) {
9405
+ await runOverM3u({
9406
+ state,
9407
+ structure
9408
+ });
9409
+ return null;
8625
9410
  }
8626
- const result = await processBox(state);
8627
- if (result) {
8628
- state.getIsoStructure().boxes.push(result);
9411
+ if (state.m3u.hasFinishedManifest()) {
9412
+ await afterManifestFetch({
9413
+ structure,
9414
+ m3uState: state.m3u,
9415
+ src: typeof state.src === "string" ? state.src : null,
9416
+ selectM3uStreamFn: state.selectM3uStreamFn
9417
+ });
9418
+ return null;
8629
9419
  }
8630
- return null;
9420
+ const box = await parseM3uManifest({
9421
+ iterator: state.iterator,
9422
+ structure,
9423
+ contentLength: state.contentLength
9424
+ });
9425
+ const isDoneNow = state.iterator.counter.getOffset() === state.contentLength;
9426
+ if (isDoneNow) {
9427
+ state.m3u.setHasFinishedManifest();
9428
+ }
9429
+ return box;
8631
9430
  };
8632
9431
 
8633
9432
  // src/containers/mp3/id3.ts
@@ -8926,7 +9725,7 @@ var parseMpegHeader = async ({
8926
9725
  bitrateKbit,
8927
9726
  startOfMpegStream: initialOffset
8928
9727
  });
8929
- await registerTrack({
9728
+ await registerAudioTrack({
8930
9729
  container: "mp3",
8931
9730
  state,
8932
9731
  track: {
@@ -9274,7 +10073,7 @@ var expectRiffBox = async (state) => {
9274
10073
  index: state.riff.getNextTrackIndex(),
9275
10074
  strf: box.strf
9276
10075
  });
9277
- await registerTrack({
10076
+ await registerAudioTrack({
9278
10077
  state,
9279
10078
  track: audioTrack,
9280
10079
  container: "avi"
@@ -9381,26 +10180,25 @@ var readSps = (iterator) => {
9381
10180
  let frame_crop_top_offset = null;
9382
10181
  let frame_crop_bottom_offset = null;
9383
10182
  let vui_parameters = null;
9384
- if (!(profile === 100 || profile === 110 || profile === 122 || profile === 244 || profile === 44 || profile === 83 || profile === 86 || profile === 118 || profile === 128 || profile === 138 || profile === 139 || profile === 134 || profile === 135)) {
9385
- throw new Error("Invalid profile");
9386
- }
9387
- const chromaFormat = iterator.readExpGolomb();
9388
- if (chromaFormat === 3) {
9389
- separate_colour_plane_flag = iterator.getBits(1);
9390
- }
9391
- bit_depth_luma_minus8 = iterator.readExpGolomb();
9392
- bit_depth_chroma_minus8 = iterator.readExpGolomb();
9393
- qpprime_y_zero_transform_bypass_flag = iterator.getBits(1);
9394
- const seq_scaling_matrix_present_flag = iterator.getBits(1);
9395
- const seq_scaling_list_present_flag = [];
9396
- if (seq_scaling_matrix_present_flag) {
9397
- for (let i = 0;i < (chromaFormat !== 3 ? 8 : 12); i++) {
9398
- seq_scaling_list_present_flag[i] = iterator.getBits(1);
9399
- if (seq_scaling_list_present_flag[i]) {
9400
- if (i < 6) {
9401
- throw new Error("Not implemented");
9402
- } else {
9403
- throw new Error("Not implemented");
10183
+ if (profile === 100 || profile === 110 || profile === 122 || profile === 244 || profile === 44 || profile === 83 || profile === 86 || profile === 118 || profile === 128 || profile === 138 || profile === 139 || profile === 134 || profile === 135) {
10184
+ const chromaFormat = iterator.readExpGolomb();
10185
+ if (chromaFormat === 3) {
10186
+ separate_colour_plane_flag = iterator.getBits(1);
10187
+ }
10188
+ bit_depth_luma_minus8 = iterator.readExpGolomb();
10189
+ bit_depth_chroma_minus8 = iterator.readExpGolomb();
10190
+ qpprime_y_zero_transform_bypass_flag = iterator.getBits(1);
10191
+ const seq_scaling_matrix_present_flag = iterator.getBits(1);
10192
+ const seq_scaling_list_present_flag = [];
10193
+ if (seq_scaling_matrix_present_flag) {
10194
+ for (let i = 0;i < (chromaFormat !== 3 ? 8 : 12); i++) {
10195
+ seq_scaling_list_present_flag[i] = iterator.getBits(1);
10196
+ if (seq_scaling_list_present_flag[i]) {
10197
+ if (i < 6) {
10198
+ throw new Error("Not implemented");
10199
+ } else {
10200
+ throw new Error("Not implemented");
10201
+ }
9404
10202
  }
9405
10203
  }
9406
10204
  }
@@ -9742,6 +10540,20 @@ var parsePat = (iterator) => {
9742
10540
  discardRestOfPacket(iterator);
9743
10541
  return tables;
9744
10542
  };
10543
+ var parseSdt = (iterator) => {
10544
+ iterator.startReadingBits();
10545
+ iterator.getBits(8);
10546
+ iterator.getBits(1);
10547
+ iterator.getBits(1);
10548
+ iterator.getBits(2);
10549
+ const sectionLength = iterator.getBits(12);
10550
+ iterator.stopReadingBits();
10551
+ iterator.discard(sectionLength);
10552
+ discardRestOfPacket(iterator);
10553
+ return {
10554
+ type: "transport-stream-sdt-box"
10555
+ };
10556
+ };
9745
10557
 
9746
10558
  // src/containers/transport-stream/parse-pes.ts
9747
10559
  var parsePes = (iterator) => {
@@ -9935,9 +10747,10 @@ var readAdtsHeader = (buffer) => {
9935
10747
  };
9936
10748
 
9937
10749
  // src/containers/transport-stream/find-separator.ts
9938
- function findSubarrayIndex(array, subarray) {
10750
+ function findNthSubarrayIndex(array, subarray, n) {
9939
10751
  const subarrayLength = subarray.length;
9940
10752
  const arrayLength = array.length;
10753
+ let count = 0;
9941
10754
  for (let i = 0;i <= arrayLength - subarrayLength; i++) {
9942
10755
  let match = true;
9943
10756
  for (let j = 0;j < subarrayLength; j++) {
@@ -9947,28 +10760,22 @@ function findSubarrayIndex(array, subarray) {
9947
10760
  }
9948
10761
  }
9949
10762
  if (match) {
9950
- if (subarray[i - 1] === 0) {
9951
- i--;
10763
+ count++;
10764
+ if (count === n) {
10765
+ return i;
9952
10766
  }
9953
- return i;
9954
10767
  }
9955
10768
  }
9956
10769
  return -1;
9957
10770
  }
9958
- var findNextSeparator = (restOfPacket, transportStreamEntry) => {
9959
- if (transportStreamEntry.streamType === 27) {
9960
- return findSubarrayIndex(restOfPacket, new Uint8Array([0, 0, 1, 9]));
9961
- }
9962
- throw new Error(`Unsupported stream ID ${transportStreamEntry.streamType}`);
9963
- };
9964
10771
 
9965
10772
  // src/containers/avc/interpret-sps.ts
9966
10773
  var getDimensionsFromSps = (sps) => {
9967
10774
  const height = sps.pic_height_in_map_units_minus1;
9968
10775
  const width = sps.pic_width_in_mbs_minus1;
9969
10776
  return {
9970
- height: (height + 1) * 16,
9971
- width: (width + 1) * 16
10777
+ height: (height + 1) * 16 - (sps.frame_crop_bottom_offset ?? 0) * 2 - (sps.frame_crop_top_offset ?? 0) * 2,
10778
+ width: (width + 1) * 16 - (sps.frame_crop_right_offset ?? 0) * 2 - (sps.frame_crop_left_offset ?? 0) * 2
9972
10779
  };
9973
10780
  };
9974
10781
  var getSampleAspectRatioFromSps = (sps) => {
@@ -10044,7 +10851,7 @@ var handleAvcPacket = async ({
10044
10851
  },
10045
10852
  color: getVideoColorFromSps(spsAndPps.sps.spsData)
10046
10853
  };
10047
- await registerTrack({ track, state, container: "transport-stream" });
10854
+ await registerVideoTrack({ track, state, container: "transport-stream" });
10048
10855
  }
10049
10856
  const sample = {
10050
10857
  cts: streamBuffer.pesHeader.pts,
@@ -10088,7 +10895,7 @@ var handleAacPacket = async ({
10088
10895
  numberOfChannels: channelConfiguration,
10089
10896
  sampleRate
10090
10897
  };
10091
- await registerTrack({
10898
+ await registerAudioTrack({
10092
10899
  track,
10093
10900
  state,
10094
10901
  container: "transport-stream"
@@ -10161,36 +10968,35 @@ var processFinalStreamBuffers = async ({
10161
10968
 
10162
10969
  // src/containers/transport-stream/parse-stream-packet.ts
10163
10970
  var parseAdtsStream = async ({
10164
- restOfPacket,
10165
10971
  transportStreamEntry,
10166
10972
  state,
10167
10973
  structure,
10168
10974
  offset
10169
10975
  }) => {
10170
10976
  const { streamBuffers, nextPesHeaderStore: nextPesHeader } = state.transportStream;
10171
- const streamBuffer = streamBuffers.get(transportStreamEntry.pid);
10172
- if (!streamBuffer) {
10173
- streamBuffers.set(transportStreamEntry.pid, {
10174
- buffer: restOfPacket,
10175
- pesHeader: nextPesHeader.getNextPesHeader(),
10176
- offset
10177
- });
10178
- return;
10179
- }
10180
- const expectedLength = readAdtsHeader(streamBuffer.buffer)?.frameLength ?? null;
10181
- const bytesToTake = expectedLength ? Math.min(restOfPacket.length, expectedLength - streamBuffer.buffer.byteLength) : restOfPacket.length;
10182
- streamBuffer.buffer = combineUint8Arrays([
10183
- streamBuffer.buffer,
10184
- restOfPacket.slice(0, bytesToTake)
10185
- ]);
10186
- if (expectedLength === streamBuffer.buffer.byteLength) {
10977
+ while (true) {
10978
+ const streamBuffer = streamBuffers.get(transportStreamEntry.pid);
10979
+ if (!streamBuffer) {
10980
+ throw new Error("Stream buffer not found");
10981
+ }
10982
+ const expectedLength = readAdtsHeader(streamBuffer.buffer)?.frameLength ?? null;
10983
+ if (expectedLength === null) {
10984
+ break;
10985
+ }
10986
+ if (expectedLength > streamBuffer.buffer.length) {
10987
+ break;
10988
+ }
10187
10989
  await processStreamBuffer({
10188
- streamBuffer,
10990
+ streamBuffer: {
10991
+ buffer: streamBuffer.buffer.slice(0, expectedLength),
10992
+ offset,
10993
+ pesHeader: streamBuffer.pesHeader
10994
+ },
10189
10995
  programId: transportStreamEntry.pid,
10190
10996
  state,
10191
10997
  structure
10192
10998
  });
10193
- const rest = restOfPacket.slice(bytesToTake);
10999
+ const rest = streamBuffer.buffer.slice(expectedLength);
10194
11000
  streamBuffers.set(transportStreamEntry.pid, {
10195
11001
  buffer: rest,
10196
11002
  pesHeader: nextPesHeader.getNextPesHeader(),
@@ -10199,82 +11005,91 @@ var parseAdtsStream = async ({
10199
11005
  }
10200
11006
  };
10201
11007
  var parseAvcStream = async ({
10202
- restOfPacket,
10203
- transportStreamEntry,
10204
11008
  programId,
10205
11009
  state,
10206
11010
  structure,
10207
- offset
11011
+ streamBuffer
10208
11012
  }) => {
10209
- const indexOfSeparator = findNextSeparator(restOfPacket, transportStreamEntry);
10210
- const { streamBuffers, nextPesHeaderStore: nextPesHeader } = state.transportStream;
10211
- const streamBuffer = streamBuffers.get(transportStreamEntry.pid);
10212
- if (indexOfSeparator === -1) {
10213
- if (streamBuffer) {
10214
- streamBuffer.buffer = combineUint8Arrays([
10215
- streamBuffer.buffer,
10216
- restOfPacket
10217
- ]);
10218
- return;
10219
- }
10220
- streamBuffers.set(programId, {
10221
- pesHeader: nextPesHeader.getNextPesHeader(),
10222
- buffer: restOfPacket,
10223
- offset
10224
- });
10225
- return;
10226
- }
10227
- if (streamBuffer) {
10228
- const packet = restOfPacket.slice(0, indexOfSeparator);
10229
- streamBuffer.buffer = combineUint8Arrays([streamBuffer.buffer, packet]);
10230
- await processStreamBuffer({
10231
- state,
10232
- streamBuffer,
10233
- programId,
10234
- structure
10235
- });
10236
- const rest = restOfPacket.slice(indexOfSeparator);
10237
- streamBuffers.set(programId, {
10238
- pesHeader: nextPesHeader.getNextPesHeader(),
10239
- buffer: rest,
10240
- offset
10241
- });
10242
- return;
10243
- }
10244
- if (indexOfSeparator !== 0) {
10245
- throw new Error("No stream buffer found but new separator is not at the beginning");
11013
+ const indexOfSeparator = findNthSubarrayIndex(streamBuffer.buffer, new Uint8Array([0, 0, 1, 9]), 2);
11014
+ if (indexOfSeparator === -1 || indexOfSeparator === 0) {
11015
+ return null;
10246
11016
  }
10247
- streamBuffers.set(programId, {
10248
- pesHeader: nextPesHeader.getNextPesHeader(),
10249
- buffer: restOfPacket.slice(indexOfSeparator),
10250
- offset
11017
+ const packet = streamBuffer.buffer.slice(0, indexOfSeparator);
11018
+ const rest = streamBuffer.buffer.slice(indexOfSeparator);
11019
+ await processStreamBuffer({
11020
+ state,
11021
+ streamBuffer: {
11022
+ offset: streamBuffer.offset,
11023
+ pesHeader: streamBuffer.pesHeader,
11024
+ buffer: packet
11025
+ },
11026
+ programId,
11027
+ structure
10251
11028
  });
11029
+ return rest;
10252
11030
  };
10253
- var parseStream = ({
11031
+ var parseStream = async ({
10254
11032
  transportStreamEntry,
10255
11033
  state,
10256
11034
  programId,
10257
11035
  structure
10258
11036
  }) => {
10259
11037
  const { iterator } = state;
10260
- const restOfPacket = getRestOfPacket(iterator);
11038
+ let restOfPacket = getRestOfPacket(iterator);
11039
+ const offset = iterator.counter.getOffset();
10261
11040
  if (transportStreamEntry.streamType === 27) {
10262
- return parseAvcStream({
10263
- restOfPacket,
10264
- transportStreamEntry,
10265
- state,
10266
- programId,
10267
- structure,
10268
- offset: iterator.counter.getOffset()
10269
- });
11041
+ const { streamBuffers, nextPesHeaderStore: nextPesHeader } = state.transportStream;
11042
+ while (true) {
11043
+ if (!streamBuffers.has(transportStreamEntry.pid)) {
11044
+ streamBuffers.set(programId, {
11045
+ pesHeader: nextPesHeader.getNextPesHeader(),
11046
+ buffer: new Uint8Array([]),
11047
+ offset
11048
+ });
11049
+ }
11050
+ const streamBuffer = streamBuffers.get(transportStreamEntry.pid);
11051
+ streamBuffer.buffer = combineUint8Arrays([
11052
+ streamBuffer.buffer,
11053
+ restOfPacket
11054
+ ]);
11055
+ const rest = await parseAvcStream({
11056
+ state,
11057
+ programId,
11058
+ structure,
11059
+ streamBuffer: streamBuffers.get(transportStreamEntry.pid)
11060
+ });
11061
+ if (rest !== null) {
11062
+ streamBuffers.delete(transportStreamEntry.pid);
11063
+ if (rest.length === 0) {
11064
+ break;
11065
+ }
11066
+ restOfPacket = rest;
11067
+ } else {
11068
+ break;
11069
+ }
11070
+ }
11071
+ return;
10270
11072
  }
10271
11073
  if (transportStreamEntry.streamType === 15) {
11074
+ const { streamBuffers, nextPesHeaderStore: nextPesHeader } = state.transportStream;
11075
+ const streamBuffer = streamBuffers.get(transportStreamEntry.pid);
11076
+ if (!streamBuffer) {
11077
+ streamBuffers.set(transportStreamEntry.pid, {
11078
+ buffer: restOfPacket,
11079
+ pesHeader: nextPesHeader.getNextPesHeader(),
11080
+ offset
11081
+ });
11082
+ } else {
11083
+ streamBuffer.buffer = combineUint8Arrays([
11084
+ streamBuffer.buffer,
11085
+ restOfPacket
11086
+ ]);
11087
+ }
10272
11088
  return parseAdtsStream({
10273
- restOfPacket,
10274
11089
  transportStreamEntry,
10275
11090
  state,
10276
11091
  structure,
10277
- offset: iterator.counter.getOffset()
11092
+ offset
10278
11093
  });
10279
11094
  }
10280
11095
  throw new Error(`Unsupported stream type ${transportStreamEntry.streamType}`);
@@ -10335,7 +11150,10 @@ var parsePacket = async ({
10335
11150
  if (programId === 0) {
10336
11151
  return Promise.resolve(parsePat(iterator));
10337
11152
  }
10338
- const program = getProgramForId(structure, programId);
11153
+ if (programId === 17) {
11154
+ return Promise.resolve(parseSdt(iterator));
11155
+ }
11156
+ const program = programId === 17 ? null : getProgramForId(structure, programId);
10339
11157
  if (program) {
10340
11158
  const pmt = parsePmt(iterator);
10341
11159
  return Promise.resolve(pmt);
@@ -10426,7 +11244,7 @@ var parseFmt = async ({
10426
11244
  type: "wav-fmt"
10427
11245
  };
10428
11246
  state.getWavStructure().boxes.push(wavHeader);
10429
- await registerTrack({
11247
+ await registerAudioTrack({
10430
11248
  state,
10431
11249
  track: {
10432
11250
  type: "audio",
@@ -10764,6 +11582,14 @@ var initVideo = ({
10764
11582
  });
10765
11583
  return;
10766
11584
  }
11585
+ if (fileType.type === "m3u") {
11586
+ Log.verbose(state.logLevel, "Detected M3U");
11587
+ state.setStructure({
11588
+ type: "m3u",
11589
+ boxes: []
11590
+ });
11591
+ return;
11592
+ }
10767
11593
  if (fileType.type === "gif") {
10768
11594
  return Promise.reject(new IsAGifError({
10769
11595
  message: "GIF files are not yet supported",
@@ -10808,10 +11634,13 @@ var runParseIteration = async ({
10808
11634
  contentLength,
10809
11635
  name
10810
11636
  }) => {
11637
+ const structure = state.getStructureOrNull();
11638
+ if (structure && structure.type === "m3u") {
11639
+ return parseM3u({ state });
11640
+ }
10811
11641
  if (state.iterator.bytesRemaining() === 0) {
10812
11642
  return Promise.reject(new Error("no bytes"));
10813
11643
  }
10814
- const structure = state.getStructureOrNull();
10815
11644
  if (structure === null) {
10816
11645
  await initVideo({ state, mimeType, name, contentLength });
10817
11646
  return null;
@@ -10906,6 +11735,7 @@ var internalParseMedia = async function({
10906
11735
  onError,
10907
11736
  acknowledgeRemotionLicense,
10908
11737
  apiName,
11738
+ selectM3uStream: selectM3uStreamFn,
10909
11739
  ...more
10910
11740
  }) {
10911
11741
  warnIfRemotionLicenseNotAcknowledged({
@@ -10918,17 +11748,19 @@ var internalParseMedia = async function({
10918
11748
  fields: fieldsInReturnValue,
10919
11749
  callbacks: more
10920
11750
  });
11751
+ Log.verbose(logLevel, `Reading ${typeof src === "string" ? src : src.name}`);
10921
11752
  const {
10922
11753
  reader: readerInstance,
10923
11754
  contentLength,
10924
11755
  name,
10925
11756
  contentType,
10926
- supportsContentRange
11757
+ supportsContentRange,
11758
+ needsContentRange
10927
11759
  } = await readerInterface.read({ src, range: null, controller });
10928
11760
  if (contentLength === null) {
10929
11761
  throw new Error('Cannot read media without a content length. This is currently not supported. Ensure the media has a "Content-Length" HTTP header.');
10930
11762
  }
10931
- if (!supportsContentRange) {
11763
+ if (!supportsContentRange && needsContentRange) {
10932
11764
  throw new Error('Cannot read media without it supporting the "Content-Range" header. This is currently not supported. Ensure the media supports the "Content-Range" HTTP header.');
10933
11765
  }
10934
11766
  const hasAudioTrackHandlers = Boolean(onAudioTrack);
@@ -10954,7 +11786,8 @@ var internalParseMedia = async function({
10954
11786
  mode,
10955
11787
  readerInterface,
10956
11788
  src,
10957
- onDiscardedData
11789
+ onDiscardedData,
11790
+ selectM3uStreamFn
10958
11791
  });
10959
11792
  const { iterator } = state;
10960
11793
  let currentReader = readerInstance;
@@ -10994,6 +11827,9 @@ var internalParseMedia = async function({
10994
11827
  return true;
10995
11828
  }
10996
11829
  if (state.iterator.counter.getOffset() === contentLength) {
11830
+ if (state.getStructure().type === "m3u" && !state.m3u.getAllChunksProcessed()) {
11831
+ return false;
11832
+ }
10997
11833
  Log.verbose(logLevel, "Reached end of file");
10998
11834
  await state.discardReadBytes(true);
10999
11835
  return true;
@@ -11037,7 +11873,7 @@ var internalParseMedia = async function({
11037
11873
  }));
11038
11874
  if (!errored) {
11039
11875
  Log.trace(logLevel, `Continuing parsing of file, currently at position ${iterator.counter.getOffset()}/${contentLength} (0x${iterator.counter.getOffset().toString(16)})`);
11040
- if (iterationWithThisOffset > 300) {
11876
+ if (iterationWithThisOffset > 300 && state.getStructure().type !== "m3u") {
11041
11877
  throw new Error("Infinite loop detected. The parser is not progressing. This is likely a bug in the parser. You can report this at https://remotion.dev/report and we will fix it as soon as possible.");
11042
11878
  }
11043
11879
  try {
@@ -11135,6 +11971,7 @@ var downloadAndParseMedia = async (options) => {
11135
11971
  onAudioTrack: null,
11136
11972
  onContainer: options.onContainer ?? null,
11137
11973
  onDimensions: options.onDimensions ?? null,
11974
+ selectM3uStream: options.selectM3uStream ?? defaultSelectM3uStreamFn,
11138
11975
  onDiscardedData: async (data) => {
11139
11976
  await content.write(data);
11140
11977
  },
@@ -11160,6 +11997,7 @@ var downloadAndParseMedia = async (options) => {
11160
11997
  onSlowNumberOfFrames: options.onSlowNumberOfFrames ?? null,
11161
11998
  onSlowVideoBitrate: options.onSlowVideoBitrate ?? null,
11162
11999
  onStructure: options.onStructure ?? null,
12000
+ onM3uStreams: options.onM3uStreams ?? null,
11163
12001
  onTracks: options.onTracks ?? null,
11164
12002
  onUnrotatedDimensions: options.onUnrotatedDimensions ?? null,
11165
12003
  onVideoCodec: options.onVideoCodec ?? null,
@@ -11183,54 +12021,8 @@ var downloadAndParseMedia = async (options) => {
11183
12021
  await content.finish();
11184
12022
  return returnValue;
11185
12023
  };
11186
- // src/parse-media.ts
11187
- var parseMedia = (options) => {
11188
- return internalParseMedia({
11189
- fields: options.fields ?? null,
11190
- logLevel: options.logLevel ?? "info",
11191
- onAudioCodec: options.onAudioCodec ?? null,
11192
- onAudioTrack: options.onAudioTrack ?? null,
11193
- onContainer: options.onContainer ?? null,
11194
- onDimensions: options.onDimensions ?? null,
11195
- onDurationInSeconds: options.onDurationInSeconds ?? null,
11196
- onFps: options.onFps ?? null,
11197
- onImages: options.onImages ?? null,
11198
- onInternalStats: options.onInternalStats ?? null,
11199
- onIsHdr: options.onIsHdr ?? null,
11200
- onKeyframes: options.onKeyframes ?? null,
11201
- onLocation: options.onLocation ?? null,
11202
- onMetadata: options.onMetadata ?? null,
11203
- onMimeType: options.onMimeType ?? null,
11204
- onName: options.onName ?? null,
11205
- onNumberOfAudioChannels: options.onNumberOfAudioChannels ?? null,
11206
- onParseProgress: options.onParseProgress ?? null,
11207
- onRotation: options.onRotation ?? null,
11208
- onSampleRate: options.onSampleRate ?? null,
11209
- onSize: options.onSize ?? null,
11210
- onSlowAudioBitrate: options.onSlowAudioBitrate ?? null,
11211
- onSlowDurationInSeconds: options.onSlowDurationInSeconds ?? null,
11212
- onSlowFps: options.onSlowFps ?? null,
11213
- onSlowKeyframes: options.onSlowKeyframes ?? null,
11214
- onSlowNumberOfFrames: options.onSlowNumberOfFrames ?? null,
11215
- onSlowVideoBitrate: options.onSlowVideoBitrate ?? null,
11216
- onStructure: options.onStructure ?? null,
11217
- onTracks: options.onTracks ?? null,
11218
- onUnrotatedDimensions: options.onUnrotatedDimensions ?? null,
11219
- onVideoCodec: options.onVideoCodec ?? null,
11220
- onVideoTrack: options.onVideoTrack ?? null,
11221
- progressIntervalInMs: options.progressIntervalInMs ?? null,
11222
- reader: options.reader ?? fetchReader,
11223
- controller: options.controller ?? undefined,
11224
- src: options.src,
11225
- mode: "query",
11226
- onDiscardedData: null,
11227
- onError: () => ({ action: "fail" }),
11228
- acknowledgeRemotionLicense: Boolean(options.acknowledgeRemotionLicense),
11229
- apiName: "parseMedia()"
11230
- });
11231
- };
11232
12024
  // src/version.ts
11233
- var VERSION = "4.0.265";
12025
+ var VERSION = "4.0.267";
11234
12026
 
11235
12027
  // src/index.ts
11236
12028
  var MediaParserInternals = {
@@ -11253,6 +12045,7 @@ export {
11253
12045
  mediaParserController,
11254
12046
  hasBeenAborted,
11255
12047
  downloadAndParseMedia,
12048
+ defaultSelectM3uStreamFn,
11256
12049
  VERSION,
11257
12050
  MediaParserInternals,
11258
12051
  MediaParserAbortError,