dasha 4.0.0-alpha.1 → 4.0.0-alpha.3

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.
package/README.md CHANGED
@@ -6,10 +6,14 @@
6
6
 
7
7
  Library for parsing MPEG-DASH (.mpd) and HLS (.m3u8) manifests. Made with the purpose of obtaining a simplified representation convenient for further downloading of segments.
8
8
 
9
+ > [!WARNING]
10
+ > This README is for the alpha version. Info about latest stable version is available on [NPM](https://www.npmjs.com/package/dasha/v/3.1.5) or [another GitHub branch](https://github.com/azot-labs/dasha/tree/v3).
11
+
12
+
9
13
  ## Install
10
14
 
11
15
  ```shell
12
- npm i dasha
16
+ npm i dasha@alpha
13
17
  ```
14
18
 
15
19
  ## Usage
@@ -19,13 +23,16 @@ import fs from 'node:fs/promises';
19
23
  import { parse } from 'dasha';
20
24
 
21
25
  const url = 'https://dash.akamaized.net/dash264/TestCases/1a/sony/SNE_DASH_SD_CASE1A_REVISED.mpd';
22
- const body = await fetch(url).then((res) => res.text());
23
- const manifest = await parse(body, url);
24
-
25
- for (const track of manifest.tracks.all) {
26
- for (const segment of track.segments) {
27
- const content = await fetch(url).then((res) => res.arrayBuffer());
28
- await fs.appendFile(`${track.id}.mp4`, content);
26
+ const streamExtractor = new StreamExtractor();
27
+ await streamExtractor.loadSourceFromUrl(url);
28
+ const streams = await streamExtractor.extractStreams();
29
+
30
+ for (const stream of streams) {
31
+ const segments = stream.playlist?.mediaParts[0].mediaSegments || [];
32
+ const filename = `${stream.name}_${stream.groupId}`;
33
+ for (const segment of segments) {
34
+ const content = await fetch(segment.url).then((res) => res.arrayBuffer());
35
+ await fs.appendFile(`${filename}.${stream.extension}`, content);
29
36
  }
30
37
  }
31
38
  ```
package/dist/dasha.cjs CHANGED
@@ -1,4 +1,3 @@
1
- "use strict";
2
1
  //#region rolldown:runtime
3
2
  var __create = Object.create;
4
3
  var __defProp = Object.defineProperty;
@@ -22,13 +21,22 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
22
21
  }) : target, mod));
23
22
 
24
23
  //#endregion
25
- const node_fs = __toESM(require("node:fs"));
26
- const node_fs_promises = __toESM(require("node:fs/promises"));
27
- const node_url = __toESM(require("node:url"));
28
- const node_path = __toESM(require("node:path"));
29
- const temporal_polyfill = __toESM(require("temporal-polyfill"));
30
- const __xmldom_xmldom = __toESM(require("@xmldom/xmldom"));
31
- const node_crypto = __toESM(require("node:crypto"));
24
+ let node_fs = require("node:fs");
25
+ node_fs = __toESM(node_fs);
26
+ let barsic = require("barsic");
27
+ barsic = __toESM(barsic);
28
+ let node_fs_promises = require("node:fs/promises");
29
+ node_fs_promises = __toESM(node_fs_promises);
30
+ let node_url = require("node:url");
31
+ node_url = __toESM(node_url);
32
+ let node_path = require("node:path");
33
+ node_path = __toESM(node_path);
34
+ let temporal_polyfill = require("temporal-polyfill");
35
+ temporal_polyfill = __toESM(temporal_polyfill);
36
+ let __xmldom_xmldom = require("@xmldom/xmldom");
37
+ __xmldom_xmldom = __toESM(__xmldom_xmldom);
38
+ let node_crypto = require("node:crypto");
39
+ node_crypto = __toESM(node_crypto);
32
40
 
33
41
  //#region lib/shared/media-type.ts
34
42
  const MEDIA_TYPES = {
@@ -41,14 +49,14 @@ const MEDIA_TYPES = {
41
49
  //#endregion
42
50
  //#region lib/shared/encrypt-method.ts
43
51
  const ENCRYPT_METHODS = {
44
- NONE: 0,
45
- AES_128: 1,
46
- AES_128_ECB: 2,
47
- SAMPLE_AES: 3,
48
- SAMPLE_AES_CTR: 4,
49
- CENC: 5,
50
- CHACHA20: 6,
51
- UNKNOWN: 7
52
+ NONE: "none",
53
+ AES_128: "aes-128",
54
+ AES_128_ECB: "aes-128-ecb",
55
+ SAMPLE_AES: "sample-aes",
56
+ SAMPLE_AES_CTR: "sample-aes-ctr",
57
+ CENC: "cenc",
58
+ CHACHA20: "chacha20",
59
+ UNKNOWN: "unknown"
52
60
  };
53
61
 
54
62
  //#endregion
@@ -76,22 +84,38 @@ const ROLE_TYPE = {
76
84
  };
77
85
 
78
86
  //#endregion
79
- //#region lib/parser-config.ts
80
- var ParserConfig = class {
81
- url = "";
82
- originalUrl = "";
83
- baseUrl;
84
- customParserArgs = {};
85
- headers = {};
86
- contentProcessors = [];
87
- urlProcessors = [];
88
- keyProcessors = [];
89
- customMethod;
90
- customKey;
91
- customIv;
92
- urlProcessorArgs;
93
- appendUrlParams = false;
94
- keyRetryCount = 3;
87
+ //#region lib/processor.ts
88
+ var DefaultUrlProcessor = class {
89
+ canProcess(_extractorType, _originalUrl, parserConfig) {
90
+ return parserConfig.appendUrlParams;
91
+ }
92
+ process(url, parserConfig) {
93
+ if (!url.startsWith("http")) return url;
94
+ const urlFromConfigQuery = new URL(parserConfig.url).searchParams;
95
+ const oldUrl = new URL(url);
96
+ const newQuery = oldUrl.searchParams;
97
+ for (const [key, value] of urlFromConfigQuery) if (newQuery.has(key)) newQuery.set(key, value);
98
+ else newQuery.append(key, value);
99
+ if (!newQuery.toString()) return url;
100
+ console.debug(`Before: ${url}`);
101
+ url = `${oldUrl.pathname}?${newQuery.toString()}`;
102
+ console.debug(`After: ${url}`);
103
+ return url;
104
+ }
105
+ };
106
+
107
+ //#endregion
108
+ //#region lib/dash/dash-content-processor.ts
109
+ var DefaultDashContentProcessor = class {
110
+ canProcess(extractorType, mpdContent) {
111
+ if (extractorType !== EXTRACTOR_TYPES.MPEG_DASH) return false;
112
+ return mpdContent.includes("<mas:") && !mpdContent.includes("xmlns:mas");
113
+ }
114
+ process(mpdContent) {
115
+ console.debug("Fix xigua mpd...");
116
+ mpdContent = mpdContent.replace("<MPD ", "<MPD xmlns:mas=\"urn:marlin:mas:1-0:services:schemas:mpd\" ");
117
+ return mpdContent;
118
+ }
95
119
  };
96
120
 
97
121
  //#endregion
@@ -125,6 +149,138 @@ const HLS_TAGS = {
125
149
  extXStart: "#EXT-X-START"
126
150
  };
127
151
 
152
+ //#endregion
153
+ //#region lib/hls/hls-content-processor.ts
154
+ var DefaultHlsContentProcessor = class DefaultHlsContentProcessor {
155
+ static YkDVRegex = /#EXT-X-DISCONTINUITY\s+#EXT-X-MAP:URI="(.*?)",BYTERANGE="(.*?)"/g;
156
+ static DNSPRegex = /#EXT-X-MAP:URI=".*?BUMPER\/[\s\S]+?#EXT-X-DISCONTINUITY/;
157
+ static DNSPSubRegex = /#EXTINF:.*?,\s+.*BUMPER.*\s+#EXT-X-DISCONTINUITY/;
158
+ static OrderFixRegex = /(#EXTINF.*)(\s+)(#EXT-X-KEY.*)/g;
159
+ static ATVRegex = /#EXT-X-MAP.*\.apple\.com\//;
160
+ static ATVRegex2 = /(#EXT-X-KEY:[\s\S]*?)(#EXT-X-DISCONTINUITY|#EXT-X-ENDLIST)/;
161
+ canProcess(extractorType) {
162
+ return extractorType === EXTRACTOR_TYPES.HLS;
163
+ }
164
+ process(m3u8Content, parserConfig) {
165
+ if (m3u8Content.includes("\r") && !m3u8Content.includes("\n")) m3u8Content = m3u8Content.replace(/\r/g, "\n");
166
+ const m3u8Url = parserConfig.url;
167
+ if (m3u8Url.includes("tlivecloud-playback-cdn.ysp.cctv.cn") && m3u8Url.includes("endtime=")) m3u8Content += "\n" + HLS_TAGS.extXEndlist;
168
+ if (m3u8Content.includes("#EXT-X-DISCONTINUITY") && m3u8Content.includes("#EXT-X-MAP") && m3u8Content.includes("ott.cibntv.net") && m3u8Content.includes("ccode=")) m3u8Content = m3u8Content.replace(DefaultHlsContentProcessor.YkDVRegex, (_match, uri, byterange) => `#EXTINF:0.000000,\n#EXT-X-BYTERANGE:${byterange}\n${uri}`);
169
+ if (m3u8Content.includes("#EXT-X-DISCONTINUITY") && m3u8Content.includes("#EXT-X-MAP") && m3u8Url.includes("media.dssott.com/")) this.applyRegexReplacement(m3u8Content, DefaultHlsContentProcessor.DNSPRegex);
170
+ if (m3u8Content.includes("#EXT-X-DISCONTINUITY") && m3u8Content.includes("seg_00000.vtt") && m3u8Url.includes("media.dssott.com/")) this.applyRegexReplacement(m3u8Content, DefaultHlsContentProcessor.DNSPSubRegex);
171
+ if (m3u8Content.includes("#EXT-X-DISCONTINUITY") && m3u8Content.includes("#EXT-X-MAP") && (m3u8Url.includes(".apple.com/") || DefaultHlsContentProcessor.ATVRegex.test(m3u8Content))) {
172
+ const match = DefaultHlsContentProcessor.ATVRegex2.exec(m3u8Content);
173
+ if (match) m3u8Content = `#EXTM3U\n${match[1]}\n#EXT-X-ENDLIST`;
174
+ }
175
+ if (DefaultHlsContentProcessor.OrderFixRegex.test(m3u8Content)) m3u8Content = m3u8Content.replace(DefaultHlsContentProcessor.OrderFixRegex, "$3$2$1");
176
+ return m3u8Content;
177
+ }
178
+ applyRegexReplacement(content, regex) {
179
+ if (regex.test(content)) {
180
+ const match = regex.exec(content);
181
+ if (match) return content.split(match[0]).join("#XXX");
182
+ }
183
+ return content;
184
+ }
185
+ };
186
+
187
+ //#endregion
188
+ //#region lib/shared/encrypt-info.ts
189
+ var EncryptInfo = class {
190
+ method = ENCRYPT_METHODS.NONE;
191
+ key;
192
+ iv;
193
+ drm;
194
+ constructor(method) {
195
+ this.method = this.parseMethod(method);
196
+ this.drm = {};
197
+ }
198
+ parseMethod(method) {
199
+ if (method) return ENCRYPT_METHODS[method.replace("-", "_")];
200
+ else return ENCRYPT_METHODS.UNKNOWN;
201
+ }
202
+ };
203
+
204
+ //#endregion
205
+ //#region lib/hls/hls-key-processor.ts
206
+ var DefaultHlsKeyProcessor = class {
207
+ canProcess(extractorType) {
208
+ return extractorType === EXTRACTOR_TYPES.HLS;
209
+ }
210
+ async process(keyLine, m3u8Url, _m3u8Content, parserConfig) {
211
+ const iv = this.getAttribute(keyLine, "IV");
212
+ const method = this.getAttribute(keyLine, "METHOD");
213
+ const uri = this.getAttribute(keyLine, "URI");
214
+ const encryptInfo = new EncryptInfo(method);
215
+ if (iv) encryptInfo.iv = barsic.b.hex().encode(iv);
216
+ if (parserConfig.customIv && parserConfig.customIv.length > 0) encryptInfo.iv = parserConfig.customIv;
217
+ try {
218
+ if (parserConfig.customKey && parserConfig.customKey.length > 0) encryptInfo.key = parserConfig.customKey;
219
+ else if (uri) {
220
+ const lowerUri = uri.toLowerCase();
221
+ if (lowerUri.startsWith("base64:")) encryptInfo.key = barsic.b.base64().encode(uri.slice(7));
222
+ else if (lowerUri.startsWith("data:;base64,")) encryptInfo.key = barsic.b.base64().encode(uri.slice(13));
223
+ else if (lowerUri.startsWith("data:text/plain;base64,")) encryptInfo.key = barsic.b.base64().encode(uri.slice(23));
224
+ else if ((0, node_fs.existsSync)(uri)) encryptInfo.key = (0, node_fs.readFileSync)(uri);
225
+ else {
226
+ const processedUrl = this.preProcessUrl(new URL(uri, m3u8Url).toString(), parserConfig);
227
+ encryptInfo.key = await this.fetchKeyWithRetry(processedUrl, parserConfig);
228
+ }
229
+ }
230
+ } catch (error) {
231
+ console.error(`Failed to load key: ${error.message}`);
232
+ encryptInfo.method = ENCRYPT_METHODS.UNKNOWN;
233
+ }
234
+ if (parserConfig.customMethod) {
235
+ console.warn(`METHOD changed from ${encryptInfo.method} to ${parserConfig.customMethod}`);
236
+ encryptInfo.method = parserConfig.customMethod;
237
+ }
238
+ return encryptInfo;
239
+ }
240
+ getAttribute(line, attrName) {
241
+ const regex = new RegExp(`${attrName}=(?:"([^"]+)"|([^,]+))`, "i");
242
+ const match = line.match(regex);
243
+ return match?.[1] ?? match?.[2] ?? null;
244
+ }
245
+ async fetchKeyWithRetry(url, parserConfig) {
246
+ let retryCount = parserConfig.keyRetryCount ?? 3;
247
+ while (retryCount >= 0) try {
248
+ const response = await fetch(url, { headers: parserConfig.headers });
249
+ return new Uint8Array(await response.arrayBuffer());
250
+ } catch (error) {
251
+ if (error.message.includes("scheme is not supported")) throw error;
252
+ console.warn(`Error fetching key: ${error.message}. Retries left: ${retryCount}`);
253
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
254
+ retryCount--;
255
+ }
256
+ throw new Error("Maximum retry attempts reached");
257
+ }
258
+ preProcessUrl(url, parserConfig) {
259
+ let processedUrl = url;
260
+ for (const processor of parserConfig.urlProcessors ?? []) if (processor.canProcess(EXTRACTOR_TYPES.HLS, processedUrl, parserConfig)) processedUrl = processor.process(processedUrl, parserConfig);
261
+ return processedUrl;
262
+ }
263
+ };
264
+
265
+ //#endregion
266
+ //#region lib/parser-config.ts
267
+ var ParserConfig = class {
268
+ url = "";
269
+ originalUrl = "";
270
+ baseUrl;
271
+ customParserArgs = {};
272
+ headers = {};
273
+ contentProcessors = [new DefaultDashContentProcessor(), new DefaultHlsContentProcessor()];
274
+ urlProcessors = [new DefaultUrlProcessor()];
275
+ keyProcessors = [new DefaultHlsKeyProcessor()];
276
+ customMethod;
277
+ customKey;
278
+ customIv;
279
+ urlProcessorArgs;
280
+ appendUrlParams = false;
281
+ keyRetryCount = 3;
282
+ };
283
+
128
284
  //#endregion
129
285
  //#region lib/shared/stream-spec.ts
130
286
  var StreamSpec = class {
@@ -211,8 +367,7 @@ const DASH_TAGS = {
211
367
  const combineUrl = (baseUrl, relativeUrl) => {
212
368
  if (!baseUrl.trim()) return relativeUrl;
213
369
  const url1 = new URL(baseUrl);
214
- const url2 = new URL(relativeUrl, url1);
215
- return url2.toString();
370
+ return new URL(relativeUrl, url1).toString();
216
371
  };
217
372
  const replaceVars = (text, dict) => {
218
373
  let result = text;
@@ -251,7 +406,7 @@ const getAttribute = (line, key = "") => {
251
406
  return result;
252
407
  };
253
408
  const distinctBy = (array, callbackfn) => {
254
- const seen = new Set();
409
+ const seen = /* @__PURE__ */ new Set();
255
410
  return array.filter((item) => {
256
411
  const value = callbackfn(item);
257
412
  if (seen.has(value)) return false;
@@ -285,21 +440,6 @@ var MediaPart = class {
285
440
  }
286
441
  };
287
442
 
288
- //#endregion
289
- //#region lib/shared/encrypt-info.ts
290
- var EncryptInfo = class {
291
- method = ENCRYPT_METHODS.NONE;
292
- key;
293
- iv;
294
- constructor(method) {
295
- this.method = this.parseMethod(method);
296
- }
297
- parseMethod(method) {
298
- if (method !== void 0) return ENCRYPT_METHODS[method.replace("-", "_")];
299
- else return ENCRYPT_METHODS.UNKNOWN;
300
- }
301
- };
302
-
303
443
  //#endregion
304
444
  //#region lib/shared/media-segment.ts
305
445
  var MediaSegment = class MediaSegment {
@@ -345,8 +485,7 @@ var MediaSegment = class MediaSegment {
345
485
  */
346
486
  const parseRange = (range) => {
347
487
  const [startRange, end] = range.split("-").map(Number);
348
- const expectLength = end - startRange + 1;
349
- return [startRange, expectLength];
488
+ return [startRange, end - startRange + 1];
350
489
  };
351
490
 
352
491
  //#endregion
@@ -382,11 +521,9 @@ var DashExtractor = class DashExtractor {
382
521
  async extractStreams(rawText) {
383
522
  const streamList = [];
384
523
  this.#mpdContent = rawText;
385
- const document = new __xmldom_xmldom.DOMParser().parseFromString(this.#mpdContent, "text/xml");
386
- const mpdElement = document.getElementsByTagName("MPD")[0];
387
- const type = mpdElement.getAttribute("type");
388
- const isLive = type === "dynamic";
389
- const maxSegmentDuration = mpdElement.getAttribute("maxSegmentDuration");
524
+ const mpdElement = new __xmldom_xmldom.DOMParser().parseFromString(this.#mpdContent, "text/xml").getElementsByTagName("MPD")[0];
525
+ const isLive = mpdElement.getAttribute("type") === "dynamic";
526
+ mpdElement.getAttribute("maxSegmentDuration");
390
527
  const availabilityStartTime = mpdElement.getAttribute("availabilityStartTime");
391
528
  const timeShiftBufferDepth = mpdElement.getAttribute("timeShiftBufferDepth") || "PT1M";
392
529
  const publishTime = mpdElement.getAttribute("publishTime");
@@ -445,8 +582,7 @@ var DashExtractor = class DashExtractor {
445
582
  if (role) {
446
583
  const roleValue = role.getAttribute("value");
447
584
  const capitalize = (word) => word.charAt(0).toUpperCase() + word.slice(1);
448
- const roleTypeKey = roleValue.split("-").map(capitalize).join("");
449
- const roleType = ROLE_TYPE[roleTypeKey];
585
+ const roleType = ROLE_TYPE[roleValue.split("-").map(capitalize).join("")];
450
586
  streamSpec.role = roleType;
451
587
  if (roleType === ROLE_TYPE.Subtitle) {
452
588
  streamSpec.mediaType = MEDIA_TYPES.SUBTITLES;
@@ -577,8 +713,7 @@ var DashExtractor = class DashExtractor {
577
713
  varDic[DASH_TAGS.TemplateNumber] = segNumber++;
578
714
  const _hashTime = mediaTemplate?.includes(DASH_TAGS.TemplateTime);
579
715
  const _media = replaceVars(mediaTemplate, varDic);
580
- const _mediaUrl = combineUrl(segBaseUrl, _media);
581
- _mediaSegment.url = _mediaUrl;
716
+ _mediaSegment.url = combineUrl(segBaseUrl, _media);
582
717
  _mediaSegment.index = segIndex++;
583
718
  _mediaSegment.duration = _duration / timescale;
584
719
  if (_hashTime) _mediaSegment.nameFromVar = currentTime.toString();
@@ -622,27 +757,37 @@ var DashExtractor = class DashExtractor {
622
757
  mediaSegment.duration = periodDurationSeconds;
623
758
  streamSpec.playlist.mediaParts[0].mediaSegments.push(mediaSegment);
624
759
  }
625
- const contentProtection = adaptationSet.getElementsByTagName("ContentProtection")[0] || representation.getElementsByTagName("ContentProtection")[0];
626
- if (contentProtection) {
760
+ const adaptationSetProtections = adaptationSet.getElementsByTagName("ContentProtection");
761
+ const representationProtections = representation.getElementsByTagName("ContentProtection");
762
+ const contentProtections = representationProtections[0] ? representationProtections : adaptationSetProtections;
763
+ if (contentProtections.length) {
627
764
  const encryptInfo = new EncryptInfo();
628
765
  encryptInfo.method = DashExtractor.#DEFAULT_METHOD;
766
+ const widevineSystemId = "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed";
767
+ const playreadySystemId = "9a04f079-9840-4286-ab92-e65be0885f95";
768
+ for (const contentProtection of contentProtections) {
769
+ const schemeIdUri = contentProtection.getAttribute("schemeIdUri");
770
+ const drmData = {
771
+ keyId: contentProtection.getAttribute("cenc:default_KID") || void 0,
772
+ pssh: contentProtection.getElementsByTagName("cenc:pssh")[0]?.textContent || void 0
773
+ };
774
+ if (schemeIdUri?.includes(widevineSystemId)) encryptInfo.drm.widevine = drmData;
775
+ else if (schemeIdUri?.includes(playreadySystemId)) encryptInfo.drm.playready = drmData;
776
+ else continue;
777
+ }
629
778
  if (streamSpec.playlist.mediaInit) streamSpec.playlist.mediaInit.encryptInfo = encryptInfo;
630
779
  const segments = streamSpec.playlist.mediaParts[0].mediaSegments;
631
780
  for (const segment of segments) if (!segment.encryptInfo) segment.encryptInfo = encryptInfo;
632
781
  }
633
782
  const _index = streamList.findIndex((item) => item.periodId !== streamSpec.periodId && item.groupId === streamSpec.groupId && item.resolution === streamSpec.resolution && item.mediaType === streamSpec.mediaType);
634
- if (_index > -1) if (isLive) {} else {
635
- const url1 = streamList[_index].playlist.mediaParts.at(-1).mediaSegments.at(-1).url;
636
- const url2 = streamSpec.playlist.mediaParts[0].mediaSegments.at(-1)?.url;
637
- if (url1 !== url2) {
638
- const startIndex = streamList[_index].playlist.mediaParts.at(-1).mediaSegments.at(-1).index + 1;
639
- const segments = streamSpec.playlist.mediaParts[0].mediaSegments;
640
- for (const segment of segments) segment.index += startIndex;
641
- const mediaPart = new MediaPart();
642
- mediaPart.mediaSegments = streamList[_index].playlist.mediaParts[0].mediaSegments;
643
- streamList[_index].playlist.mediaParts.push(mediaPart);
644
- } else streamList[_index].playlist.mediaParts.at(-1).mediaSegments.at(-1).duration += streamSpec.playlist.mediaParts[0].mediaSegments.reduce((sum, segment) => sum + segment.duration, 0);
645
- }
783
+ if (_index > -1) if (isLive) {} else if (streamList[_index].playlist.mediaParts.at(-1).mediaSegments.at(-1).url !== streamSpec.playlist.mediaParts[0].mediaSegments.at(-1)?.url) {
784
+ const startIndex = streamList[_index].playlist.mediaParts.at(-1).mediaSegments.at(-1).index + 1;
785
+ const segments = streamSpec.playlist.mediaParts[0].mediaSegments;
786
+ for (const segment of segments) segment.index += startIndex;
787
+ const mediaPart = new MediaPart();
788
+ mediaPart.mediaSegments = streamList[_index].playlist.mediaParts[0].mediaSegments;
789
+ streamList[_index].playlist.mediaParts.push(mediaPart);
790
+ } else streamList[_index].playlist.mediaParts.at(-1).mediaSegments.at(-1).duration += streamSpec.playlist.mediaParts[0].mediaSegments.reduce((sum, segment) => sum + segment.duration, 0);
646
791
  else {
647
792
  if (streamSpec.mediaType === MEDIA_TYPES.SUBTITLES && streamSpec.extension === "mp4") streamSpec.extension = "m4s";
648
793
  if (streamSpec.mediaType !== MEDIA_TYPES.SUBTITLES && (streamSpec.extension == null || streamSpec.playlist.mediaParts.reduce((sum, part) => sum + part.mediaSegments.length, 0) > 1)) streamSpec.extension = "m4s";
@@ -657,8 +802,8 @@ var DashExtractor = class DashExtractor {
657
802
  const subtitleList = streamList.filter((stream) => stream.mediaType === MEDIA_TYPES.SUBTITLES);
658
803
  const videoList = streamList.filter((stream) => stream.mediaType === MEDIA_TYPES.VIDEO);
659
804
  for (const video of videoList) {
660
- const audioGroupId = audioList.toSorted((a, b) => (b.bandwidth || 0) - (a.bandwidth || 0)).at(0)?.groupId;
661
- const subtitleGroupId = subtitleList.toSorted((a, b) => (b.bandwidth || 0) - (a.bandwidth || 0)).at(0)?.groupId;
805
+ const audioGroupId = audioList.toSorted((a, b$1) => (b$1.bandwidth || 0) - (a.bandwidth || 0)).at(0)?.groupId;
806
+ const subtitleGroupId = subtitleList.toSorted((a, b$1) => (b$1.bandwidth || 0) - (a.bandwidth || 0)).at(0)?.groupId;
662
807
  if (audioGroupId) video.audioId = audioGroupId;
663
808
  if (subtitleGroupId) video.subtitleId = subtitleGroupId;
664
809
  }
@@ -668,17 +813,38 @@ var DashExtractor = class DashExtractor {
668
813
  if (!v) return;
669
814
  return v;
670
815
  }
671
- fetchPlayList(streamSpecs) {
672
- throw new Error("Method not implemented.");
816
+ async refreshPlayList(streamSpecs) {
817
+ if (!streamSpecs.length) return;
818
+ const response = await fetch(this.#parserConfig.url, this.#parserConfig.headers).catch(() => fetch(this.#parserConfig.originalUrl, this.#parserConfig.headers));
819
+ const rawText = await response.text();
820
+ const url = response.url;
821
+ this.#parserConfig.url = url;
822
+ this.#setInitUrl();
823
+ const newStreams = await this.extractStreams(rawText);
824
+ for (const streamSpec of streamSpecs) {
825
+ let results = newStreams.filter((n) => n.toShortString() === streamSpec.toShortString());
826
+ if (!results.length) results = newStreams.filter((n) => n.playlist?.mediaInit?.url === streamSpec.playlist?.mediaInit?.url);
827
+ if (results.length) streamSpec.playlist.mediaParts = results.at(0).playlist.mediaParts;
828
+ }
829
+ await this.#processUrl(streamSpecs);
830
+ }
831
+ async #processUrl(streamSpecs) {
832
+ for (const spec of streamSpecs) {
833
+ const playlist = spec.playlist;
834
+ if (!playlist) continue;
835
+ if (playlist.mediaInit) playlist.mediaInit.url = this.preProcessUrl(playlist.mediaInit.url);
836
+ for (const part of playlist.mediaParts) for (const segment of part.mediaSegments) segment.url = this.preProcessUrl(segment.url);
837
+ }
673
838
  }
674
- refreshPlayList(streamSpecs) {
675
- throw new Error("Method not implemented.");
839
+ async fetchPlayList(streamSpecs) {
840
+ this.#processUrl(streamSpecs);
676
841
  }
677
842
  preProcessUrl(url) {
678
- throw new Error("Method not implemented.");
843
+ for (const processor of this.#parserConfig.urlProcessors) if (processor.canProcess(this.extractorType, url, this.#parserConfig)) url = processor.process(url, this.#parserConfig);
844
+ return url;
679
845
  }
680
846
  preProcessContent() {
681
- throw new Error("Method not implemented.");
847
+ for (const processor of this.#parserConfig.contentProcessors) if (processor.canProcess(this.extractorType, this.#mpdContent, this.#parserConfig)) this.#mpdContent = processor.process(this.#mpdContent, this.#parserConfig);
682
848
  }
683
849
  };
684
850
 
@@ -757,8 +923,7 @@ var HlsExtractor = class {
757
923
  expectPlaylist = true;
758
924
  } else if (line.startsWith(HLS_TAGS.extXMedia)) {
759
925
  streamSpec = new StreamSpec();
760
- const type = getAttribute(line, "TYPE").replace("-", "_");
761
- const mediaType = MEDIA_TYPES[type];
926
+ const mediaType = MEDIA_TYPES[getAttribute(line, "TYPE").replace("-", "_")];
762
927
  if (mediaType) streamSpec.mediaType = mediaType;
763
928
  if (mediaType === MEDIA_TYPES.CLOSED_CAPTIONS) continue;
764
929
  let url = getAttribute(line, "URI");
@@ -809,8 +974,7 @@ var HlsExtractor = class {
809
974
  for (const line of lines) {
810
975
  if (!line.trim()) continue;
811
976
  if (line.startsWith(HLS_TAGS.extXByterange)) {
812
- const p = getAttribute(line);
813
- const [n, o] = getRange(p);
977
+ const [n, o] = getRange(getAttribute(line));
814
978
  segment.expectLength = n;
815
979
  segment.startRange = o || (segments.at(-1)?.startRange || 0) + (segments.at(-1)?.expectLength || 0);
816
980
  expectSegment = true;
@@ -832,10 +996,8 @@ var HlsExtractor = class {
832
996
  mediaParts.push(new MediaPart(segments));
833
997
  segments = [];
834
998
  } else if (line.startsWith(HLS_TAGS.extXKey)) {
835
- const uri = getAttribute(line, "URI");
836
- const uriLast = getAttribute(lastKeyLine, "URI");
837
- if (uri !== uriLast) {
838
- const parsedInfo = this.#parseKey(line);
999
+ if (getAttribute(line, "URI") !== getAttribute(lastKeyLine, "URI")) {
1000
+ const parsedInfo = await this.#parseKey(line);
839
1001
  currentEncryptInfo.method = parsedInfo.method;
840
1002
  currentEncryptInfo.key = parsedInfo.key;
841
1003
  currentEncryptInfo.iv = parsedInfo.iv;
@@ -862,8 +1024,7 @@ var HlsExtractor = class {
862
1024
  mediaSegment.index = -1;
863
1025
  playlist.mediaInit = mediaSegment;
864
1026
  if (line.includes("BYTERANGE")) {
865
- const p = getAttribute(line, "BYTERANGE");
866
- const [n, o] = getRange(p);
1027
+ const [n, o] = getRange(getAttribute(line, "BYTERANGE"));
867
1028
  mediaSegment.expectLength = n;
868
1029
  mediaSegment.startRange = o || 0;
869
1030
  }
@@ -905,17 +1066,14 @@ var HlsExtractor = class {
905
1066
  if (playlist.isLive) playlist.refreshIntervalMs = (playlist.targetDuration || 5) * 2 * 1e3;
906
1067
  return playlist;
907
1068
  }
908
- #parseKey(keyLine) {
1069
+ async #parseKey(keyLine) {
909
1070
  for (const p of this.parserConfig.keyProcessors) if (p.canProcess(this.extractorType, keyLine, this.#m3u8Url, this.#m3u8Content, this.parserConfig)) return p.process(keyLine, this.#m3u8Url, this.#m3u8Content, this.parserConfig);
910
1071
  throw new Error("No key processor found");
911
1072
  }
912
1073
  async extractStreams(rawText) {
913
1074
  this.#m3u8Content = rawText;
914
1075
  this.preProcessContent();
915
- if (this.#m3u8Content.includes(HLS_TAGS.extXStreamInf)) {
916
- console.log("Master m3u8 found");
917
- return this.#parseMasterList().then((lists) => distinctBy(lists, (list) => list.url));
918
- }
1076
+ if (this.#m3u8Content.includes(HLS_TAGS.extXStreamInf)) return this.#parseMasterList().then((lists) => distinctBy(lists, (list) => list.url));
919
1077
  const playlist = await this.#parseList();
920
1078
  const streamSpec = new StreamSpec();
921
1079
  streamSpec.url = this.parserConfig.url;
@@ -925,8 +1083,7 @@ var HlsExtractor = class {
925
1083
  }
926
1084
  async #loadM3u8FromUrl(url) {
927
1085
  if (url.startsWith("file:")) {
928
- const uri = new URL(url);
929
- const filePath = uri.pathname;
1086
+ const filePath = new URL(url).pathname;
930
1087
  this.#m3u8Content = await (0, node_fs_promises.readFile)(filePath, "utf8");
931
1088
  } else if (url.startsWith("http")) try {
932
1089
  const response = await fetch(url, { headers: this.parserConfig.headers });
@@ -966,9 +1123,9 @@ var HlsExtractor = class {
966
1123
  else list.playlist = newPlaylist;
967
1124
  if (list.mediaType === MEDIA_TYPES.SUBTITLES) {
968
1125
  const a = list.playlist.mediaParts.some((part) => part.mediaSegments.some((segment) => segment.url.includes(".ttml")));
969
- const b = list.playlist.mediaParts.some((part) => part.mediaSegments.some((segment) => segment.url.includes(".vtt") || segment.url.includes(".webvtt")));
1126
+ const b$1 = list.playlist.mediaParts.some((part) => part.mediaSegments.some((segment) => segment.url.includes(".vtt") || segment.url.includes(".webvtt")));
970
1127
  if (a) list.extension = "ttml";
971
- if (b) list.extension = "vtt";
1128
+ if (b$1) list.extension = "vtt";
972
1129
  } else list.extension = list.playlist.mediaInit ? "m4s" : "ts";
973
1130
  }
974
1131
  }
@@ -996,8 +1153,7 @@ var StreamExtractor = class {
996
1153
  }
997
1154
  async loadSourceFromUrl(url) {
998
1155
  if (url.startsWith("file:")) {
999
- const uri = new URL(url);
1000
- const filePath = uri.pathname;
1156
+ const filePath = new URL(url).pathname;
1001
1157
  this.#rawText = await (0, node_fs_promises.readFile)(filePath, "utf8");
1002
1158
  this.#setUrl(url);
1003
1159
  } else if (url.startsWith("http")) {
@@ -1024,8 +1180,11 @@ var StreamExtractor = class {
1024
1180
  } else if (this.#rawText.includes("</MPD>") && this.#rawText.includes("<MPD")) {
1025
1181
  this.#extractor = new DashExtractor(this.#parserConfig);
1026
1182
  rawType = "mpd";
1027
- } else if (this.#rawText.includes("</SmoothStreamingMedia>") && this.#rawText.includes("<SmoothStreamingMedia")) rawType = "ism";
1028
- else if (rawText === "<RE_LIVE_TS>") {} else throw new Error("Unsupported stream type");
1183
+ } else if (this.#rawText.includes("</SmoothStreamingMedia>") && this.#rawText.includes("<SmoothStreamingMedia")) {
1184
+ rawType = "ism";
1185
+ throw new Error("Smooth Streaming is not supported yet");
1186
+ } else if (rawText === "<RE_LIVE_TS>") throw new Error("Live TS is not supported yet");
1187
+ else throw new Error("Unsupported stream type");
1029
1188
  this.#rawFiles[`raw.${rawType}`] = rawText;
1030
1189
  }
1031
1190
  async extractStreams() {
@@ -1040,9 +1199,10 @@ var StreamExtractor = class {
1040
1199
  };
1041
1200
 
1042
1201
  //#endregion
1043
- exports.ENCRYPT_METHODS = ENCRYPT_METHODS
1044
- exports.EXTRACTOR_TYPES = EXTRACTOR_TYPES
1045
- exports.MEDIA_TYPES = MEDIA_TYPES
1046
- exports.ParserConfig = ParserConfig
1047
- exports.ROLE_TYPE = ROLE_TYPE
1048
- exports.StreamExtractor = StreamExtractor
1202
+ exports.DefaultUrlProcessor = DefaultUrlProcessor;
1203
+ exports.ENCRYPT_METHODS = ENCRYPT_METHODS;
1204
+ exports.EXTRACTOR_TYPES = EXTRACTOR_TYPES;
1205
+ exports.MEDIA_TYPES = MEDIA_TYPES;
1206
+ exports.ParserConfig = ParserConfig;
1207
+ exports.ROLE_TYPE = ROLE_TYPE;
1208
+ exports.StreamExtractor = StreamExtractor;