dasha 4.0.0-alpha.2 → 4.0.0-alpha.4
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 +2 -2
- package/dist/dasha.cjs +267 -240
- package/dist/dasha.d.cts +200 -90
- package/dist/dasha.d.mts +289 -0
- package/dist/{dasha.js → dasha.mjs} +240 -224
- package/package.json +17 -20
- package/dist/dasha.d.ts +0 -179
|
@@ -1,30 +1,39 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
1
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { b } from "barsic";
|
|
2
4
|
import { readFile } from "node:fs/promises";
|
|
3
5
|
import { pathToFileURL } from "node:url";
|
|
4
6
|
import path from "node:path";
|
|
5
7
|
import { Temporal } from "temporal-polyfill";
|
|
6
8
|
import { DOMParser } from "@xmldom/xmldom";
|
|
7
|
-
import crypto from "node:crypto";
|
|
8
9
|
|
|
9
|
-
//#region lib/shared/
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
//#region lib/shared/encrypt-method.ts
|
|
11
|
+
const ENCRYPT_METHODS = {
|
|
12
|
+
NONE: "none",
|
|
13
|
+
AES_128: "aes-128",
|
|
14
|
+
AES_128_ECB: "aes-128-ecb",
|
|
15
|
+
SAMPLE_AES: "sample-aes",
|
|
16
|
+
SAMPLE_AES_CTR: "sample-aes-ctr",
|
|
17
|
+
CENC: "cenc",
|
|
18
|
+
CHACHA20: "chacha20",
|
|
19
|
+
UNKNOWN: "unknown"
|
|
15
20
|
};
|
|
16
21
|
|
|
17
22
|
//#endregion
|
|
18
|
-
//#region lib/shared/encrypt-
|
|
19
|
-
|
|
20
|
-
NONE
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
//#region lib/shared/encrypt-info.ts
|
|
24
|
+
var EncryptInfo = class {
|
|
25
|
+
method = ENCRYPT_METHODS.NONE;
|
|
26
|
+
key;
|
|
27
|
+
iv;
|
|
28
|
+
drm;
|
|
29
|
+
constructor(method) {
|
|
30
|
+
this.method = this.parseMethod(method);
|
|
31
|
+
this.drm = {};
|
|
32
|
+
}
|
|
33
|
+
parseMethod(method) {
|
|
34
|
+
if (method) return ENCRYPT_METHODS[method.replace("-", "_")];
|
|
35
|
+
else return ENCRYPT_METHODS.UNKNOWN;
|
|
36
|
+
}
|
|
28
37
|
};
|
|
29
38
|
|
|
30
39
|
//#endregion
|
|
@@ -36,6 +45,132 @@ const EXTRACTOR_TYPES = {
|
|
|
36
45
|
MSS: "MSS"
|
|
37
46
|
};
|
|
38
47
|
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region lib/shared/media-part.ts
|
|
50
|
+
var MediaPart = class {
|
|
51
|
+
mediaSegments = [];
|
|
52
|
+
constructor(segments) {
|
|
53
|
+
this.mediaSegments = segments || [];
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region lib/shared/media-segment.ts
|
|
59
|
+
var MediaSegment = class MediaSegment {
|
|
60
|
+
index = NaN;
|
|
61
|
+
duration = NaN;
|
|
62
|
+
title;
|
|
63
|
+
dateTime;
|
|
64
|
+
startRange;
|
|
65
|
+
get stopRange() {
|
|
66
|
+
return this.startRange !== void 0 && this.expectLength !== void 0 ? this.startRange + this.expectLength - 1 : void 0;
|
|
67
|
+
}
|
|
68
|
+
expectLength;
|
|
69
|
+
encryptInfo = new EncryptInfo();
|
|
70
|
+
get isEncrypted() {
|
|
71
|
+
return this.encryptInfo.method !== ENCRYPT_METHODS.NONE;
|
|
72
|
+
}
|
|
73
|
+
url = "";
|
|
74
|
+
nameFromVar;
|
|
75
|
+
equals(segment) {
|
|
76
|
+
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;
|
|
77
|
+
else return false;
|
|
78
|
+
}
|
|
79
|
+
getHashCode() {
|
|
80
|
+
const payload = [
|
|
81
|
+
this.index,
|
|
82
|
+
this.duration,
|
|
83
|
+
this.title,
|
|
84
|
+
this.startRange,
|
|
85
|
+
this.stopRange,
|
|
86
|
+
this.expectLength,
|
|
87
|
+
this.url
|
|
88
|
+
].join("-");
|
|
89
|
+
return crypto.createHash("md5").update(payload).digest("hex");
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region lib/shared/media-type.ts
|
|
95
|
+
const MEDIA_TYPES = {
|
|
96
|
+
VIDEO: "video",
|
|
97
|
+
AUDIO: "audio",
|
|
98
|
+
SUBTITLES: "subtitle",
|
|
99
|
+
CLOSED_CAPTIONS: "closed-captions"
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region lib/shared/stream-spec.ts
|
|
104
|
+
var StreamSpec = class {
|
|
105
|
+
mediaType;
|
|
106
|
+
groupId = null;
|
|
107
|
+
language;
|
|
108
|
+
name;
|
|
109
|
+
default;
|
|
110
|
+
skippedDuration;
|
|
111
|
+
bandwidth;
|
|
112
|
+
codecs = null;
|
|
113
|
+
resolution;
|
|
114
|
+
frameRate;
|
|
115
|
+
channels = null;
|
|
116
|
+
extension = null;
|
|
117
|
+
role;
|
|
118
|
+
videoRange;
|
|
119
|
+
characteristics;
|
|
120
|
+
publishTime;
|
|
121
|
+
audioId;
|
|
122
|
+
videoId;
|
|
123
|
+
subtitleId;
|
|
124
|
+
periodId = null;
|
|
125
|
+
url = "";
|
|
126
|
+
originalUrl = "";
|
|
127
|
+
playlist;
|
|
128
|
+
get segmentsCount() {
|
|
129
|
+
return this.playlist?.mediaParts.reduce((sum, part) => sum + part.mediaSegments.length, 0) ?? 0;
|
|
130
|
+
}
|
|
131
|
+
toShortString() {
|
|
132
|
+
let prefixStr = "";
|
|
133
|
+
let returnStr = "";
|
|
134
|
+
const encStr = "";
|
|
135
|
+
const bandwidth = this.bandwidth ? `${this.bandwidth / 1e3} Kbps` : "";
|
|
136
|
+
const channels = this.channels ? `${this.channels}CH` : "";
|
|
137
|
+
if (this.mediaType === MEDIA_TYPES.AUDIO) {
|
|
138
|
+
prefixStr = `Aud ${encStr}`;
|
|
139
|
+
returnStr = [
|
|
140
|
+
this.groupId,
|
|
141
|
+
bandwidth,
|
|
142
|
+
this.name,
|
|
143
|
+
this.codecs,
|
|
144
|
+
this.language,
|
|
145
|
+
channels,
|
|
146
|
+
this.role
|
|
147
|
+
].filter(Boolean).join(" | ");
|
|
148
|
+
} else if (this.mediaType === MEDIA_TYPES.SUBTITLES) {
|
|
149
|
+
prefixStr = `Sub ${encStr}`;
|
|
150
|
+
returnStr = [
|
|
151
|
+
this.groupId,
|
|
152
|
+
this.language,
|
|
153
|
+
this.name,
|
|
154
|
+
this.codecs,
|
|
155
|
+
this.role
|
|
156
|
+
].filter(Boolean).join(" | ");
|
|
157
|
+
} else {
|
|
158
|
+
prefixStr = `Vid ${encStr}`;
|
|
159
|
+
returnStr = [
|
|
160
|
+
this.resolution,
|
|
161
|
+
bandwidth,
|
|
162
|
+
this.groupId,
|
|
163
|
+
this.frameRate,
|
|
164
|
+
this.codecs,
|
|
165
|
+
this.videoRange,
|
|
166
|
+
this.role
|
|
167
|
+
].filter(Boolean).join(" | ");
|
|
168
|
+
}
|
|
169
|
+
returnStr = `${prefixStr} | ${returnStr}`;
|
|
170
|
+
return returnStr.trim();
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
39
174
|
//#endregion
|
|
40
175
|
//#region lib/shared/role-type.ts
|
|
41
176
|
const ROLE_TYPE = {
|
|
@@ -53,17 +188,13 @@ const ROLE_TYPE = {
|
|
|
53
188
|
|
|
54
189
|
//#endregion
|
|
55
190
|
//#region lib/processor.ts
|
|
56
|
-
var
|
|
57
|
-
var KeyProcessor = class {};
|
|
58
|
-
var UrlProcessor = class {};
|
|
59
|
-
var DefaultUrlProcessor = class extends UrlProcessor {
|
|
191
|
+
var DefaultUrlProcessor = class {
|
|
60
192
|
canProcess(_extractorType, _originalUrl, parserConfig) {
|
|
61
193
|
return parserConfig.appendUrlParams;
|
|
62
194
|
}
|
|
63
195
|
process(url, parserConfig) {
|
|
64
196
|
if (!url.startsWith("http")) return url;
|
|
65
|
-
const
|
|
66
|
-
const urlFromConfigQuery = urlFromConfig.searchParams;
|
|
197
|
+
const urlFromConfigQuery = new URL(parserConfig.url).searchParams;
|
|
67
198
|
const oldUrl = new URL(url);
|
|
68
199
|
const newQuery = oldUrl.searchParams;
|
|
69
200
|
for (const [key, value] of urlFromConfigQuery) if (newQuery.has(key)) newQuery.set(key, value);
|
|
@@ -78,7 +209,7 @@ var DefaultUrlProcessor = class extends UrlProcessor {
|
|
|
78
209
|
|
|
79
210
|
//#endregion
|
|
80
211
|
//#region lib/dash/dash-content-processor.ts
|
|
81
|
-
var DefaultDashContentProcessor = class
|
|
212
|
+
var DefaultDashContentProcessor = class {
|
|
82
213
|
canProcess(extractorType, mpdContent) {
|
|
83
214
|
if (extractorType !== EXTRACTOR_TYPES.MPEG_DASH) return false;
|
|
84
215
|
return mpdContent.includes("<mas:") && !mpdContent.includes("xmlns:mas");
|
|
@@ -123,7 +254,7 @@ const HLS_TAGS = {
|
|
|
123
254
|
|
|
124
255
|
//#endregion
|
|
125
256
|
//#region lib/hls/hls-content-processor.ts
|
|
126
|
-
var DefaultHlsContentProcessor = class DefaultHlsContentProcessor
|
|
257
|
+
var DefaultHlsContentProcessor = class DefaultHlsContentProcessor {
|
|
127
258
|
static YkDVRegex = /#EXT-X-DISCONTINUITY\s+#EXT-X-MAP:URI="(.*?)",BYTERANGE="(.*?)"/g;
|
|
128
259
|
static DNSPRegex = /#EXT-X-MAP:URI=".*?BUMPER\/[\s\S]+?#EXT-X-DISCONTINUITY/;
|
|
129
260
|
static DNSPSubRegex = /#EXTINF:.*?,\s+.*BUMPER.*\s+#EXT-X-DISCONTINUITY/;
|
|
@@ -156,24 +287,9 @@ var DefaultHlsContentProcessor = class DefaultHlsContentProcessor extends Conten
|
|
|
156
287
|
}
|
|
157
288
|
};
|
|
158
289
|
|
|
159
|
-
//#endregion
|
|
160
|
-
//#region lib/shared/encrypt-info.ts
|
|
161
|
-
var EncryptInfo = class {
|
|
162
|
-
method = ENCRYPT_METHODS.NONE;
|
|
163
|
-
key;
|
|
164
|
-
iv;
|
|
165
|
-
constructor(method) {
|
|
166
|
-
this.method = this.parseMethod(method);
|
|
167
|
-
}
|
|
168
|
-
parseMethod(method) {
|
|
169
|
-
if (method) return ENCRYPT_METHODS[method.replace("-", "_")];
|
|
170
|
-
else return ENCRYPT_METHODS.UNKNOWN;
|
|
171
|
-
}
|
|
172
|
-
};
|
|
173
|
-
|
|
174
290
|
//#endregion
|
|
175
291
|
//#region lib/hls/hls-key-processor.ts
|
|
176
|
-
var DefaultHlsKeyProcessor = class
|
|
292
|
+
var DefaultHlsKeyProcessor = class {
|
|
177
293
|
canProcess(extractorType) {
|
|
178
294
|
return extractorType === EXTRACTOR_TYPES.HLS;
|
|
179
295
|
}
|
|
@@ -181,17 +297,16 @@ var DefaultHlsKeyProcessor = class extends KeyProcessor {
|
|
|
181
297
|
const iv = this.getAttribute(keyLine, "IV");
|
|
182
298
|
const method = this.getAttribute(keyLine, "METHOD");
|
|
183
299
|
const uri = this.getAttribute(keyLine, "URI");
|
|
184
|
-
console.debug(`METHOD:${method}, URI:${uri}, IV:${iv}`);
|
|
185
300
|
const encryptInfo = new EncryptInfo(method);
|
|
186
|
-
if (iv) encryptInfo.iv =
|
|
301
|
+
if (iv) encryptInfo.iv = b.hex().encode(iv);
|
|
187
302
|
if (parserConfig.customIv && parserConfig.customIv.length > 0) encryptInfo.iv = parserConfig.customIv;
|
|
188
303
|
try {
|
|
189
304
|
if (parserConfig.customKey && parserConfig.customKey.length > 0) encryptInfo.key = parserConfig.customKey;
|
|
190
305
|
else if (uri) {
|
|
191
306
|
const lowerUri = uri.toLowerCase();
|
|
192
|
-
if (lowerUri.startsWith("base64:")) encryptInfo.key =
|
|
193
|
-
else if (lowerUri.startsWith("data:;base64,")) encryptInfo.key =
|
|
194
|
-
else if (lowerUri.startsWith("data:text/plain;base64,")) encryptInfo.key =
|
|
307
|
+
if (lowerUri.startsWith("base64:")) encryptInfo.key = b.base64().encode(uri.slice(7));
|
|
308
|
+
else if (lowerUri.startsWith("data:;base64,")) encryptInfo.key = b.base64().encode(uri.slice(13));
|
|
309
|
+
else if (lowerUri.startsWith("data:text/plain;base64,")) encryptInfo.key = b.base64().encode(uri.slice(23));
|
|
195
310
|
else if (existsSync(uri)) encryptInfo.key = readFileSync(uri);
|
|
196
311
|
else {
|
|
197
312
|
const processedUrl = this.preProcessUrl(new URL(uri, m3u8Url).toString(), parserConfig);
|
|
@@ -209,15 +324,15 @@ var DefaultHlsKeyProcessor = class extends KeyProcessor {
|
|
|
209
324
|
return encryptInfo;
|
|
210
325
|
}
|
|
211
326
|
getAttribute(line, attrName) {
|
|
212
|
-
const regex = new RegExp(`${attrName}="([^"]+)"`, "i");
|
|
327
|
+
const regex = new RegExp(`${attrName}=(?:"([^"]+)"|([^,]+))`, "i");
|
|
213
328
|
const match = line.match(regex);
|
|
214
|
-
return match?.[1] ?? null;
|
|
329
|
+
return match?.[1] ?? match?.[2] ?? null;
|
|
215
330
|
}
|
|
216
331
|
async fetchKeyWithRetry(url, parserConfig) {
|
|
217
332
|
let retryCount = parserConfig.keyRetryCount ?? 3;
|
|
218
333
|
while (retryCount >= 0) try {
|
|
219
334
|
const response = await fetch(url, { headers: parserConfig.headers });
|
|
220
|
-
return
|
|
335
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
221
336
|
} catch (error) {
|
|
222
337
|
if (error.message.includes("scheme is not supported")) throw error;
|
|
223
338
|
console.warn(`Error fetching key: ${error.message}. Retries left: ${retryCount}`);
|
|
@@ -252,78 +367,6 @@ var ParserConfig = class {
|
|
|
252
367
|
keyRetryCount = 3;
|
|
253
368
|
};
|
|
254
369
|
|
|
255
|
-
//#endregion
|
|
256
|
-
//#region lib/shared/stream-spec.ts
|
|
257
|
-
var StreamSpec = class {
|
|
258
|
-
mediaType;
|
|
259
|
-
groupId = null;
|
|
260
|
-
language;
|
|
261
|
-
name;
|
|
262
|
-
default;
|
|
263
|
-
skippedDuration;
|
|
264
|
-
bandwidth;
|
|
265
|
-
codecs = null;
|
|
266
|
-
resolution;
|
|
267
|
-
frameRate;
|
|
268
|
-
channels = null;
|
|
269
|
-
extension = null;
|
|
270
|
-
role;
|
|
271
|
-
videoRange;
|
|
272
|
-
characteristics;
|
|
273
|
-
publishTime;
|
|
274
|
-
audioId;
|
|
275
|
-
videoId;
|
|
276
|
-
subtitleId;
|
|
277
|
-
periodId = null;
|
|
278
|
-
url = "";
|
|
279
|
-
originalUrl = "";
|
|
280
|
-
playlist;
|
|
281
|
-
get segmentsCount() {
|
|
282
|
-
return this.playlist?.mediaParts.reduce((sum, part) => sum + part.mediaSegments.length, 0) ?? 0;
|
|
283
|
-
}
|
|
284
|
-
toShortString() {
|
|
285
|
-
let prefixStr = "";
|
|
286
|
-
let returnStr = "";
|
|
287
|
-
const encStr = "";
|
|
288
|
-
const bandwidth = this.bandwidth ? `${this.bandwidth / 1e3} Kbps` : "";
|
|
289
|
-
const channels = this.channels ? `${this.channels}CH` : "";
|
|
290
|
-
if (this.mediaType === MEDIA_TYPES.AUDIO) {
|
|
291
|
-
prefixStr = `Aud ${encStr}`;
|
|
292
|
-
returnStr = [
|
|
293
|
-
this.groupId,
|
|
294
|
-
bandwidth,
|
|
295
|
-
this.name,
|
|
296
|
-
this.codecs,
|
|
297
|
-
this.language,
|
|
298
|
-
channels,
|
|
299
|
-
this.role
|
|
300
|
-
].filter(Boolean).join(" | ");
|
|
301
|
-
} else if (this.mediaType === MEDIA_TYPES.SUBTITLES) {
|
|
302
|
-
prefixStr = `Sub ${encStr}`;
|
|
303
|
-
returnStr = [
|
|
304
|
-
this.groupId,
|
|
305
|
-
this.language,
|
|
306
|
-
this.name,
|
|
307
|
-
this.codecs,
|
|
308
|
-
this.role
|
|
309
|
-
].filter(Boolean).join(" | ");
|
|
310
|
-
} else {
|
|
311
|
-
prefixStr = `Vid ${encStr}`;
|
|
312
|
-
returnStr = [
|
|
313
|
-
this.resolution,
|
|
314
|
-
bandwidth,
|
|
315
|
-
this.groupId,
|
|
316
|
-
this.frameRate,
|
|
317
|
-
this.codecs,
|
|
318
|
-
this.videoRange,
|
|
319
|
-
this.role
|
|
320
|
-
].filter(Boolean).join(" | ");
|
|
321
|
-
}
|
|
322
|
-
returnStr = `${prefixStr} | ${returnStr}`;
|
|
323
|
-
return returnStr.trim();
|
|
324
|
-
}
|
|
325
|
-
};
|
|
326
|
-
|
|
327
370
|
//#endregion
|
|
328
371
|
//#region lib/dash/dash-tags.ts
|
|
329
372
|
const DASH_TAGS = {
|
|
@@ -338,8 +381,7 @@ const DASH_TAGS = {
|
|
|
338
381
|
const combineUrl = (baseUrl, relativeUrl) => {
|
|
339
382
|
if (!baseUrl.trim()) return relativeUrl;
|
|
340
383
|
const url1 = new URL(baseUrl);
|
|
341
|
-
|
|
342
|
-
return url2.toString();
|
|
384
|
+
return new URL(relativeUrl, url1).toString();
|
|
343
385
|
};
|
|
344
386
|
const replaceVars = (text, dict) => {
|
|
345
387
|
let result = text;
|
|
@@ -378,7 +420,7 @@ const getAttribute = (line, key = "") => {
|
|
|
378
420
|
return result;
|
|
379
421
|
};
|
|
380
422
|
const distinctBy = (array, callbackfn) => {
|
|
381
|
-
const seen = new Set();
|
|
423
|
+
const seen = /* @__PURE__ */ new Set();
|
|
382
424
|
return array.filter((item) => {
|
|
383
425
|
const value = callbackfn(item);
|
|
384
426
|
if (seen.has(value)) return false;
|
|
@@ -403,51 +445,6 @@ var Playlist = class {
|
|
|
403
445
|
mediaParts = [];
|
|
404
446
|
};
|
|
405
447
|
|
|
406
|
-
//#endregion
|
|
407
|
-
//#region lib/shared/media-part.ts
|
|
408
|
-
var MediaPart = class {
|
|
409
|
-
mediaSegments = [];
|
|
410
|
-
constructor(segments) {
|
|
411
|
-
this.mediaSegments = segments || [];
|
|
412
|
-
}
|
|
413
|
-
};
|
|
414
|
-
|
|
415
|
-
//#endregion
|
|
416
|
-
//#region lib/shared/media-segment.ts
|
|
417
|
-
var MediaSegment = class MediaSegment {
|
|
418
|
-
index = NaN;
|
|
419
|
-
duration = NaN;
|
|
420
|
-
title;
|
|
421
|
-
dateTime;
|
|
422
|
-
startRange;
|
|
423
|
-
get stopRange() {
|
|
424
|
-
return this.startRange !== void 0 && this.expectLength !== void 0 ? this.startRange + this.expectLength - 1 : void 0;
|
|
425
|
-
}
|
|
426
|
-
expectLength;
|
|
427
|
-
encryptInfo = new EncryptInfo();
|
|
428
|
-
get isEncrypted() {
|
|
429
|
-
return this.encryptInfo.method !== ENCRYPT_METHODS.NONE;
|
|
430
|
-
}
|
|
431
|
-
url = "";
|
|
432
|
-
nameFromVar;
|
|
433
|
-
equals(segment) {
|
|
434
|
-
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;
|
|
435
|
-
else return false;
|
|
436
|
-
}
|
|
437
|
-
getHashCode() {
|
|
438
|
-
const payload = [
|
|
439
|
-
this.index,
|
|
440
|
-
this.duration,
|
|
441
|
-
this.title,
|
|
442
|
-
this.startRange,
|
|
443
|
-
this.stopRange,
|
|
444
|
-
this.expectLength,
|
|
445
|
-
this.url
|
|
446
|
-
].join("-");
|
|
447
|
-
return crypto.createHash("md5").update(payload).digest("hex");
|
|
448
|
-
}
|
|
449
|
-
};
|
|
450
|
-
|
|
451
448
|
//#endregion
|
|
452
449
|
//#region lib/dash/dash-utils.ts
|
|
453
450
|
/**
|
|
@@ -457,8 +454,7 @@ var MediaSegment = class MediaSegment {
|
|
|
457
454
|
*/
|
|
458
455
|
const parseRange = (range) => {
|
|
459
456
|
const [startRange, end] = range.split("-").map(Number);
|
|
460
|
-
|
|
461
|
-
return [startRange, expectLength];
|
|
457
|
+
return [startRange, end - startRange + 1];
|
|
462
458
|
};
|
|
463
459
|
|
|
464
460
|
//#endregion
|
|
@@ -494,11 +490,9 @@ var DashExtractor = class DashExtractor {
|
|
|
494
490
|
async extractStreams(rawText) {
|
|
495
491
|
const streamList = [];
|
|
496
492
|
this.#mpdContent = rawText;
|
|
497
|
-
const
|
|
498
|
-
const
|
|
499
|
-
|
|
500
|
-
const isLive = type === "dynamic";
|
|
501
|
-
const maxSegmentDuration = mpdElement.getAttribute("maxSegmentDuration");
|
|
493
|
+
const mpdElement = new DOMParser().parseFromString(this.#mpdContent, "text/xml").getElementsByTagName("MPD")[0];
|
|
494
|
+
const isLive = mpdElement.getAttribute("type") === "dynamic";
|
|
495
|
+
mpdElement.getAttribute("maxSegmentDuration");
|
|
502
496
|
const availabilityStartTime = mpdElement.getAttribute("availabilityStartTime");
|
|
503
497
|
const timeShiftBufferDepth = mpdElement.getAttribute("timeShiftBufferDepth") || "PT1M";
|
|
504
498
|
const publishTime = mpdElement.getAttribute("publishTime");
|
|
@@ -557,8 +551,7 @@ var DashExtractor = class DashExtractor {
|
|
|
557
551
|
if (role) {
|
|
558
552
|
const roleValue = role.getAttribute("value");
|
|
559
553
|
const capitalize = (word) => word.charAt(0).toUpperCase() + word.slice(1);
|
|
560
|
-
const
|
|
561
|
-
const roleType = ROLE_TYPE[roleTypeKey];
|
|
554
|
+
const roleType = ROLE_TYPE[roleValue.split("-").map(capitalize).join("")];
|
|
562
555
|
streamSpec.role = roleType;
|
|
563
556
|
if (roleType === ROLE_TYPE.Subtitle) {
|
|
564
557
|
streamSpec.mediaType = MEDIA_TYPES.SUBTITLES;
|
|
@@ -689,8 +682,7 @@ var DashExtractor = class DashExtractor {
|
|
|
689
682
|
varDic[DASH_TAGS.TemplateNumber] = segNumber++;
|
|
690
683
|
const _hashTime = mediaTemplate?.includes(DASH_TAGS.TemplateTime);
|
|
691
684
|
const _media = replaceVars(mediaTemplate, varDic);
|
|
692
|
-
|
|
693
|
-
_mediaSegment.url = _mediaUrl;
|
|
685
|
+
_mediaSegment.url = combineUrl(segBaseUrl, _media);
|
|
694
686
|
_mediaSegment.index = segIndex++;
|
|
695
687
|
_mediaSegment.duration = _duration / timescale;
|
|
696
688
|
if (_hashTime) _mediaSegment.nameFromVar = currentTime.toString();
|
|
@@ -734,27 +726,37 @@ var DashExtractor = class DashExtractor {
|
|
|
734
726
|
mediaSegment.duration = periodDurationSeconds;
|
|
735
727
|
streamSpec.playlist.mediaParts[0].mediaSegments.push(mediaSegment);
|
|
736
728
|
}
|
|
737
|
-
const
|
|
738
|
-
|
|
729
|
+
const adaptationSetProtections = adaptationSet.getElementsByTagName("ContentProtection");
|
|
730
|
+
const representationProtections = representation.getElementsByTagName("ContentProtection");
|
|
731
|
+
const contentProtections = representationProtections[0] ? representationProtections : adaptationSetProtections;
|
|
732
|
+
if (contentProtections.length) {
|
|
739
733
|
const encryptInfo = new EncryptInfo();
|
|
740
734
|
encryptInfo.method = DashExtractor.#DEFAULT_METHOD;
|
|
735
|
+
const widevineSystemId = "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed";
|
|
736
|
+
const playreadySystemId = "9a04f079-9840-4286-ab92-e65be0885f95";
|
|
737
|
+
for (const contentProtection of contentProtections) {
|
|
738
|
+
const schemeIdUri = contentProtection.getAttribute("schemeIdUri");
|
|
739
|
+
const drmData = {
|
|
740
|
+
keyId: contentProtection.getAttribute("cenc:default_KID") || void 0,
|
|
741
|
+
pssh: contentProtection.getElementsByTagName("cenc:pssh")[0]?.textContent || void 0
|
|
742
|
+
};
|
|
743
|
+
if (schemeIdUri?.includes(widevineSystemId)) encryptInfo.drm.widevine = drmData;
|
|
744
|
+
else if (schemeIdUri?.includes(playreadySystemId)) encryptInfo.drm.playready = drmData;
|
|
745
|
+
else continue;
|
|
746
|
+
}
|
|
741
747
|
if (streamSpec.playlist.mediaInit) streamSpec.playlist.mediaInit.encryptInfo = encryptInfo;
|
|
742
748
|
const segments = streamSpec.playlist.mediaParts[0].mediaSegments;
|
|
743
749
|
for (const segment of segments) if (!segment.encryptInfo) segment.encryptInfo = encryptInfo;
|
|
744
750
|
}
|
|
745
751
|
const _index = streamList.findIndex((item) => item.periodId !== streamSpec.periodId && item.groupId === streamSpec.groupId && item.resolution === streamSpec.resolution && item.mediaType === streamSpec.mediaType);
|
|
746
|
-
if (_index > -1) if (isLive) {} else {
|
|
747
|
-
const
|
|
748
|
-
const
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
mediaPart.mediaSegments = streamList[_index].playlist.mediaParts[0].mediaSegments;
|
|
755
|
-
streamList[_index].playlist.mediaParts.push(mediaPart);
|
|
756
|
-
} else streamList[_index].playlist.mediaParts.at(-1).mediaSegments.at(-1).duration += streamSpec.playlist.mediaParts[0].mediaSegments.reduce((sum, segment) => sum + segment.duration, 0);
|
|
757
|
-
}
|
|
752
|
+
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) {
|
|
753
|
+
const startIndex = streamList[_index].playlist.mediaParts.at(-1).mediaSegments.at(-1).index + 1;
|
|
754
|
+
const segments = streamSpec.playlist.mediaParts[0].mediaSegments;
|
|
755
|
+
for (const segment of segments) segment.index += startIndex;
|
|
756
|
+
const mediaPart = new MediaPart();
|
|
757
|
+
mediaPart.mediaSegments = streamList[_index].playlist.mediaParts[0].mediaSegments;
|
|
758
|
+
streamList[_index].playlist.mediaParts.push(mediaPart);
|
|
759
|
+
} else streamList[_index].playlist.mediaParts.at(-1).mediaSegments.at(-1).duration += streamSpec.playlist.mediaParts[0].mediaSegments.reduce((sum, segment) => sum + segment.duration, 0);
|
|
758
760
|
else {
|
|
759
761
|
if (streamSpec.mediaType === MEDIA_TYPES.SUBTITLES && streamSpec.extension === "mp4") streamSpec.extension = "m4s";
|
|
760
762
|
if (streamSpec.mediaType !== MEDIA_TYPES.SUBTITLES && (streamSpec.extension == null || streamSpec.playlist.mediaParts.reduce((sum, part) => sum + part.mediaSegments.length, 0) > 1)) streamSpec.extension = "m4s";
|
|
@@ -769,8 +771,8 @@ var DashExtractor = class DashExtractor {
|
|
|
769
771
|
const subtitleList = streamList.filter((stream) => stream.mediaType === MEDIA_TYPES.SUBTITLES);
|
|
770
772
|
const videoList = streamList.filter((stream) => stream.mediaType === MEDIA_TYPES.VIDEO);
|
|
771
773
|
for (const video of videoList) {
|
|
772
|
-
const audioGroupId = audioList.toSorted((a, b) => (b.bandwidth || 0) - (a.bandwidth || 0)).at(0)?.groupId;
|
|
773
|
-
const subtitleGroupId = subtitleList.toSorted((a, b) => (b.bandwidth || 0) - (a.bandwidth || 0)).at(0)?.groupId;
|
|
774
|
+
const audioGroupId = audioList.toSorted((a, b$1) => (b$1.bandwidth || 0) - (a.bandwidth || 0)).at(0)?.groupId;
|
|
775
|
+
const subtitleGroupId = subtitleList.toSorted((a, b$1) => (b$1.bandwidth || 0) - (a.bandwidth || 0)).at(0)?.groupId;
|
|
774
776
|
if (audioGroupId) video.audioId = audioGroupId;
|
|
775
777
|
if (subtitleGroupId) video.subtitleId = subtitleGroupId;
|
|
776
778
|
}
|
|
@@ -780,17 +782,38 @@ var DashExtractor = class DashExtractor {
|
|
|
780
782
|
if (!v) return;
|
|
781
783
|
return v;
|
|
782
784
|
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
+
async refreshPlayList(streamSpecs) {
|
|
786
|
+
if (!streamSpecs.length) return;
|
|
787
|
+
const response = await fetch(this.#parserConfig.url, this.#parserConfig.headers).catch(() => fetch(this.#parserConfig.originalUrl, this.#parserConfig.headers));
|
|
788
|
+
const rawText = await response.text();
|
|
789
|
+
const url = response.url;
|
|
790
|
+
this.#parserConfig.url = url;
|
|
791
|
+
this.#setInitUrl();
|
|
792
|
+
const newStreams = await this.extractStreams(rawText);
|
|
793
|
+
for (const streamSpec of streamSpecs) {
|
|
794
|
+
let results = newStreams.filter((n) => n.toShortString() === streamSpec.toShortString());
|
|
795
|
+
if (!results.length) results = newStreams.filter((n) => n.playlist?.mediaInit?.url === streamSpec.playlist?.mediaInit?.url);
|
|
796
|
+
if (results.length) streamSpec.playlist.mediaParts = results.at(0).playlist.mediaParts;
|
|
797
|
+
}
|
|
798
|
+
await this.#processUrl(streamSpecs);
|
|
785
799
|
}
|
|
786
|
-
|
|
787
|
-
|
|
800
|
+
async #processUrl(streamSpecs) {
|
|
801
|
+
for (const spec of streamSpecs) {
|
|
802
|
+
const playlist = spec.playlist;
|
|
803
|
+
if (!playlist) continue;
|
|
804
|
+
if (playlist.mediaInit) playlist.mediaInit.url = this.preProcessUrl(playlist.mediaInit.url);
|
|
805
|
+
for (const part of playlist.mediaParts) for (const segment of part.mediaSegments) segment.url = this.preProcessUrl(segment.url);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
async fetchPlayList(streamSpecs) {
|
|
809
|
+
this.#processUrl(streamSpecs);
|
|
788
810
|
}
|
|
789
811
|
preProcessUrl(url) {
|
|
790
|
-
|
|
812
|
+
for (const processor of this.#parserConfig.urlProcessors) if (processor.canProcess(this.extractorType, url, this.#parserConfig)) url = processor.process(url, this.#parserConfig);
|
|
813
|
+
return url;
|
|
791
814
|
}
|
|
792
815
|
preProcessContent() {
|
|
793
|
-
|
|
816
|
+
for (const processor of this.#parserConfig.contentProcessors) if (processor.canProcess(this.extractorType, this.#mpdContent, this.#parserConfig)) this.#mpdContent = processor.process(this.#mpdContent, this.#parserConfig);
|
|
794
817
|
}
|
|
795
818
|
};
|
|
796
819
|
|
|
@@ -869,8 +892,7 @@ var HlsExtractor = class {
|
|
|
869
892
|
expectPlaylist = true;
|
|
870
893
|
} else if (line.startsWith(HLS_TAGS.extXMedia)) {
|
|
871
894
|
streamSpec = new StreamSpec();
|
|
872
|
-
const
|
|
873
|
-
const mediaType = MEDIA_TYPES[type];
|
|
895
|
+
const mediaType = MEDIA_TYPES[getAttribute(line, "TYPE").replace("-", "_")];
|
|
874
896
|
if (mediaType) streamSpec.mediaType = mediaType;
|
|
875
897
|
if (mediaType === MEDIA_TYPES.CLOSED_CAPTIONS) continue;
|
|
876
898
|
let url = getAttribute(line, "URI");
|
|
@@ -921,8 +943,7 @@ var HlsExtractor = class {
|
|
|
921
943
|
for (const line of lines) {
|
|
922
944
|
if (!line.trim()) continue;
|
|
923
945
|
if (line.startsWith(HLS_TAGS.extXByterange)) {
|
|
924
|
-
const
|
|
925
|
-
const [n, o] = getRange(p);
|
|
946
|
+
const [n, o] = getRange(getAttribute(line));
|
|
926
947
|
segment.expectLength = n;
|
|
927
948
|
segment.startRange = o || (segments.at(-1)?.startRange || 0) + (segments.at(-1)?.expectLength || 0);
|
|
928
949
|
expectSegment = true;
|
|
@@ -944,9 +965,7 @@ var HlsExtractor = class {
|
|
|
944
965
|
mediaParts.push(new MediaPart(segments));
|
|
945
966
|
segments = [];
|
|
946
967
|
} else if (line.startsWith(HLS_TAGS.extXKey)) {
|
|
947
|
-
|
|
948
|
-
const uriLast = getAttribute(lastKeyLine, "URI");
|
|
949
|
-
if (uri !== uriLast) {
|
|
968
|
+
if (getAttribute(line, "URI") !== getAttribute(lastKeyLine, "URI")) {
|
|
950
969
|
const parsedInfo = await this.#parseKey(line);
|
|
951
970
|
currentEncryptInfo.method = parsedInfo.method;
|
|
952
971
|
currentEncryptInfo.key = parsedInfo.key;
|
|
@@ -974,8 +993,7 @@ var HlsExtractor = class {
|
|
|
974
993
|
mediaSegment.index = -1;
|
|
975
994
|
playlist.mediaInit = mediaSegment;
|
|
976
995
|
if (line.includes("BYTERANGE")) {
|
|
977
|
-
const
|
|
978
|
-
const [n, o] = getRange(p);
|
|
996
|
+
const [n, o] = getRange(getAttribute(line, "BYTERANGE"));
|
|
979
997
|
mediaSegment.expectLength = n;
|
|
980
998
|
mediaSegment.startRange = o || 0;
|
|
981
999
|
}
|
|
@@ -1024,10 +1042,7 @@ var HlsExtractor = class {
|
|
|
1024
1042
|
async extractStreams(rawText) {
|
|
1025
1043
|
this.#m3u8Content = rawText;
|
|
1026
1044
|
this.preProcessContent();
|
|
1027
|
-
if (this.#m3u8Content.includes(HLS_TAGS.extXStreamInf))
|
|
1028
|
-
console.log("Master m3u8 found");
|
|
1029
|
-
return this.#parseMasterList().then((lists) => distinctBy(lists, (list) => list.url));
|
|
1030
|
-
}
|
|
1045
|
+
if (this.#m3u8Content.includes(HLS_TAGS.extXStreamInf)) return this.#parseMasterList().then((lists) => distinctBy(lists, (list) => list.url));
|
|
1031
1046
|
const playlist = await this.#parseList();
|
|
1032
1047
|
const streamSpec = new StreamSpec();
|
|
1033
1048
|
streamSpec.url = this.parserConfig.url;
|
|
@@ -1037,8 +1052,7 @@ var HlsExtractor = class {
|
|
|
1037
1052
|
}
|
|
1038
1053
|
async #loadM3u8FromUrl(url) {
|
|
1039
1054
|
if (url.startsWith("file:")) {
|
|
1040
|
-
const
|
|
1041
|
-
const filePath = uri.pathname;
|
|
1055
|
+
const filePath = new URL(url).pathname;
|
|
1042
1056
|
this.#m3u8Content = await readFile(filePath, "utf8");
|
|
1043
1057
|
} else if (url.startsWith("http")) try {
|
|
1044
1058
|
const response = await fetch(url, { headers: this.parserConfig.headers });
|
|
@@ -1078,9 +1092,9 @@ var HlsExtractor = class {
|
|
|
1078
1092
|
else list.playlist = newPlaylist;
|
|
1079
1093
|
if (list.mediaType === MEDIA_TYPES.SUBTITLES) {
|
|
1080
1094
|
const a = list.playlist.mediaParts.some((part) => part.mediaSegments.some((segment) => segment.url.includes(".ttml")));
|
|
1081
|
-
const b = list.playlist.mediaParts.some((part) => part.mediaSegments.some((segment) => segment.url.includes(".vtt") || segment.url.includes(".webvtt")));
|
|
1095
|
+
const b$1 = list.playlist.mediaParts.some((part) => part.mediaSegments.some((segment) => segment.url.includes(".vtt") || segment.url.includes(".webvtt")));
|
|
1082
1096
|
if (a) list.extension = "ttml";
|
|
1083
|
-
if (b) list.extension = "vtt";
|
|
1097
|
+
if (b$1) list.extension = "vtt";
|
|
1084
1098
|
} else list.extension = list.playlist.mediaInit ? "m4s" : "ts";
|
|
1085
1099
|
}
|
|
1086
1100
|
}
|
|
@@ -1108,8 +1122,7 @@ var StreamExtractor = class {
|
|
|
1108
1122
|
}
|
|
1109
1123
|
async loadSourceFromUrl(url) {
|
|
1110
1124
|
if (url.startsWith("file:")) {
|
|
1111
|
-
const
|
|
1112
|
-
const filePath = uri.pathname;
|
|
1125
|
+
const filePath = new URL(url).pathname;
|
|
1113
1126
|
this.#rawText = await readFile(filePath, "utf8");
|
|
1114
1127
|
this.#setUrl(url);
|
|
1115
1128
|
} else if (url.startsWith("http")) {
|
|
@@ -1136,8 +1149,11 @@ var StreamExtractor = class {
|
|
|
1136
1149
|
} else if (this.#rawText.includes("</MPD>") && this.#rawText.includes("<MPD")) {
|
|
1137
1150
|
this.#extractor = new DashExtractor(this.#parserConfig);
|
|
1138
1151
|
rawType = "mpd";
|
|
1139
|
-
} else if (this.#rawText.includes("</SmoothStreamingMedia>") && this.#rawText.includes("<SmoothStreamingMedia"))
|
|
1140
|
-
|
|
1152
|
+
} else if (this.#rawText.includes("</SmoothStreamingMedia>") && this.#rawText.includes("<SmoothStreamingMedia")) {
|
|
1153
|
+
rawType = "ism";
|
|
1154
|
+
throw new Error("Smooth Streaming is not supported yet");
|
|
1155
|
+
} else if (rawText === "<RE_LIVE_TS>") throw new Error("Live TS is not supported yet");
|
|
1156
|
+
else throw new Error("Unsupported stream type");
|
|
1141
1157
|
this.#rawFiles[`raw.${rawType}`] = rawText;
|
|
1142
1158
|
}
|
|
1143
1159
|
async extractStreams() {
|
|
@@ -1152,4 +1168,4 @@ var StreamExtractor = class {
|
|
|
1152
1168
|
};
|
|
1153
1169
|
|
|
1154
1170
|
//#endregion
|
|
1155
|
-
export {
|
|
1171
|
+
export { DASH_TAGS, DashExtractor, DefaultDashContentProcessor, DefaultHlsContentProcessor, DefaultHlsKeyProcessor, DefaultUrlProcessor, ENCRYPT_METHODS, EXTRACTOR_TYPES, EncryptInfo, HLS_TAGS, HlsExtractor, MEDIA_TYPES, MediaPart, MediaSegment, ParserConfig, ROLE_TYPE, StreamExtractor, StreamSpec, getRange, parseRange };
|