dasha 4.0.0-alpha.9 → 4.0.1

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/dist/dasha.mjs DELETED
@@ -1,1576 +0,0 @@
1
- import crypto from "node:crypto";
2
- import { existsSync, readFileSync } from "node:fs";
3
- import { b } from "barsic";
4
- import { readFile } from "node:fs/promises";
5
- import { pathToFileURL } from "node:url";
6
- import path from "node:path";
7
- import { Temporal } from "temporal-polyfill";
8
- import { DOMParser } from "@xmldom/xmldom";
9
-
10
- //#region lib/shared/codec.ts
11
- /**
12
- * List of known video codecs, ordered by encoding preference.
13
- * @group Codecs
14
- * @public
15
- */
16
- const VIDEO_CODECS = [
17
- "avc",
18
- "hevc",
19
- "vp8",
20
- "vp9",
21
- "av1",
22
- "vc1"
23
- ];
24
- /**
25
- * List of known video dynamic ranges.
26
- * @group Codecs
27
- * @public
28
- */
29
- const VIDEO_DYNAMIC_RANGES = [
30
- "sdr",
31
- "hlg",
32
- "hdr10",
33
- "hdr10+",
34
- "dv"
35
- ];
36
- /**
37
- * List of known audio codecs, ordered by encoding preference.
38
- * @group Codecs
39
- * @public
40
- */
41
- const AUDIO_CODECS = [
42
- "aac",
43
- "opus",
44
- "mp3",
45
- "vorbis",
46
- "flac",
47
- "alac",
48
- "ac3",
49
- "eac3",
50
- "dts"
51
- ];
52
- /**
53
- * List of known subtitle codecs, ordered by encoding preference.
54
- * @group Codecs
55
- * @public
56
- */
57
- const SUBTITLE_CODECS = [
58
- "srt",
59
- "vtt",
60
- "ttml",
61
- "dfxp",
62
- "ssa",
63
- "ass",
64
- "stpp",
65
- "wvtt"
66
- ];
67
-
68
- //#endregion
69
- //#region lib/shared/encrypt-method.ts
70
- const ENCRYPT_METHODS = {
71
- NONE: "none",
72
- AES_128: "aes-128",
73
- AES_128_ECB: "aes-128-ecb",
74
- SAMPLE_AES: "sample-aes",
75
- SAMPLE_AES_CTR: "sample-aes-ctr",
76
- CENC: "cenc",
77
- CHACHA20: "chacha20",
78
- UNKNOWN: "unknown"
79
- };
80
-
81
- //#endregion
82
- //#region lib/shared/encrypt-info.ts
83
- var EncryptInfo = class {
84
- method = ENCRYPT_METHODS.NONE;
85
- key;
86
- iv;
87
- drm;
88
- constructor(method) {
89
- this.method = this.parseMethod(method);
90
- this.drm = {};
91
- }
92
- parseMethod(method) {
93
- if (method) return ENCRYPT_METHODS[method.replace("-", "_")];
94
- else return ENCRYPT_METHODS.UNKNOWN;
95
- }
96
- };
97
-
98
- //#endregion
99
- //#region lib/shared/extractor-type.ts
100
- const EXTRACTOR_TYPES = {
101
- MPEG_DASH: "MPEG_DASH",
102
- HLS: "HLS",
103
- HTTP_LIVE: "HTTP_LIVE",
104
- MSS: "MSS"
105
- };
106
-
107
- //#endregion
108
- //#region lib/shared/media-part.ts
109
- var MediaPart = class {
110
- mediaSegments = [];
111
- constructor(segments) {
112
- this.mediaSegments = segments || [];
113
- }
114
- };
115
-
116
- //#endregion
117
- //#region lib/shared/media-segment.ts
118
- var MediaSegment = class MediaSegment {
119
- index = NaN;
120
- duration = NaN;
121
- title;
122
- dateTime;
123
- startRange;
124
- get stopRange() {
125
- return this.startRange !== void 0 && this.expectLength !== void 0 ? this.startRange + this.expectLength - 1 : void 0;
126
- }
127
- expectLength;
128
- encryptInfo = new EncryptInfo();
129
- get isEncrypted() {
130
- return this.encryptInfo.method !== ENCRYPT_METHODS.NONE;
131
- }
132
- url = "";
133
- nameFromVar;
134
- equals(segment) {
135
- if (segment instanceof MediaSegment) return this.index == segment.index && Math.abs(this.duration - segment.duration) < .001 && this.title == segment.title && this.startRange == segment.startRange && this.stopRange == segment.stopRange && this.expectLength == segment.expectLength && this.url == segment.url;
136
- else return false;
137
- }
138
- getHashCode() {
139
- const payload = [
140
- this.index,
141
- this.duration,
142
- this.title,
143
- this.startRange,
144
- this.stopRange,
145
- this.expectLength,
146
- this.url
147
- ].join("-");
148
- return crypto.createHash("md5").update(payload).digest("hex");
149
- }
150
- };
151
-
152
- //#endregion
153
- //#region lib/shared/playlist.ts
154
- var Playlist = class {
155
- url = "";
156
- isLive = false;
157
- refreshIntervalMs = 15e3;
158
- get totalDuration() {
159
- let result = 0;
160
- for (const part of this.mediaParts) for (const segment of part.mediaSegments) result += segment.duration;
161
- return result;
162
- }
163
- targetDuration;
164
- mediaInit;
165
- mediaParts = [];
166
- };
167
-
168
- //#endregion
169
- //#region lib/shared/role-type.ts
170
- const ROLE_TYPE = {
171
- Subtitle: 0,
172
- Main: 1,
173
- Alternate: 2,
174
- Supplementary: 3,
175
- Commentary: 4,
176
- Dub: 5,
177
- Description: 6,
178
- Sign: 7,
179
- Metadata: 8,
180
- ForcedSubtitle: 9
181
- };
182
-
183
- //#endregion
184
- //#region lib/shared/stream-info.ts
185
- /**
186
- * List of all stream types.
187
- * @group Miscellaneous
188
- * @public
189
- */
190
- const ALL_STREAM_TYPES = [
191
- "video",
192
- "audio",
193
- "subtitle"
194
- ];
195
- const bitrateToString = (bitrate) => {
196
- return bitrate ? `${Math.round(bitrate / 1e3)} Kbps` : "";
197
- };
198
- const roleToString = (role) => {
199
- for (const [key, value] of Object.entries(ROLE_TYPE)) if (value === role) return key;
200
- return "";
201
- };
202
- const durationToString = (seconds) => {
203
- if (!Number.isFinite(seconds) || seconds < 0) return "";
204
- const mins = Math.floor(seconds / 60);
205
- const secs = Math.round(seconds % 60);
206
- if (mins > 0) return `~${mins}m${secs.toString().padStart(2, "0")}s`;
207
- return `~${secs}s`;
208
- };
209
- var StreamInfo = class {
210
- codec;
211
- languageCode;
212
- bitrate;
213
- name;
214
- url = "";
215
- originalUrl = "";
216
- playlist;
217
- default;
218
- skippedDuration;
219
- role;
220
- videoRange;
221
- characteristics;
222
- publishTime;
223
- groupId = null;
224
- audioId;
225
- videoId;
226
- subtitleId;
227
- periodId = null;
228
- extension = null;
229
- /**
230
- * @deprecated Use `codec`
231
- */
232
- codecs = null;
233
- /**
234
- * @deprecated Use `numberOfChannels` in `AudioStreamInfo`
235
- */
236
- channels = null;
237
- /**
238
- * @deprecated Use `width` and `height` in `VideoStreamInfo`
239
- */
240
- resolution;
241
- /**
242
- * @deprecated Use `bitrate`
243
- */
244
- bandwidth;
245
- get segmentsCount() {
246
- return this.playlist?.mediaParts.reduce((sum, part) => sum + part.mediaSegments.length, 0) ?? 0;
247
- }
248
- };
249
- var VideoStreamInfo = class extends StreamInfo {
250
- codec;
251
- width;
252
- height;
253
- frameRate;
254
- dynamicRange;
255
- dolbyVisionProfile;
256
- get type() {
257
- return "video";
258
- }
259
- constructor(info) {
260
- super();
261
- this.codec = info?.codec;
262
- }
263
- toShortString() {
264
- const parts = ["Vid"];
265
- if (this.width) parts.push(`${this.width}x${this.height}`);
266
- if (this.bitrate) parts.push(bitrateToString(this.bitrate));
267
- if (this.groupId) parts.push(this.groupId);
268
- if (this.frameRate) parts.push(this.frameRate.toString());
269
- if (this.codec) parts.push(this.codec);
270
- if (this.videoRange) parts.push(this.videoRange);
271
- if (this.segmentsCount) parts.push(`${this.segmentsCount} segments`);
272
- if (this.role) parts.push(roleToString(this.role));
273
- if (this.playlist) parts.push(durationToString(this.playlist.totalDuration));
274
- return parts.filter(Boolean).join(" | ").trim();
275
- }
276
- };
277
- var AudioStreamInfo = class extends StreamInfo {
278
- codec;
279
- numberOfChannels;
280
- sampleRate;
281
- atmos;
282
- descriptive;
283
- joc;
284
- get type() {
285
- return "audio";
286
- }
287
- constructor(info) {
288
- super();
289
- this.codec = info?.codec;
290
- }
291
- toShortString() {
292
- const parts = ["Aud"];
293
- if (this.groupId) parts.push(this.groupId);
294
- if (this.bitrate) parts.push(bitrateToString(this.bitrate));
295
- if (this.name) parts.push(this.name);
296
- if (this.codec) parts.push(this.codec);
297
- if (this.languageCode) parts.push(this.languageCode);
298
- if (this.numberOfChannels) parts.push(`${this.numberOfChannels}CH`);
299
- if (this.role) parts.push(roleToString(this.role));
300
- return parts.filter(Boolean).join(" | ").trim();
301
- }
302
- };
303
- var SubtitleStreamInfo = class extends StreamInfo {
304
- codec;
305
- cc;
306
- sdh;
307
- forced;
308
- get type() {
309
- return "subtitle";
310
- }
311
- constructor(info) {
312
- super();
313
- this.codec = info?.codec;
314
- }
315
- toShortString() {
316
- const parts = ["Sub"];
317
- const text = parts.filter(Boolean).join(" | ");
318
- if (this.groupId) parts.push(this.groupId);
319
- if (this.languageCode) parts.push(this.languageCode);
320
- if (this.name) parts.push(this.name);
321
- if (this.codec) parts.push(this.codec);
322
- if (this.role) parts.push(roleToString(this.role));
323
- return text.trim();
324
- }
325
- };
326
-
327
- //#endregion
328
- //#region lib/processor.ts
329
- var DefaultUrlProcessor = class {
330
- canProcess(_extractorType, _originalUrl, parserConfig) {
331
- return parserConfig.appendUrlParams;
332
- }
333
- process(url, parserConfig) {
334
- if (!url.startsWith("http")) return url;
335
- const urlFromConfigQuery = new URL(parserConfig.url).searchParams;
336
- const oldUrl = new URL(url);
337
- const newQuery = oldUrl.searchParams;
338
- for (const [key, value] of urlFromConfigQuery) if (newQuery.has(key)) newQuery.set(key, value);
339
- else newQuery.append(key, value);
340
- if (!newQuery.toString()) return url;
341
- console.debug(`Before: ${url}`);
342
- url = `${oldUrl.pathname}?${newQuery.toString()}`;
343
- console.debug(`After: ${url}`);
344
- return url;
345
- }
346
- };
347
-
348
- //#endregion
349
- //#region lib/dash/dash-content-processor.ts
350
- const namespaceMap = new Map([
351
- ["cenc", "urn:mpeg:cenc:2013"],
352
- ["mspr", "urn:microsoft:playready"],
353
- ["mas", "urn:marlin:mas:1-0:services:schemas:mpd"]
354
- ]);
355
- const isMissingNs = (rawText, tag) => !rawText.includes(`xmlns:${tag}`) && rawText.includes(`<${tag}:`);
356
- function replaceFirst(source, oldValue, newValue) {
357
- const index = source.indexOf(oldValue);
358
- return index < 0 ? source : source.slice(0, index) + newValue + source.slice(index + oldValue.length);
359
- }
360
- var DefaultDashContentProcessor = class {
361
- canProcess(extractorType, mpdContent) {
362
- if (extractorType !== EXTRACTOR_TYPES.MPEG_DASH) return false;
363
- return namespaceMap.keys().some((key) => isMissingNs(mpdContent, key));
364
- }
365
- process(mpdContent) {
366
- console.debug("Namespace missing, trying to fix...");
367
- const missingNamespaceKeys = Array.from(namespaceMap.keys().filter((key) => isMissingNs(mpdContent, key)));
368
- if (!missingNamespaceKeys.length) return mpdContent;
369
- return replaceFirst(mpdContent, "<MPD ", `<MPD ${missingNamespaceKeys.map((key) => `xmlns:${key}="${namespaceMap.get(key)}"`).join(" ")} `);
370
- }
371
- };
372
-
373
- //#endregion
374
- //#region lib/hls/hls-tags.ts
375
- const HLS_TAGS = {
376
- extM3u: "#EXTM3U",
377
- extXTargetDuration: "#EXT-X-TARGETDURATION",
378
- extXMediaSequence: "#EXT-X-MEDIA-SEQUENCE",
379
- extXDiscontinuitySequence: "#EXT-X-DISCONTINUITY-SEQUENCE",
380
- extXProgramDateTime: "#EXT-X-PROGRAM-DATE-TIME",
381
- extXMedia: "#EXT-X-MEDIA",
382
- extXPlaylistType: "#EXT-X-PLAYLIST-TYPE",
383
- extXKey: "#EXT-X-KEY",
384
- extXStreamInf: "#EXT-X-STREAM-INF",
385
- extXVersion: "#EXT-X-VERSION",
386
- extXAllowCache: "#EXT-X-ALLOW-CACHE",
387
- extXEndlist: "#EXT-X-ENDLIST",
388
- extInf: "#EXTINF",
389
- extIframesOnly: "#EXT-X-I-FRAMES-ONLY",
390
- extXByterange: "#EXT-X-BYTERANGE",
391
- extXIframeStreamInf: "#EXT-X-I-FRAME-STREAM-INF",
392
- extXDiscontinuity: "#EXT-X-DISCONTINUITY",
393
- extXCueOutStart: "#EXT-X-CUE-OUT",
394
- extXCueOut: "#EXT-X-CUE-OUT-CONT",
395
- extIsIndependentSegments: "#EXT-X-INDEPENDENT-SEGMENTS",
396
- extXScte35: "#EXT-OATCLS-SCTE35",
397
- extXCueStart: "#EXT-X-CUE-OUT",
398
- extXCueEnd: "#EXT-X-CUE-IN",
399
- extXCueSpan: "#EXT-X-CUE-SPAN",
400
- extXMap: "#EXT-X-MAP",
401
- extXStart: "#EXT-X-START"
402
- };
403
-
404
- //#endregion
405
- //#region lib/hls/hls-content-processor.ts
406
- var DefaultHlsContentProcessor = class DefaultHlsContentProcessor {
407
- static YkDVRegex = /#EXT-X-DISCONTINUITY\s+#EXT-X-MAP:URI="(.*?)",BYTERANGE="(.*?)"/g;
408
- static DNSPRegex = /#EXT-X-MAP:URI=".*?BUMPER\/[\s\S]+?#EXT-X-DISCONTINUITY/;
409
- static DNSPSubRegex = /#EXTINF:.*?,\s+.*BUMPER.*\s+#EXT-X-DISCONTINUITY/;
410
- static OrderFixRegex = /(#EXTINF.*)(\s+)(#EXT-X-KEY.*)/g;
411
- static ATVRegex = /#EXT-X-MAP.*\.apple\.com\//;
412
- static ATVRegex2 = /(#EXT-X-KEY:[\s\S]*?)(#EXT-X-DISCONTINUITY|#EXT-X-ENDLIST)/;
413
- canProcess(extractorType) {
414
- return extractorType === EXTRACTOR_TYPES.HLS;
415
- }
416
- process(m3u8Content, parserConfig) {
417
- if (m3u8Content.includes("\r") && !m3u8Content.includes("\n")) m3u8Content = m3u8Content.replace(/\r/g, "\n");
418
- const m3u8Url = parserConfig.url;
419
- if (m3u8Url.includes("tlivecloud-playback-cdn.ysp.cctv.cn") && m3u8Url.includes("endtime=")) m3u8Content += "\n" + HLS_TAGS.extXEndlist;
420
- 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}`);
421
- if (m3u8Content.includes("#EXT-X-DISCONTINUITY") && m3u8Content.includes("#EXT-X-MAP") && m3u8Url.includes("media.dssott.com/")) this.applyRegexReplacement(m3u8Content, DefaultHlsContentProcessor.DNSPRegex);
422
- if (m3u8Content.includes("#EXT-X-DISCONTINUITY") && m3u8Content.includes("seg_00000.vtt") && m3u8Url.includes("media.dssott.com/")) this.applyRegexReplacement(m3u8Content, DefaultHlsContentProcessor.DNSPSubRegex);
423
- if (m3u8Content.includes("#EXT-X-DISCONTINUITY") && m3u8Content.includes("#EXT-X-MAP") && (m3u8Url.includes(".apple.com/") || DefaultHlsContentProcessor.ATVRegex.test(m3u8Content))) {
424
- const match = DefaultHlsContentProcessor.ATVRegex2.exec(m3u8Content);
425
- if (match) m3u8Content = `#EXTM3U\n${match[1]}\n#EXT-X-ENDLIST`;
426
- }
427
- if (DefaultHlsContentProcessor.OrderFixRegex.test(m3u8Content)) m3u8Content = m3u8Content.replace(DefaultHlsContentProcessor.OrderFixRegex, "$3$2$1");
428
- return m3u8Content;
429
- }
430
- applyRegexReplacement(content, regex) {
431
- if (regex.test(content)) {
432
- const match = regex.exec(content);
433
- if (match) return content.split(match[0]).join("#XXX");
434
- }
435
- return content;
436
- }
437
- };
438
-
439
- //#endregion
440
- //#region lib/hls/hls-key-processor.ts
441
- var DefaultHlsKeyProcessor = class {
442
- canProcess(extractorType) {
443
- return extractorType === EXTRACTOR_TYPES.HLS;
444
- }
445
- async process(keyLine, m3u8Url, _m3u8Content, parserConfig) {
446
- const iv = this.getAttribute(keyLine, "IV");
447
- const method = this.getAttribute(keyLine, "METHOD");
448
- const uri = this.getAttribute(keyLine, "URI");
449
- const encryptInfo = new EncryptInfo(method);
450
- if (iv) encryptInfo.iv = b.hex().encode(iv);
451
- if (parserConfig.customIv && parserConfig.customIv.length > 0) encryptInfo.iv = parserConfig.customIv;
452
- try {
453
- if (parserConfig.customKey && parserConfig.customKey.length > 0) encryptInfo.key = parserConfig.customKey;
454
- else if (uri) {
455
- const lowerUri = uri.toLowerCase();
456
- if (lowerUri.startsWith("base64:")) encryptInfo.key = b.base64().encode(uri.slice(7));
457
- else if (lowerUri.startsWith("data:;base64,")) encryptInfo.key = b.base64().encode(uri.slice(13));
458
- else if (lowerUri.startsWith("data:text/plain;base64,")) encryptInfo.key = b.base64().encode(uri.slice(23));
459
- else if (existsSync(uri)) encryptInfo.key = readFileSync(uri);
460
- else {
461
- const processedUrl = this.preProcessUrl(new URL(uri, m3u8Url).toString(), parserConfig);
462
- encryptInfo.key = await this.fetchKeyWithRetry(processedUrl, parserConfig);
463
- }
464
- }
465
- } catch (error) {
466
- console.error(`Failed to load key: ${error.message}`);
467
- encryptInfo.method = ENCRYPT_METHODS.UNKNOWN;
468
- }
469
- if (parserConfig.customMethod) {
470
- console.warn(`METHOD changed from ${encryptInfo.method} to ${parserConfig.customMethod}`);
471
- encryptInfo.method = parserConfig.customMethod;
472
- }
473
- return encryptInfo;
474
- }
475
- getAttribute(line, attrName) {
476
- const regex = new RegExp(`${attrName}=(?:"([^"]+)"|([^,]+))`, "i");
477
- const match = line.match(regex);
478
- return match?.[1] ?? match?.[2] ?? null;
479
- }
480
- async fetchKeyWithRetry(url, parserConfig) {
481
- let retryCount = parserConfig.keyRetryCount ?? 3;
482
- while (retryCount >= 0) try {
483
- const response = await fetch(url, { headers: parserConfig.headers });
484
- return new Uint8Array(await response.arrayBuffer());
485
- } catch (error) {
486
- if (error.message.includes("scheme is not supported")) throw error;
487
- console.warn(`Error fetching key: ${error.message}. Retries left: ${retryCount}`);
488
- await new Promise((resolve) => setTimeout(resolve, 1e3));
489
- retryCount--;
490
- }
491
- throw new Error("Maximum retry attempts reached");
492
- }
493
- preProcessUrl(url, parserConfig) {
494
- let processedUrl = url;
495
- for (const processor of parserConfig.urlProcessors ?? []) if (processor.canProcess(EXTRACTOR_TYPES.HLS, processedUrl, parserConfig)) processedUrl = processor.process(processedUrl, parserConfig);
496
- return processedUrl;
497
- }
498
- };
499
-
500
- //#endregion
501
- //#region lib/parser-config.ts
502
- var ParserConfig = class {
503
- url = "";
504
- originalUrl = "";
505
- baseUrl;
506
- customParserArgs = {};
507
- headers = {};
508
- contentProcessors = [new DefaultDashContentProcessor(), new DefaultHlsContentProcessor()];
509
- urlProcessors = [new DefaultUrlProcessor()];
510
- keyProcessors = [new DefaultHlsKeyProcessor()];
511
- customMethod;
512
- customKey;
513
- customIv;
514
- urlProcessorArgs;
515
- appendUrlParams = false;
516
- keyRetryCount = 3;
517
- };
518
-
519
- //#endregion
520
- //#region lib/dash/dash-tags.ts
521
- const DASH_TAGS = {
522
- TemplateRepresentationID: "$RepresentationID$",
523
- TemplateBandwidth: "$Bandwidth$",
524
- TemplateNumber: "$Number$",
525
- TemplateTime: "$Time$"
526
- };
527
-
528
- //#endregion
529
- //#region lib/shared/util.ts
530
- const combineUrl = (baseUrl, relativeUrl) => {
531
- if (!baseUrl.trim()) return relativeUrl;
532
- const url1 = new URL(baseUrl);
533
- return new URL(relativeUrl, url1).toString();
534
- };
535
- const replaceVars = (text, dict) => {
536
- let result = text;
537
- for (const [key, value] of Object.entries(dict)) result = result.replaceAll(key, String(value));
538
- const regex = /\$Number%([0-9]+)d\$/g;
539
- if (regex.test(result)) {
540
- const template = dict[DASH_TAGS.TemplateNumber];
541
- result = result.replace(regex, (_match, p1) => {
542
- if (!template) return "";
543
- return template.toString().padStart(parseInt(p1), "0");
544
- });
545
- }
546
- return result;
547
- };
548
- /**
549
- * Extracts parameters from text like:
550
- * #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2149280,CODECS="mp4a.40.2,avc1.64001f",RESOLUTION=1280x720,NAME="720"
551
- * @param line - The line of text to be parsed
552
- * @param key - If empty, returns all characters after the first colon
553
- * @returns The extracted attribute value
554
- */
555
- const getAttribute = (line, key = "") => {
556
- line = line.trim();
557
- if (key === "") return line.slice(line.indexOf(":") + 1);
558
- let index = -1;
559
- let result = "";
560
- if ((index = line.indexOf(key + "=\"")) > -1) {
561
- const startIndex = index + (key + "=\"").length;
562
- const endIndex = line.indexOf("\"", startIndex);
563
- result = line.slice(startIndex, endIndex);
564
- } else if ((index = line.indexOf(key + "=")) > -1) {
565
- const startIndex = index + (key + "=").length;
566
- const endIndex = line.indexOf(",", startIndex);
567
- result = endIndex >= startIndex ? line.slice(startIndex, endIndex) : line.slice(startIndex);
568
- }
569
- return result;
570
- };
571
- const distinctBy = (array, callbackfn) => {
572
- const seen = /* @__PURE__ */ new Set();
573
- return array.filter((item) => {
574
- const value = callbackfn(item);
575
- if (seen.has(value)) return false;
576
- seen.add(value);
577
- return true;
578
- });
579
- };
580
- const parseMimes = (codecs) => codecs.toLowerCase().split(",").map((codec) => codec.trim().split(".")[0]);
581
-
582
- //#endregion
583
- //#region lib/dash/dash-utils.ts
584
- /**
585
- * Extracts StartRange and ExpectLength information from a string like "100-300"
586
- * @param range - The range string in the format "start-end"
587
- * @returns A tuple containing [StartRange, ExpectLength]
588
- */
589
- const parseRange = (range) => {
590
- const [startRange, end] = range.split("-").map(Number);
591
- return [startRange, end - startRange + 1];
592
- };
593
-
594
- //#endregion
595
- //#region lib/dash/dash-video.ts
596
- const PRIMARIES = {
597
- Unspecified: 0,
598
- BT_709: 1,
599
- BT_601_625: 5,
600
- BT_601_525: 6,
601
- BT_2020_and_2100: 9,
602
- SMPTE_ST_2113_and_EG_4321: 12
603
- };
604
- const TRANSFER = {
605
- Unspecified: 0,
606
- BT_709: 1,
607
- BT_601: 6,
608
- BT_2020: 14,
609
- BT_2100: 15,
610
- BT_2100_PQ: 16,
611
- BT_2100_HLG: 18
612
- };
613
- const MATRIX = {
614
- RGB: 0,
615
- YCbCr_BT_709: 1,
616
- YCbCr_BT_601_625: 5,
617
- YCbCr_BT_601_525: 6,
618
- YCbCr_BT_2020_and_2100: 9,
619
- ICtCp_BT_2100: 14
620
- };
621
- const parseVideoCodecFromMime = (mime) => {
622
- const target = mime.toLowerCase().trim().split(".")[0];
623
- const avc = [
624
- "avc1",
625
- "avc2",
626
- "avc3",
627
- "dva1",
628
- "dvav"
629
- ];
630
- const hevc = [
631
- "hev1",
632
- "hev2",
633
- "hev3",
634
- "hvc1",
635
- "hvc2",
636
- "hvc3",
637
- "dvh1",
638
- "dvhe",
639
- "lhv1",
640
- "lhe1"
641
- ];
642
- const vc1 = ["vc-1"];
643
- const vp8 = ["vp08", "vp8"];
644
- const vp9 = ["vp09", "vp9"];
645
- const av1 = ["av01"];
646
- if (avc.includes(target)) return "avc";
647
- if (hevc.includes(target)) return "hevc";
648
- if (vc1.includes(target)) return "vc1";
649
- if (vp8.includes(target)) return "vp8";
650
- if (vp9.includes(target)) return "vp9";
651
- if (av1.includes(target)) return "av1";
652
- throw new Error(`The MIME ${mime} is not supported as video codec`);
653
- };
654
- const parseDynamicRangeFromCicp = (primaries, transfer, matrix) => {
655
- if (transfer == 5) transfer = TRANSFER.BT_601;
656
- if (primaries == PRIMARIES.Unspecified && transfer == TRANSFER.Unspecified && matrix == MATRIX.RGB) return "sdr";
657
- else if ([PRIMARIES.BT_601_625, PRIMARIES.BT_601_525].includes(primaries)) return "sdr";
658
- else if (TRANSFER.BT_2100_PQ === transfer) return "hdr10";
659
- else if (TRANSFER.BT_2100_HLG === transfer) return "hlg";
660
- else return "sdr";
661
- };
662
- const parseVideoCodec = (codecs) => {
663
- for (const codec of codecs.toLowerCase().split(",")) {
664
- const mime = codec.trim().split(".")[0];
665
- try {
666
- return parseVideoCodecFromMime(mime);
667
- } catch (e) {
668
- continue;
669
- }
670
- }
671
- throw new Error(`No MIME types matched any supported Video Codecs in ${codecs}`);
672
- };
673
- const tryParseVideoCodec = (codecs) => {
674
- try {
675
- return parseVideoCodec(codecs);
676
- } catch (e) {
677
- return;
678
- }
679
- };
680
- const parseDynamicRange = (codecs, supplementalProps = [], essentialProps = []) => {
681
- if ([
682
- "dva1",
683
- "dvav",
684
- "dvhe",
685
- "dvh1"
686
- ].some((value) => codecs.startsWith(value))) return "dv";
687
- const primariesScheme = "urn:mpeg:mpegB:cicp:ColourPrimaries";
688
- const transferScheme = "urn:mpeg:mpegB:cicp:TransferCharacteristics";
689
- const matrixScheme = "urn:mpeg:mpegB:cicp:MatrixCoefficients";
690
- const allProps = [...essentialProps, ...supplementalProps];
691
- const getValues = (schemeIdUri) => allProps.filter((prop) => prop.schemeIdUri === schemeIdUri).map((prop) => parseInt(prop.value));
692
- return parseDynamicRangeFromCicp(getValues(primariesScheme).reduce((acc, current) => acc + current, 0), getValues(transferScheme).reduce((acc, current) => acc + current, 0), getValues(matrixScheme).reduce((acc, current) => acc + current, 0));
693
- };
694
-
695
- //#endregion
696
- //#region lib/dash/dash-subtitle.ts
697
- const parseSubtitleCodecFromMime = (mime) => {
698
- switch (mime.toLowerCase().trim().split(".")[0]) {
699
- case "srt":
700
- case "x-subrip": return "srt";
701
- case "ssa": return "ssa";
702
- case "ass": return "ass";
703
- case "ttml": return "ttml";
704
- case "vtt": return "vtt";
705
- case "stpp": return "stpp";
706
- case "wvtt": return "wvtt";
707
- default: throw new Error(`The MIME ${mime} is not supported as subtitle codec`);
708
- }
709
- };
710
- const parseSubtitleCodec = (codecs) => {
711
- const mimes = parseMimes(codecs);
712
- for (const mime of mimes) try {
713
- return parseSubtitleCodecFromMime(mime);
714
- } catch (e) {
715
- continue;
716
- }
717
- throw new Error(`No MIME types matched any supported Subtitle Codecs in ${codecs}`);
718
- };
719
- const tryParseSubtitleCodec = (codecs) => {
720
- try {
721
- return parseSubtitleCodec(codecs);
722
- } catch (e) {
723
- return;
724
- }
725
- };
726
- const checkIsClosedCaption = (roles = []) => {
727
- for (const role of roles) if (role.schemeIdUri === "urn:mpeg:dash:role:2011" && role.value === "caption") return true;
728
- return false;
729
- };
730
- const checkIsSdh = (accessibilities = []) => {
731
- for (const accessibility of accessibilities) {
732
- const { schemeIdUri, value } = accessibility;
733
- if (schemeIdUri === "urn:tva:metadata:cs:AudioPurposeCS:2007" && value === "2") return true;
734
- }
735
- return false;
736
- };
737
-
738
- //#endregion
739
- //#region lib/dash/dash-audio.ts
740
- const parseAudioCodecFromMime = (mime) => {
741
- switch (mime.toLowerCase().trim().split(".")[0]) {
742
- case "mp4a": return "aac";
743
- case "ac-3": return "ac3";
744
- case "ec-3": return "eac3";
745
- case "opus": return "opus";
746
- case "dtsc": return "dts";
747
- case "alac": return "alac";
748
- case "flac": return "flac";
749
- default: throw new Error(`The MIME ${mime} is not supported as audio codec`);
750
- }
751
- };
752
- const parseAudioCodec = (codecs) => {
753
- const mimes = parseMimes(codecs);
754
- for (const mime of mimes) try {
755
- return parseAudioCodecFromMime(mime);
756
- } catch (e) {
757
- continue;
758
- }
759
- throw new Error(`No MIME types matched any supported Audio Codecs in ${codecs}`);
760
- };
761
- const tryParseAudioCodec = (codecs) => {
762
- try {
763
- return parseAudioCodec(codecs);
764
- } catch (e) {
765
- return;
766
- }
767
- };
768
- const getDolbyDigitalPlusComplexityIndex = (supplementalProps = []) => {
769
- const targetScheme = "tag:dolby.com,2018:dash:EC3_ExtensionComplexityIndex:2018";
770
- for (const prop of supplementalProps) if (prop.schemeIdUri === targetScheme) return parseInt(prop.value);
771
- };
772
- const checkIsDescriptive = (accessibilities = []) => {
773
- for (const accessibility of accessibilities) {
774
- const { schemeIdUri, value } = accessibility;
775
- if (schemeIdUri == "urn:mpeg:dash:role:2011" && value === "descriptive" || schemeIdUri == "urn:tva:metadata:cs:AudioPurposeCS:2007" && value === "1") return true;
776
- }
777
- return false;
778
- };
779
- const parseChannels = (channels) => {
780
- const isDigit = (char) => char >= "0" && char <= "9";
781
- if (typeof channels === "string") {
782
- if (channels.toUpperCase() == "A000") return 2;
783
- else if (channels.toUpperCase() == "F801") return 5.1;
784
- else if (isDigit(channels.replace("ch", "").replace(".", "")[0])) return parseFloat(channels.replace("ch", ""));
785
- throw new Error(`Unsupported audio channels value, '${channels}'`);
786
- }
787
- return parseFloat(channels);
788
- };
789
-
790
- //#endregion
791
- //#region lib/shared/pipe.ts
792
- const pipe = (...fns) => (value) => fns.reduce((acc, fn) => fn(acc), value);
793
-
794
- //#endregion
795
- //#region lib/dash/dash-extractor.ts
796
- const createMediaStreamInfo = (params) => {
797
- const codecs = params.contentType === "text" && !params.mimeType?.includes("mp4") ? params.mimeType?.split("/")[1] : params.codecs;
798
- if (!params.codecs && codecs) params.codecs = codecs;
799
- if (params.codecs) {
800
- const videoCodec = tryParseVideoCodec(params.codecs);
801
- if (videoCodec) return new VideoStreamInfo({ codec: videoCodec });
802
- const audioCodec = tryParseAudioCodec(params.codecs);
803
- if (audioCodec) return new AudioStreamInfo({ codec: audioCodec });
804
- const subtitleCodec = tryParseSubtitleCodec(params.codecs);
805
- if (subtitleCodec) return new SubtitleStreamInfo({ codec: subtitleCodec });
806
- } else {
807
- const type = params.contentType || params.mimeType?.split("/")[0];
808
- if (type === "video") return new VideoStreamInfo();
809
- if (type === "audio") return new AudioStreamInfo();
810
- if (type === "text") return new SubtitleStreamInfo();
811
- }
812
- throw new Error("Unable to determine the type of a track, cannot continue...");
813
- };
814
- const selectNonEmpty = (args) => {
815
- for (const element of args.elements) {
816
- const results = element.getElementsByTagName(args.tag);
817
- if (results.length) return results;
818
- }
819
- };
820
- const toSchemeValueArray = (elements) => {
821
- const results = [];
822
- if (!elements) return results;
823
- for (const element of elements) {
824
- const schemeIdUri = element.getAttribute("schemeIdUri");
825
- const value = element.getAttribute("value");
826
- results.push({
827
- schemeIdUri,
828
- value
829
- });
830
- }
831
- return results;
832
- };
833
- const getTagAttrs = (tag, ...elements) => {
834
- return pipe(selectNonEmpty, toSchemeValueArray)({
835
- tag,
836
- elements
837
- });
838
- };
839
- var DashExtractor = class DashExtractor {
840
- static #DEFAULT_METHOD = ENCRYPT_METHODS.CENC;
841
- get extractorType() {
842
- return EXTRACTOR_TYPES.MPEG_DASH;
843
- }
844
- #mpdUrl = "";
845
- #baseUrl = "";
846
- #mpdContent = "";
847
- #parserConfig;
848
- constructor(parserConfig) {
849
- this.#parserConfig = parserConfig;
850
- this.#setInitUrl();
851
- }
852
- #setInitUrl() {
853
- this.#mpdUrl = this.#parserConfig.url ?? "";
854
- this.#baseUrl = this.#parserConfig.baseUrl ?? this.#mpdUrl;
855
- }
856
- #extendBaseUrl(node, baseUrl) {
857
- const target = node.getElementsByTagName("BaseURL").filter((n) => !!n.parentNode?.isSameNode(node))[0];
858
- if (target?.textContent) return combineUrl(baseUrl, target.textContent);
859
- return baseUrl;
860
- }
861
- #getFrameRate(node) {
862
- const frameRate = node.getAttribute("frameRate");
863
- if (!frameRate || !frameRate.includes("/")) return;
864
- const d = Number(frameRate.split("/")[0]) / Number(frameRate.split("/")[1]);
865
- return Number(d.toFixed(3));
866
- }
867
- async extractStreams(rawText) {
868
- const streamInfos = [];
869
- this.#mpdContent = rawText;
870
- const mpdElement = new DOMParser().parseFromString(this.#mpdContent, "text/xml").getElementsByTagName("MPD")[0];
871
- const isLive = mpdElement.getAttribute("type") === "dynamic";
872
- const availabilityStartTime = mpdElement.getAttribute("availabilityStartTime");
873
- const timeShiftBufferDepth = mpdElement.getAttribute("timeShiftBufferDepth") || "PT1M";
874
- const publishTime = mpdElement.getAttribute("publishTime");
875
- const mediaPresentationDuration = mpdElement.getAttribute("mediaPresentationDuration");
876
- const baseUrlElement = mpdElement.getElementsByTagName("BaseURL")[0];
877
- if (baseUrlElement?.textContent) {
878
- let baseUrl = baseUrlElement.textContent;
879
- if (baseUrl.includes("kkbox.com.tw/")) baseUrl = baseUrl.replace("//https:%2F%2F", "//");
880
- this.#baseUrl = combineUrl(this.#mpdUrl, baseUrl);
881
- }
882
- const periods = mpdElement.getElementsByTagName("Period");
883
- for (const period of periods) {
884
- const periodDuration = period.getAttribute("duration");
885
- const periodId = period.getAttribute("id");
886
- const periodDurationSeconds = Temporal.Duration.from(periodDuration || mediaPresentationDuration || "PT0S").total("seconds");
887
- let segBaseUrl = this.#extendBaseUrl(period, this.#baseUrl);
888
- const adaptationSetsBaseUrl = segBaseUrl;
889
- const adaptationSets = period.getElementsByTagName("AdaptationSet");
890
- for (const adaptationSet of adaptationSets) {
891
- segBaseUrl = this.#extendBaseUrl(adaptationSet, segBaseUrl);
892
- const representationsBaseUrl = segBaseUrl;
893
- let contentType = adaptationSet.getAttribute("contentType");
894
- let mimeType = adaptationSet.getAttribute("mimeType");
895
- const frameRate = this.#getFrameRate(adaptationSet);
896
- const representations = adaptationSet.getElementsByTagName("Representation");
897
- for (const representation of representations) {
898
- segBaseUrl = this.#extendBaseUrl(representation, segBaseUrl);
899
- if (!contentType) contentType = representation.getAttribute("contentType");
900
- if (!mimeType) mimeType = representation.getAttribute("mimeType");
901
- const codecs = representation.getAttribute("codecs") || adaptationSet.getAttribute("codecs");
902
- const widthParameterString = representation.getAttribute("width");
903
- const heightParameterString = representation.getAttribute("height");
904
- const roles = getTagAttrs("Role", representation, adaptationSet);
905
- const supplementalProps = getTagAttrs("SupplementalProperty", representation, adaptationSet);
906
- const essentialProps = getTagAttrs("EssentialProperty", representation, adaptationSet);
907
- const accessibilities = getTagAttrs("Accessibility", representation, adaptationSet);
908
- const channelsString = getTagAttrs("AudioChannelConfiguration", adaptationSet, representation)[0]?.value;
909
- const streamInfo = createMediaStreamInfo({
910
- codecs,
911
- contentType,
912
- mimeType
913
- });
914
- const bitrate = Number(representation.getAttribute("bandwidth") ?? "");
915
- streamInfo.languageCode = this.#filterLanguage(representation.getAttribute("lang") || adaptationSet.getAttribute("lang"));
916
- if (streamInfo.type === "video") {
917
- streamInfo.bitrate = bitrate;
918
- streamInfo.width = Number(widthParameterString);
919
- streamInfo.height = Number(heightParameterString);
920
- streamInfo.frameRate = frameRate || this.#getFrameRate(representation);
921
- if (supplementalProps && essentialProps) streamInfo.dynamicRange = parseDynamicRange(codecs, supplementalProps, essentialProps);
922
- } else if (streamInfo.type === "audio") {
923
- streamInfo.bitrate = bitrate;
924
- if (accessibilities) streamInfo.descriptive = checkIsDescriptive(accessibilities);
925
- if (supplementalProps) streamInfo.joc = getDolbyDigitalPlusComplexityIndex(supplementalProps);
926
- if (channelsString) {
927
- streamInfo.numberOfChannels = parseChannels(channelsString);
928
- streamInfo.channels = channelsString;
929
- }
930
- } else if (streamInfo.type === "subtitle") {
931
- streamInfo.bitrate = bitrate;
932
- if (roles) streamInfo.cc = checkIsClosedCaption(roles);
933
- if (accessibilities) streamInfo.sdh = checkIsSdh(accessibilities);
934
- }
935
- streamInfo.url = this.#mpdUrl;
936
- streamInfo.originalUrl = this.#parserConfig.originalUrl;
937
- streamInfo.playlist = new Playlist();
938
- streamInfo.playlist.mediaParts.push(new MediaPart());
939
- streamInfo.periodId = periodId;
940
- streamInfo.groupId = representation.getAttribute("id");
941
- streamInfo.codecs = codecs;
942
- const volumeAdjust = representation.getAttribute("volumeAdjust");
943
- if (volumeAdjust) streamInfo.groupId = streamInfo.groupId + "-" + volumeAdjust;
944
- const mType = representation.getAttribute("mimeType") || adaptationSet.getAttribute("mimeType");
945
- if (mType) {
946
- const mTypeSplit = mType.split("/");
947
- streamInfo.extension = mTypeSplit.length === 2 ? mTypeSplit[1] : null;
948
- }
949
- const role = roles?.[0];
950
- if (role) {
951
- const roleValue = role.value;
952
- const capitalize = (word) => word.charAt(0).toUpperCase() + word.slice(1);
953
- streamInfo.role = ROLE_TYPE[roleValue.split("-").map(capitalize).join("")];
954
- }
955
- streamInfo.playlist.isLive = isLive;
956
- if (timeShiftBufferDepth) streamInfo.playlist.refreshIntervalMs = Temporal.Duration.from(timeShiftBufferDepth).total("milliseconds") / 2;
957
- if (publishTime) streamInfo.publishTime = new Date(publishTime);
958
- const segmentBaseElement = representation.getElementsByTagName("SegmentBase")[0];
959
- if (segmentBaseElement) {
960
- const initialization = segmentBaseElement.getElementsByTagName("Initialization")[0];
961
- if (initialization) {
962
- const sourceUrl = initialization.getAttribute("sourceURL");
963
- if (!sourceUrl) {
964
- const mediaSegment = new MediaSegment();
965
- mediaSegment.index = 0;
966
- mediaSegment.url = segBaseUrl;
967
- mediaSegment.duration = periodDurationSeconds;
968
- streamInfo.playlist.mediaParts[0].mediaSegments.push(mediaSegment);
969
- } else {
970
- const initUrl = combineUrl(segBaseUrl, sourceUrl);
971
- const initRange = initialization.getAttribute("range");
972
- const initSegment = new MediaSegment();
973
- initSegment.index = -1;
974
- initSegment.url = initUrl;
975
- if (initRange) {
976
- const [start, expect] = parseRange(initRange);
977
- initSegment.startRange = start;
978
- initSegment.expectLength = expect;
979
- }
980
- streamInfo.playlist.mediaInit = initSegment;
981
- }
982
- }
983
- }
984
- const segmentList = representation.getElementsByTagName("SegmentList")[0];
985
- if (segmentList) {
986
- const durationStr = segmentList.getAttribute("duration");
987
- const initialization = segmentList.getElementsByTagName("Initialization")[0];
988
- if (initialization) {
989
- const sourceUrl = initialization.getAttribute("sourceURL");
990
- const initUrl = combineUrl(segBaseUrl, sourceUrl);
991
- const initRange = initialization.getAttribute("range");
992
- const initSegment = new MediaSegment();
993
- initSegment.index = -1;
994
- initSegment.url = initUrl;
995
- if (initRange) {
996
- const [start, expect] = parseRange(initRange);
997
- initSegment.startRange = start;
998
- initSegment.expectLength = expect;
999
- }
1000
- streamInfo.playlist.mediaInit = initSegment;
1001
- }
1002
- const segmentUrls = segmentList.getElementsByTagName("SegmentURL");
1003
- const timescaleStr = segmentList.getAttribute("timescale") || "1";
1004
- for (let segmentIndex = 0; segmentIndex < segmentUrls.length; segmentIndex++) {
1005
- const segmentUrl = segmentUrls[segmentIndex];
1006
- const mediaUrl = combineUrl(segBaseUrl, segmentUrl.getAttribute("media"));
1007
- const mediaRange = segmentUrl.getAttribute("mediaRange");
1008
- const timescale = Number(timescaleStr);
1009
- const duration = Number(durationStr);
1010
- const segment = new MediaSegment();
1011
- segment.index = segmentIndex;
1012
- segment.url = mediaUrl;
1013
- segment.duration = duration / timescale;
1014
- if (mediaRange) {
1015
- const [start, expect] = parseRange(mediaRange);
1016
- segment.startRange = start;
1017
- segment.expectLength = expect;
1018
- }
1019
- streamInfo.playlist.mediaParts[0].mediaSegments.push(segment);
1020
- }
1021
- }
1022
- const segmentTemplateElementsOuter = adaptationSet.getElementsByTagName("SegmentTemplate");
1023
- const segmentTemplateElements = representation.getElementsByTagName("SegmentTemplate");
1024
- if (segmentTemplateElementsOuter.length || segmentTemplateElements.length) {
1025
- const segmentTemplate = segmentTemplateElements[0] || segmentTemplateElementsOuter[0];
1026
- const segmentTemplateOuter = segmentTemplateElementsOuter[0] || segmentTemplateElements[0];
1027
- const varDic = {};
1028
- varDic[DASH_TAGS.TemplateRepresentationID] = streamInfo.groupId;
1029
- varDic[DASH_TAGS.TemplateBandwidth] = bitrate;
1030
- const presentationTimeOffsetStr = segmentTemplate.getAttribute("presentationTimeOffset") || segmentTemplateOuter.getAttribute("presentationTimeOffset") || "0";
1031
- const timescaleStr = segmentTemplate.getAttribute("timescale") || segmentTemplateOuter.getAttribute("timescale") || "1";
1032
- const durationStr = segmentTemplate.getAttribute("duration") || segmentTemplateOuter.getAttribute("duration");
1033
- const startNumberStr = segmentTemplate.getAttribute("startNumber") || segmentTemplateOuter.getAttribute("startNumber") || "1";
1034
- const initialization = segmentTemplate.getAttribute("initialization") || segmentTemplateOuter.getAttribute("initialization");
1035
- if (initialization) {
1036
- const _init = replaceVars(initialization, varDic);
1037
- const initUrl = combineUrl(segBaseUrl, _init);
1038
- const mediaSegment = new MediaSegment();
1039
- mediaSegment.index = -1;
1040
- mediaSegment.url = initUrl;
1041
- streamInfo.playlist.mediaInit = mediaSegment;
1042
- }
1043
- const mediaTemplate = segmentTemplate.getAttribute("media") || segmentTemplateOuter.getAttribute("media");
1044
- const segmentTimeline = segmentTemplate.getElementsByTagName("SegmentTimeline")[0];
1045
- if (segmentTimeline) {
1046
- const Ss = segmentTimeline.getElementsByTagName("S");
1047
- let segNumber = Number(startNumberStr);
1048
- let currentTime = 0;
1049
- let segIndex = 0;
1050
- for (const s of Ss) {
1051
- const _startTimeStr = s.getAttribute("t");
1052
- const _durationStr = s.getAttribute("d");
1053
- const _repeatCountStr = s.getAttribute("r");
1054
- if (_startTimeStr) currentTime = Number(_startTimeStr);
1055
- const _duration = Number(_durationStr);
1056
- const timescale = Number(timescaleStr);
1057
- let _repeatCount = Number(_repeatCountStr);
1058
- varDic[DASH_TAGS.TemplateTime] = currentTime;
1059
- varDic[DASH_TAGS.TemplateNumber] = segNumber++;
1060
- const hasTime = mediaTemplate?.includes(DASH_TAGS.TemplateTime);
1061
- const media = replaceVars(mediaTemplate, varDic);
1062
- const mediaUrl = combineUrl(segBaseUrl, media);
1063
- const mediaSegment = new MediaSegment();
1064
- mediaSegment.url = mediaUrl;
1065
- if (hasTime) mediaSegment.nameFromVar = currentTime.toString();
1066
- mediaSegment.duration = _duration / timescale;
1067
- mediaSegment.index = segIndex++;
1068
- streamInfo.playlist.mediaParts[0].mediaSegments.push(mediaSegment);
1069
- if (_repeatCount < 0) _repeatCount = Math.ceil(periodDurationSeconds * timescale / _duration) - 1;
1070
- for (let i = 0; i < _repeatCount; i++) {
1071
- currentTime += _duration;
1072
- const _mediaSegment = new MediaSegment();
1073
- varDic[DASH_TAGS.TemplateTime] = currentTime;
1074
- varDic[DASH_TAGS.TemplateNumber] = segNumber++;
1075
- const _hashTime = mediaTemplate?.includes(DASH_TAGS.TemplateTime);
1076
- const _media = replaceVars(mediaTemplate, varDic);
1077
- _mediaSegment.url = combineUrl(segBaseUrl, _media);
1078
- _mediaSegment.index = segIndex++;
1079
- _mediaSegment.duration = _duration / timescale;
1080
- if (_hashTime) _mediaSegment.nameFromVar = currentTime.toString();
1081
- streamInfo.playlist.mediaParts[0].mediaSegments.push(_mediaSegment);
1082
- }
1083
- currentTime += _duration;
1084
- }
1085
- } else {
1086
- const timescale = Number(timescaleStr);
1087
- let startNumber = Number(startNumberStr);
1088
- const duration = Number(durationStr);
1089
- let totalNumber = Math.ceil(periodDurationSeconds * timescale / duration);
1090
- if (totalNumber === 0 && isLive) {
1091
- const now = Date.now();
1092
- const availableTime = new Date(availabilityStartTime);
1093
- const offsetMs = Number(presentationTimeOffsetStr) / 1e3;
1094
- availableTime.setUTCMilliseconds(availableTime.getUTCMilliseconds() + offsetMs);
1095
- const ts = (now - availableTime.getTime()) / 1e3;
1096
- const updateTs = Temporal.Duration.from(timeShiftBufferDepth).total("seconds");
1097
- startNumber += (ts - updateTs) * timescale / duration;
1098
- totalNumber = updateTs * timescale / duration;
1099
- }
1100
- for (let index = startNumber, segIndex = 0; index < startNumber + totalNumber; index++, segIndex++) {
1101
- varDic[DASH_TAGS.TemplateNumber] = index;
1102
- const hasNumber = mediaTemplate.includes(DASH_TAGS.TemplateNumber);
1103
- const media = replaceVars(mediaTemplate, varDic);
1104
- const mediaUrl = combineUrl(segBaseUrl, media);
1105
- const mediaSegment = new MediaSegment();
1106
- mediaSegment.url = mediaUrl;
1107
- if (hasNumber) mediaSegment.nameFromVar = index.toString();
1108
- mediaSegment.index = isLive ? index : segIndex;
1109
- mediaSegment.duration = duration / timescale;
1110
- streamInfo.playlist.mediaParts[0].mediaSegments.push(mediaSegment);
1111
- }
1112
- }
1113
- }
1114
- if (streamInfo.playlist.mediaParts[0].mediaSegments.length === 0) {
1115
- const mediaSegment = new MediaSegment();
1116
- mediaSegment.index = 0;
1117
- mediaSegment.url = segBaseUrl;
1118
- mediaSegment.duration = periodDurationSeconds;
1119
- streamInfo.playlist.mediaParts[0].mediaSegments.push(mediaSegment);
1120
- }
1121
- const adaptationSetProtections = adaptationSet.getElementsByTagName("ContentProtection");
1122
- const representationProtections = representation.getElementsByTagName("ContentProtection");
1123
- const contentProtections = representationProtections[0] ? representationProtections : adaptationSetProtections;
1124
- if (contentProtections.length) {
1125
- const encryptInfo = new EncryptInfo();
1126
- encryptInfo.method = DashExtractor.#DEFAULT_METHOD;
1127
- const widevineSystemId = "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed";
1128
- const playreadySystemId = "9a04f079-9840-4286-ab92-e65be0885f95";
1129
- for (const contentProtection of contentProtections) {
1130
- const schemeIdUri = contentProtection.getAttribute("schemeIdUri");
1131
- const drmData = {
1132
- keyId: contentProtection.getAttribute("cenc:default_KID") || void 0,
1133
- pssh: contentProtection.getElementsByTagName("cenc:pssh")[0]?.textContent || void 0
1134
- };
1135
- if (schemeIdUri?.includes(widevineSystemId)) encryptInfo.drm.widevine = drmData;
1136
- else if (schemeIdUri?.includes(playreadySystemId)) encryptInfo.drm.playready = drmData;
1137
- else continue;
1138
- }
1139
- if (streamInfo.playlist.mediaInit) streamInfo.playlist.mediaInit.encryptInfo = encryptInfo;
1140
- const segments = streamInfo.playlist.mediaParts[0].mediaSegments;
1141
- for (const segment of segments) if (!segment.encryptInfo) segment.encryptInfo = encryptInfo;
1142
- }
1143
- const _index = streamInfos.findIndex((item) => item.type === streamInfo.type && item.periodId !== streamInfo.periodId && item.groupId === streamInfo.groupId && (item.type === "video" && streamInfo.type === "video" ? item.width === streamInfo.width && item.height === streamInfo.height : true));
1144
- if (_index > -1) if (isLive) {} else if (streamInfos[_index].playlist.mediaParts.at(-1).mediaSegments.at(-1).url !== streamInfo.playlist.mediaParts[0].mediaSegments.at(-1)?.url) {
1145
- const startIndex = streamInfos[_index].playlist.mediaParts.at(-1).mediaSegments.at(-1).index + 1;
1146
- const segments = streamInfo.playlist.mediaParts[0].mediaSegments;
1147
- for (const segment of segments) segment.index += startIndex;
1148
- const mediaPart = new MediaPart();
1149
- mediaPart.mediaSegments = streamInfos[_index].playlist.mediaParts[0].mediaSegments;
1150
- streamInfos[_index].playlist.mediaParts.push(mediaPart);
1151
- } else streamInfos[_index].playlist.mediaParts.at(-1).mediaSegments.at(-1).duration += streamInfo.playlist.mediaParts[0].mediaSegments.reduce((sum, segment) => sum + segment.duration, 0);
1152
- else {
1153
- if (streamInfo.type === "subtitle" && streamInfo.extension === "mp4") streamInfo.extension = "m4s";
1154
- if (streamInfo.type !== "subtitle" && (streamInfo.extension == null || streamInfo.playlist.mediaParts.reduce((sum, part) => sum + part.mediaSegments.length, 0) > 1)) streamInfo.extension = "m4s";
1155
- streamInfos.push(streamInfo);
1156
- }
1157
- segBaseUrl = representationsBaseUrl;
1158
- }
1159
- segBaseUrl = adaptationSetsBaseUrl;
1160
- }
1161
- }
1162
- const audioList = streamInfos.filter((stream) => stream.type === "audio");
1163
- const subtitleList = streamInfos.filter((stream) => stream.type === "subtitle");
1164
- const videoList = streamInfos.filter((stream) => stream.type === "video");
1165
- for (const video of videoList) {
1166
- const audioGroupId = audioList.toSorted((a, b$1) => (b$1.bitrate || 0) - (a.bitrate || 0)).at(0)?.groupId;
1167
- const subtitleGroupId = subtitleList.toSorted((a, b$1) => (b$1.bitrate || 0) - (a.bitrate || 0)).at(0)?.groupId;
1168
- if (audioGroupId) video.audioId = audioGroupId;
1169
- if (subtitleGroupId) video.subtitleId = subtitleGroupId;
1170
- }
1171
- return streamInfos;
1172
- }
1173
- #filterLanguage(v) {
1174
- if (!v) return;
1175
- return v;
1176
- }
1177
- async refreshPlayList(streamInfos) {
1178
- if (!streamInfos.length) return;
1179
- const response = await fetch(this.#parserConfig.url, this.#parserConfig.headers).catch(() => fetch(this.#parserConfig.originalUrl, this.#parserConfig.headers));
1180
- const rawText = await response.text();
1181
- const url = response.url;
1182
- this.#parserConfig.url = url;
1183
- this.#setInitUrl();
1184
- const newStreams = await this.extractStreams(rawText);
1185
- for (const streamInfo of streamInfos) {
1186
- let results = newStreams.filter((n) => n.toShortString() === streamInfo.toShortString());
1187
- if (!results.length) results = newStreams.filter((n) => n.playlist?.mediaInit?.url === streamInfo.playlist?.mediaInit?.url);
1188
- if (results.length) streamInfo.playlist.mediaParts = results.at(0).playlist.mediaParts;
1189
- }
1190
- await this.#processUrl(streamInfos);
1191
- }
1192
- async #processUrl(streamInfos) {
1193
- for (const spec of streamInfos) {
1194
- const playlist = spec.playlist;
1195
- if (!playlist) continue;
1196
- if (playlist.mediaInit) playlist.mediaInit.url = this.preProcessUrl(playlist.mediaInit.url);
1197
- for (const part of playlist.mediaParts) for (const segment of part.mediaSegments) segment.url = this.preProcessUrl(segment.url);
1198
- }
1199
- }
1200
- async fetchPlayList(streamInfos) {
1201
- this.#processUrl(streamInfos);
1202
- }
1203
- preProcessUrl(url) {
1204
- for (const processor of this.#parserConfig.urlProcessors) if (processor.canProcess(this.extractorType, url, this.#parserConfig)) url = processor.process(url, this.#parserConfig);
1205
- return url;
1206
- }
1207
- preProcessContent() {
1208
- for (const processor of this.#parserConfig.contentProcessors) if (processor.canProcess(this.extractorType, this.#mpdContent, this.#parserConfig)) this.#mpdContent = processor.process(this.#mpdContent, this.#parserConfig);
1209
- }
1210
- };
1211
-
1212
- //#endregion
1213
- //#region lib/hls/hls-utils.ts
1214
- /**
1215
- * Extracts length and optional start values from a string formatted as "n[@o]".
1216
- * @param input - The input string.
1217
- * @returns A tuple containing [n (length), o (start)].
1218
- */
1219
- function getRange(input) {
1220
- const parts = input.split("@");
1221
- switch (parts.length) {
1222
- case 0: return [0, null];
1223
- case 1: return [parseInt(parts[0], 10), null];
1224
- case 2: return [parseInt(parts[0], 10), parseInt(parts[1], 10)];
1225
- default: return [0, null];
1226
- }
1227
- }
1228
-
1229
- //#endregion
1230
- //#region lib/hls/hls-extractor.ts
1231
- var HlsExtractor = class {
1232
- get extractorType() {
1233
- return EXTRACTOR_TYPES.HLS;
1234
- }
1235
- #m3u8Url = "";
1236
- #baseUrl = "";
1237
- #m3u8Content = "";
1238
- #masterM3u8Flag = false;
1239
- parserConfig;
1240
- constructor(parserConfig) {
1241
- this.parserConfig = parserConfig;
1242
- this.#m3u8Url = parserConfig.url || "";
1243
- this.#setBaseUrl();
1244
- }
1245
- #setBaseUrl() {
1246
- this.#baseUrl = this.parserConfig.baseUrl || this.#m3u8Url;
1247
- }
1248
- preProcessContent() {
1249
- this.#m3u8Content = this.#m3u8Content.trim();
1250
- if (!this.#m3u8Content.startsWith(HLS_TAGS.extM3u)) throw new Error("Invalid m3u8");
1251
- for (const processor of this.parserConfig.contentProcessors) if (processor.canProcess(this.extractorType, this.#m3u8Content, this.parserConfig)) this.#m3u8Content = processor.process(this.#m3u8Content, this.parserConfig);
1252
- }
1253
- preProcessUrl(url) {
1254
- let result = url;
1255
- for (const processor of this.parserConfig.urlProcessors) if (processor.canProcess(this.extractorType, url, this.parserConfig)) result = processor.process(url, this.parserConfig);
1256
- return result;
1257
- }
1258
- async #parseMasterList() {
1259
- this.#masterM3u8Flag = true;
1260
- const streamInfos = [];
1261
- let expectPlaylist = false;
1262
- let streamInfo = new VideoStreamInfo();
1263
- const lines = this.#m3u8Content.split("\n");
1264
- for (const line of lines) {
1265
- if (!line.trim()) continue;
1266
- if (line.startsWith(HLS_TAGS.extXStreamInf)) {
1267
- streamInfo = new VideoStreamInfo();
1268
- streamInfo.originalUrl = this.parserConfig.originalUrl;
1269
- const bandwidth = getAttribute(line, "AVERAGE-BANDWIDTH") || getAttribute(line, "BANDWIDTH");
1270
- streamInfo.bitrate = Number(bandwidth || 0);
1271
- streamInfo.codecs = getAttribute(line, "CODECS");
1272
- const resolution = getAttribute(line, "RESOLUTION");
1273
- const [widthString, heightString] = resolution.split("x");
1274
- streamInfo.width = parseInt(widthString);
1275
- streamInfo.height = parseInt(heightString);
1276
- streamInfo.resolution = resolution;
1277
- const frameRate = getAttribute(line, "FRAME-RATE");
1278
- if (frameRate) streamInfo.frameRate = Number(frameRate);
1279
- const audioId = getAttribute(line, "AUDIO");
1280
- if (audioId) streamInfo.audioId = audioId;
1281
- const videoId = getAttribute(line, "VIDEO");
1282
- if (videoId) streamInfo.videoId = videoId;
1283
- const subtitleId = getAttribute(line, "SUBTITLES");
1284
- if (subtitleId) streamInfo.subtitleId = subtitleId;
1285
- const videoRange = getAttribute(line, "VIDEO-RANGE");
1286
- if (videoRange) streamInfo.videoRange = videoRange;
1287
- if (streamInfo.codecs && streamInfo.audioId) streamInfo.codecs = streamInfo.codecs.split(",")[0];
1288
- expectPlaylist = true;
1289
- } else if (line.startsWith(HLS_TAGS.extXMedia)) {
1290
- streamInfo = new VideoStreamInfo();
1291
- const type = getAttribute(line, "TYPE");
1292
- if (type === "VIDEO") streamInfo = new VideoStreamInfo();
1293
- if (type === "AUDIO") streamInfo = new AudioStreamInfo();
1294
- if (type === "SUBTITLES") streamInfo = new SubtitleStreamInfo();
1295
- if (type === "CLOSED-CAPTIONS") {
1296
- streamInfo = new SubtitleStreamInfo();
1297
- streamInfo.cc = true;
1298
- continue;
1299
- }
1300
- let url = getAttribute(line, "URI");
1301
- if (!url) continue;
1302
- url = combineUrl(this.#baseUrl, url);
1303
- streamInfo.url = this.preProcessUrl(url);
1304
- const groupId = getAttribute(line, "GROUP-ID");
1305
- if (groupId) streamInfo.groupId = groupId;
1306
- const language = getAttribute(line, "LANGUAGE");
1307
- if (language) streamInfo.languageCode = language;
1308
- const name = getAttribute(line, "NAME");
1309
- if (name) streamInfo.name = name;
1310
- const defaultFlag = getAttribute(line, "DEFAULT");
1311
- if (defaultFlag) streamInfo.default = defaultFlag.toLowerCase() === "yes";
1312
- const channelsString = getAttribute(line, "CHANNELS");
1313
- if (channelsString) {
1314
- streamInfo.channels = channelsString;
1315
- if (streamInfo.type === "audio") streamInfo.numberOfChannels = parseFloat(channelsString);
1316
- }
1317
- const characteristics = getAttribute(line, "CHARACTERISTICS");
1318
- if (characteristics) streamInfo.characteristics = characteristics.split(",").at(-1)?.split(".").at(-1);
1319
- streamInfos.push(streamInfo);
1320
- } else if (line.startsWith("#")) continue;
1321
- else if (expectPlaylist) {
1322
- const url = combineUrl(this.#baseUrl, line);
1323
- streamInfo.url = this.preProcessUrl(url);
1324
- expectPlaylist = false;
1325
- streamInfos.push(streamInfo);
1326
- }
1327
- }
1328
- return streamInfos;
1329
- }
1330
- async #parseList() {
1331
- let hasAd = false;
1332
- const allowHlsMultiExtMap = this.parserConfig.customParserArgs["allowHlsMultiExtMap"] === "true";
1333
- if (allowHlsMultiExtMap) console.log(`allowHlsMultiExtMap is set to true`);
1334
- let expectSegment = false;
1335
- let isEndList = false;
1336
- let segIndex = 0;
1337
- let isAd = false;
1338
- const playlist = new Playlist();
1339
- const mediaParts = [];
1340
- const currentEncryptInfo = new EncryptInfo();
1341
- if (this.parserConfig.customMethod) currentEncryptInfo.method = this.parserConfig.customMethod;
1342
- if (this.parserConfig.customKey) currentEncryptInfo.key = this.parserConfig.customKey;
1343
- if (this.parserConfig.customIv) currentEncryptInfo.iv = this.parserConfig.customIv;
1344
- let lastKeyLine = "";
1345
- let segment = new MediaSegment();
1346
- let segments = [];
1347
- const lines = this.#m3u8Content.split("\n");
1348
- for (const line of lines) {
1349
- if (!line.trim()) continue;
1350
- if (line.startsWith(HLS_TAGS.extXByterange)) {
1351
- const [n, o] = getRange(getAttribute(line));
1352
- segment.expectLength = n;
1353
- segment.startRange = o || (segments.at(-1)?.startRange || 0) + (segments.at(-1)?.expectLength || 0);
1354
- expectSegment = true;
1355
- } else if (line.startsWith("#UPLYNK-SEGMENT")) {
1356
- if (line.includes(",ad")) isAd = true;
1357
- else if (line.includes(",segment")) isAd = false;
1358
- } else if (isAd) continue;
1359
- else if (line.startsWith(HLS_TAGS.extXTargetDuration)) playlist.targetDuration = Number(getAttribute(line));
1360
- else if (line.startsWith(HLS_TAGS.extXMediaSequence)) segIndex = Number(getAttribute(line));
1361
- else if (line.startsWith(HLS_TAGS.extXProgramDateTime)) segment.dateTime = new Date(getAttribute(line));
1362
- else if (line.startsWith(HLS_TAGS.extXDiscontinuity)) {
1363
- if (hasAd && mediaParts.length) {
1364
- segments = mediaParts.at(-1)?.mediaSegments || [];
1365
- mediaParts.pop();
1366
- hasAd = false;
1367
- continue;
1368
- }
1369
- if (hasAd && !segments.length) continue;
1370
- mediaParts.push(new MediaPart(segments));
1371
- segments = [];
1372
- } else if (line.startsWith(HLS_TAGS.extXKey)) {
1373
- if (getAttribute(line, "URI") !== getAttribute(lastKeyLine, "URI")) {
1374
- const parsedInfo = await this.#parseKey(line);
1375
- currentEncryptInfo.method = parsedInfo.method;
1376
- currentEncryptInfo.key = parsedInfo.key;
1377
- currentEncryptInfo.iv = parsedInfo.iv;
1378
- }
1379
- lastKeyLine = line;
1380
- } else if (line.startsWith(HLS_TAGS.extInf)) {
1381
- const tmp = getAttribute(line).split(",");
1382
- segment.duration = Number(tmp[0]);
1383
- segment.index = segIndex;
1384
- if (currentEncryptInfo.method != ENCRYPT_METHODS.NONE) {
1385
- segment.encryptInfo.method = currentEncryptInfo.method;
1386
- segment.encryptInfo.key = currentEncryptInfo.key;
1387
- segment.encryptInfo.iv = currentEncryptInfo.iv;
1388
- }
1389
- expectSegment = true;
1390
- segIndex++;
1391
- } else if (line.startsWith(HLS_TAGS.extXEndlist)) {
1392
- if (segments.length > 0) mediaParts.push(new MediaPart(segments));
1393
- segments = [];
1394
- isEndList = true;
1395
- } else if (line.startsWith(HLS_TAGS.extXMap)) if (!playlist.mediaInit || hasAd) {
1396
- const mediaSegment = new MediaSegment();
1397
- mediaSegment.url = this.preProcessUrl(combineUrl(this.#baseUrl, getAttribute(line, "URI")));
1398
- mediaSegment.index = -1;
1399
- playlist.mediaInit = mediaSegment;
1400
- if (line.includes("BYTERANGE")) {
1401
- const [n, o] = getRange(getAttribute(line, "BYTERANGE"));
1402
- mediaSegment.expectLength = n;
1403
- mediaSegment.startRange = o || 0;
1404
- }
1405
- if (currentEncryptInfo.method === ENCRYPT_METHODS.NONE) continue;
1406
- playlist.mediaInit.encryptInfo.method = currentEncryptInfo.method;
1407
- playlist.mediaInit.encryptInfo.key = currentEncryptInfo.key;
1408
- playlist.mediaInit.encryptInfo.iv = currentEncryptInfo.iv;
1409
- } else {
1410
- if (segments.length) mediaParts.push(new MediaPart(segments));
1411
- segments = [];
1412
- if (!allowHlsMultiExtMap) {
1413
- isEndList = true;
1414
- break;
1415
- }
1416
- }
1417
- else if (line.startsWith("#")) continue;
1418
- else if (line.startsWith("\r\n")) continue;
1419
- else if (expectSegment) {
1420
- const segUrl = this.preProcessUrl(combineUrl(this.#baseUrl, line));
1421
- segment.url = segUrl;
1422
- segments.push(segment);
1423
- segment = new MediaSegment();
1424
- if (segUrl.includes("ccode=") && segUrl.includes("/ad/") && segUrl.includes("duratio=")) {
1425
- segments.pop();
1426
- segIndex--;
1427
- hasAd = true;
1428
- }
1429
- if (segUrl.includes("ccode=0902") && segUrl.includes("duration=")) {
1430
- segments.pop();
1431
- segIndex--;
1432
- hasAd = false;
1433
- }
1434
- expectSegment = false;
1435
- }
1436
- }
1437
- if (!isEndList) mediaParts.push(new MediaPart(segments));
1438
- playlist.mediaParts = mediaParts;
1439
- playlist.isLive = !isEndList;
1440
- if (playlist.isLive) playlist.refreshIntervalMs = (playlist.targetDuration || 5) * 2 * 1e3;
1441
- return playlist;
1442
- }
1443
- async #parseKey(keyLine) {
1444
- 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);
1445
- throw new Error("No key processor found");
1446
- }
1447
- async extractStreams(rawText) {
1448
- this.#m3u8Content = rawText;
1449
- this.preProcessContent();
1450
- if (this.#m3u8Content.includes(HLS_TAGS.extXStreamInf)) return this.#parseMasterList().then((lists) => distinctBy(lists, (list) => list.url));
1451
- const playlist = await this.#parseList();
1452
- const streamSpec = new VideoStreamInfo();
1453
- streamSpec.url = this.parserConfig.url;
1454
- streamSpec.playlist = playlist;
1455
- streamSpec.extension = playlist.mediaInit ? "mp4" : "ts";
1456
- return [streamSpec];
1457
- }
1458
- async #loadM3u8FromUrl(url) {
1459
- if (url.startsWith("file:")) {
1460
- const filePath = new URL(url).pathname;
1461
- this.#m3u8Content = await readFile(filePath, "utf8");
1462
- } else if (url.startsWith("http")) try {
1463
- const response = await fetch(url, { headers: this.parserConfig.headers });
1464
- url = response.url;
1465
- this.#m3u8Content = await response.text();
1466
- } catch (e) {
1467
- if (this.parserConfig.originalUrl.startsWith("http") && url !== this.parserConfig.originalUrl) {
1468
- const response = await fetch(this.parserConfig.originalUrl, { headers: this.parserConfig.headers });
1469
- url = response.url;
1470
- this.#m3u8Content = await response.text();
1471
- }
1472
- }
1473
- this.#m3u8Url = url;
1474
- this.#setBaseUrl();
1475
- this.preProcessContent();
1476
- }
1477
- async #refreshUrlFromMaster(lists) {
1478
- await this.#loadM3u8FromUrl(this.parserConfig.url);
1479
- const newStreams = await this.#parseMasterList().then((lists$1) => distinctBy(lists$1, (list) => list.url));
1480
- for (const list of lists) {
1481
- const match = newStreams.filter((stream) => stream.toShortString() === list.toShortString());
1482
- if (!match.length) continue;
1483
- list.url = match.at(0).url;
1484
- }
1485
- }
1486
- async fetchPlayList(lists) {
1487
- for (const list of lists) {
1488
- try {
1489
- await this.#loadM3u8FromUrl(list.url);
1490
- } catch (e) {
1491
- if (this.#masterM3u8Flag) console.warn("Can not load m3u8. Try refreshing url from master url...");
1492
- await this.#refreshUrlFromMaster(lists);
1493
- await this.#loadM3u8FromUrl(list.url);
1494
- }
1495
- const newPlaylist = await this.#parseList();
1496
- if (list.playlist?.mediaInit) list.playlist.mediaParts = newPlaylist.mediaParts;
1497
- else list.playlist = newPlaylist;
1498
- if (list.type === "subtitle") {
1499
- const a = list.playlist.mediaParts.some((part) => part.mediaSegments.some((segment) => segment.url.includes(".ttml")));
1500
- const b$1 = list.playlist.mediaParts.some((part) => part.mediaSegments.some((segment) => segment.url.includes(".vtt") || segment.url.includes(".webvtt")));
1501
- if (a) list.extension = "ttml";
1502
- if (b$1) list.extension = "vtt";
1503
- } else list.extension = list.playlist.mediaInit ? "m4s" : "ts";
1504
- }
1505
- }
1506
- async refreshPlayList(streamInfos) {
1507
- await this.fetchPlayList(streamInfos);
1508
- }
1509
- };
1510
-
1511
- //#endregion
1512
- //#region lib/stream-extractor.ts
1513
- var StreamExtractor = class {
1514
- #extractor;
1515
- #rawText;
1516
- #parserConfig;
1517
- #rawFiles = {};
1518
- constructor(parserConfig) {
1519
- this.#parserConfig = parserConfig ?? new ParserConfig();
1520
- }
1521
- get extractorType() {
1522
- return this.#extractor.extractorType;
1523
- }
1524
- #setUrl(url) {
1525
- this.#parserConfig.originalUrl = url;
1526
- this.#parserConfig.url = url;
1527
- }
1528
- async loadSourceFromUrl(url) {
1529
- if (url.startsWith("file:")) {
1530
- const filePath = new URL(url).pathname;
1531
- this.#rawText = await readFile(filePath, "utf8");
1532
- this.#setUrl(url);
1533
- } else if (url.startsWith("http")) {
1534
- this.#parserConfig.originalUrl = url;
1535
- const response = await fetch(url, { headers: this.#parserConfig.headers });
1536
- this.#rawText = await response.text();
1537
- this.#parserConfig.url = response.url;
1538
- } else if (existsSync(url)) {
1539
- const filePath = path.resolve(url);
1540
- this.#rawText = await readFile(filePath, "utf8");
1541
- const absoluteUri = pathToFileURL(filePath).toString();
1542
- this.#setUrl(absoluteUri);
1543
- }
1544
- this.#rawText = this.#rawText.trim();
1545
- this.loadSourceFromText(this.#rawText);
1546
- }
1547
- loadSourceFromText(rawText, url) {
1548
- if (url) this.#setUrl(url);
1549
- let rawType = "txt";
1550
- this.#rawText = rawText.trim();
1551
- if (this.#rawText.startsWith(HLS_TAGS.extM3u)) {
1552
- this.#extractor = new HlsExtractor(this.#parserConfig);
1553
- rawType = "m3u8";
1554
- } else if (this.#rawText.includes("</MPD>") && this.#rawText.includes("<MPD")) {
1555
- this.#extractor = new DashExtractor(this.#parserConfig);
1556
- rawType = "mpd";
1557
- } else if (this.#rawText.includes("</SmoothStreamingMedia>") && this.#rawText.includes("<SmoothStreamingMedia")) {
1558
- rawType = "ism";
1559
- throw new Error("Smooth Streaming is not supported yet");
1560
- } else if (rawText === "<RE_LIVE_TS>") throw new Error("Live TS is not supported yet");
1561
- else throw new Error("Unsupported stream type");
1562
- this.#rawFiles[`raw.${rawType}`] = rawText;
1563
- }
1564
- async extractStreams() {
1565
- return this.#extractor.extractStreams(this.#rawText);
1566
- }
1567
- async fetchPlayList(streamInfos) {
1568
- return this.#extractor.fetchPlayList(streamInfos);
1569
- }
1570
- async refreshPlayList(streamInfos) {
1571
- return this.#extractor.refreshPlayList(streamInfos);
1572
- }
1573
- };
1574
-
1575
- //#endregion
1576
- export { ALL_STREAM_TYPES, AUDIO_CODECS, AudioStreamInfo, DASH_TAGS, DashExtractor, DefaultDashContentProcessor, DefaultHlsContentProcessor, DefaultHlsKeyProcessor, DefaultUrlProcessor, ENCRYPT_METHODS, EXTRACTOR_TYPES, EncryptInfo, HLS_TAGS, HlsExtractor, MediaPart, MediaSegment, ParserConfig, Playlist, ROLE_TYPE, SUBTITLE_CODECS, StreamExtractor, StreamInfo, SubtitleStreamInfo, VIDEO_CODECS, VIDEO_DYNAMIC_RANGES, VideoStreamInfo, getRange, parseRange };