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
package/dist/dasha.cjs
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
//#region rolldown:runtime
|
|
3
2
|
var __create = Object.create;
|
|
4
3
|
var __defProp = Object.defineProperty;
|
|
@@ -22,33 +21,44 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
22
21
|
}) : target, mod));
|
|
23
22
|
|
|
24
23
|
//#endregion
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
let node_crypto = require("node:crypto");
|
|
25
|
+
node_crypto = __toESM(node_crypto);
|
|
26
|
+
let node_fs = require("node:fs");
|
|
27
|
+
let barsic = require("barsic");
|
|
28
|
+
let node_fs_promises = require("node:fs/promises");
|
|
29
|
+
let node_url = require("node:url");
|
|
30
|
+
let node_path = require("node:path");
|
|
31
|
+
node_path = __toESM(node_path);
|
|
32
|
+
let temporal_polyfill = require("temporal-polyfill");
|
|
33
|
+
let __xmldom_xmldom = require("@xmldom/xmldom");
|
|
32
34
|
|
|
33
|
-
//#region lib/shared/
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
//#region lib/shared/encrypt-method.ts
|
|
36
|
+
const ENCRYPT_METHODS = {
|
|
37
|
+
NONE: "none",
|
|
38
|
+
AES_128: "aes-128",
|
|
39
|
+
AES_128_ECB: "aes-128-ecb",
|
|
40
|
+
SAMPLE_AES: "sample-aes",
|
|
41
|
+
SAMPLE_AES_CTR: "sample-aes-ctr",
|
|
42
|
+
CENC: "cenc",
|
|
43
|
+
CHACHA20: "chacha20",
|
|
44
|
+
UNKNOWN: "unknown"
|
|
39
45
|
};
|
|
40
46
|
|
|
41
47
|
//#endregion
|
|
42
|
-
//#region lib/shared/encrypt-
|
|
43
|
-
|
|
44
|
-
NONE
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
//#region lib/shared/encrypt-info.ts
|
|
49
|
+
var EncryptInfo = class {
|
|
50
|
+
method = ENCRYPT_METHODS.NONE;
|
|
51
|
+
key;
|
|
52
|
+
iv;
|
|
53
|
+
drm;
|
|
54
|
+
constructor(method) {
|
|
55
|
+
this.method = this.parseMethod(method);
|
|
56
|
+
this.drm = {};
|
|
57
|
+
}
|
|
58
|
+
parseMethod(method) {
|
|
59
|
+
if (method) return ENCRYPT_METHODS[method.replace("-", "_")];
|
|
60
|
+
else return ENCRYPT_METHODS.UNKNOWN;
|
|
61
|
+
}
|
|
52
62
|
};
|
|
53
63
|
|
|
54
64
|
//#endregion
|
|
@@ -60,6 +70,132 @@ const EXTRACTOR_TYPES = {
|
|
|
60
70
|
MSS: "MSS"
|
|
61
71
|
};
|
|
62
72
|
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region lib/shared/media-part.ts
|
|
75
|
+
var MediaPart = class {
|
|
76
|
+
mediaSegments = [];
|
|
77
|
+
constructor(segments) {
|
|
78
|
+
this.mediaSegments = segments || [];
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
//#endregion
|
|
83
|
+
//#region lib/shared/media-segment.ts
|
|
84
|
+
var MediaSegment = class MediaSegment {
|
|
85
|
+
index = NaN;
|
|
86
|
+
duration = NaN;
|
|
87
|
+
title;
|
|
88
|
+
dateTime;
|
|
89
|
+
startRange;
|
|
90
|
+
get stopRange() {
|
|
91
|
+
return this.startRange !== void 0 && this.expectLength !== void 0 ? this.startRange + this.expectLength - 1 : void 0;
|
|
92
|
+
}
|
|
93
|
+
expectLength;
|
|
94
|
+
encryptInfo = new EncryptInfo();
|
|
95
|
+
get isEncrypted() {
|
|
96
|
+
return this.encryptInfo.method !== ENCRYPT_METHODS.NONE;
|
|
97
|
+
}
|
|
98
|
+
url = "";
|
|
99
|
+
nameFromVar;
|
|
100
|
+
equals(segment) {
|
|
101
|
+
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;
|
|
102
|
+
else return false;
|
|
103
|
+
}
|
|
104
|
+
getHashCode() {
|
|
105
|
+
const payload = [
|
|
106
|
+
this.index,
|
|
107
|
+
this.duration,
|
|
108
|
+
this.title,
|
|
109
|
+
this.startRange,
|
|
110
|
+
this.stopRange,
|
|
111
|
+
this.expectLength,
|
|
112
|
+
this.url
|
|
113
|
+
].join("-");
|
|
114
|
+
return node_crypto.default.createHash("md5").update(payload).digest("hex");
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
//#endregion
|
|
119
|
+
//#region lib/shared/media-type.ts
|
|
120
|
+
const MEDIA_TYPES = {
|
|
121
|
+
VIDEO: "video",
|
|
122
|
+
AUDIO: "audio",
|
|
123
|
+
SUBTITLES: "subtitle",
|
|
124
|
+
CLOSED_CAPTIONS: "closed-captions"
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region lib/shared/stream-spec.ts
|
|
129
|
+
var StreamSpec = class {
|
|
130
|
+
mediaType;
|
|
131
|
+
groupId = null;
|
|
132
|
+
language;
|
|
133
|
+
name;
|
|
134
|
+
default;
|
|
135
|
+
skippedDuration;
|
|
136
|
+
bandwidth;
|
|
137
|
+
codecs = null;
|
|
138
|
+
resolution;
|
|
139
|
+
frameRate;
|
|
140
|
+
channels = null;
|
|
141
|
+
extension = null;
|
|
142
|
+
role;
|
|
143
|
+
videoRange;
|
|
144
|
+
characteristics;
|
|
145
|
+
publishTime;
|
|
146
|
+
audioId;
|
|
147
|
+
videoId;
|
|
148
|
+
subtitleId;
|
|
149
|
+
periodId = null;
|
|
150
|
+
url = "";
|
|
151
|
+
originalUrl = "";
|
|
152
|
+
playlist;
|
|
153
|
+
get segmentsCount() {
|
|
154
|
+
return this.playlist?.mediaParts.reduce((sum, part) => sum + part.mediaSegments.length, 0) ?? 0;
|
|
155
|
+
}
|
|
156
|
+
toShortString() {
|
|
157
|
+
let prefixStr = "";
|
|
158
|
+
let returnStr = "";
|
|
159
|
+
const encStr = "";
|
|
160
|
+
const bandwidth = this.bandwidth ? `${this.bandwidth / 1e3} Kbps` : "";
|
|
161
|
+
const channels = this.channels ? `${this.channels}CH` : "";
|
|
162
|
+
if (this.mediaType === MEDIA_TYPES.AUDIO) {
|
|
163
|
+
prefixStr = `Aud ${encStr}`;
|
|
164
|
+
returnStr = [
|
|
165
|
+
this.groupId,
|
|
166
|
+
bandwidth,
|
|
167
|
+
this.name,
|
|
168
|
+
this.codecs,
|
|
169
|
+
this.language,
|
|
170
|
+
channels,
|
|
171
|
+
this.role
|
|
172
|
+
].filter(Boolean).join(" | ");
|
|
173
|
+
} else if (this.mediaType === MEDIA_TYPES.SUBTITLES) {
|
|
174
|
+
prefixStr = `Sub ${encStr}`;
|
|
175
|
+
returnStr = [
|
|
176
|
+
this.groupId,
|
|
177
|
+
this.language,
|
|
178
|
+
this.name,
|
|
179
|
+
this.codecs,
|
|
180
|
+
this.role
|
|
181
|
+
].filter(Boolean).join(" | ");
|
|
182
|
+
} else {
|
|
183
|
+
prefixStr = `Vid ${encStr}`;
|
|
184
|
+
returnStr = [
|
|
185
|
+
this.resolution,
|
|
186
|
+
bandwidth,
|
|
187
|
+
this.groupId,
|
|
188
|
+
this.frameRate,
|
|
189
|
+
this.codecs,
|
|
190
|
+
this.videoRange,
|
|
191
|
+
this.role
|
|
192
|
+
].filter(Boolean).join(" | ");
|
|
193
|
+
}
|
|
194
|
+
returnStr = `${prefixStr} | ${returnStr}`;
|
|
195
|
+
return returnStr.trim();
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
63
199
|
//#endregion
|
|
64
200
|
//#region lib/shared/role-type.ts
|
|
65
201
|
const ROLE_TYPE = {
|
|
@@ -77,17 +213,13 @@ const ROLE_TYPE = {
|
|
|
77
213
|
|
|
78
214
|
//#endregion
|
|
79
215
|
//#region lib/processor.ts
|
|
80
|
-
var
|
|
81
|
-
var KeyProcessor = class {};
|
|
82
|
-
var UrlProcessor = class {};
|
|
83
|
-
var DefaultUrlProcessor = class extends UrlProcessor {
|
|
216
|
+
var DefaultUrlProcessor = class {
|
|
84
217
|
canProcess(_extractorType, _originalUrl, parserConfig) {
|
|
85
218
|
return parserConfig.appendUrlParams;
|
|
86
219
|
}
|
|
87
220
|
process(url, parserConfig) {
|
|
88
221
|
if (!url.startsWith("http")) return url;
|
|
89
|
-
const
|
|
90
|
-
const urlFromConfigQuery = urlFromConfig.searchParams;
|
|
222
|
+
const urlFromConfigQuery = new URL(parserConfig.url).searchParams;
|
|
91
223
|
const oldUrl = new URL(url);
|
|
92
224
|
const newQuery = oldUrl.searchParams;
|
|
93
225
|
for (const [key, value] of urlFromConfigQuery) if (newQuery.has(key)) newQuery.set(key, value);
|
|
@@ -102,7 +234,7 @@ var DefaultUrlProcessor = class extends UrlProcessor {
|
|
|
102
234
|
|
|
103
235
|
//#endregion
|
|
104
236
|
//#region lib/dash/dash-content-processor.ts
|
|
105
|
-
var DefaultDashContentProcessor = class
|
|
237
|
+
var DefaultDashContentProcessor = class {
|
|
106
238
|
canProcess(extractorType, mpdContent) {
|
|
107
239
|
if (extractorType !== EXTRACTOR_TYPES.MPEG_DASH) return false;
|
|
108
240
|
return mpdContent.includes("<mas:") && !mpdContent.includes("xmlns:mas");
|
|
@@ -147,7 +279,7 @@ const HLS_TAGS = {
|
|
|
147
279
|
|
|
148
280
|
//#endregion
|
|
149
281
|
//#region lib/hls/hls-content-processor.ts
|
|
150
|
-
var DefaultHlsContentProcessor = class DefaultHlsContentProcessor
|
|
282
|
+
var DefaultHlsContentProcessor = class DefaultHlsContentProcessor {
|
|
151
283
|
static YkDVRegex = /#EXT-X-DISCONTINUITY\s+#EXT-X-MAP:URI="(.*?)",BYTERANGE="(.*?)"/g;
|
|
152
284
|
static DNSPRegex = /#EXT-X-MAP:URI=".*?BUMPER\/[\s\S]+?#EXT-X-DISCONTINUITY/;
|
|
153
285
|
static DNSPSubRegex = /#EXTINF:.*?,\s+.*BUMPER.*\s+#EXT-X-DISCONTINUITY/;
|
|
@@ -180,24 +312,9 @@ var DefaultHlsContentProcessor = class DefaultHlsContentProcessor extends Conten
|
|
|
180
312
|
}
|
|
181
313
|
};
|
|
182
314
|
|
|
183
|
-
//#endregion
|
|
184
|
-
//#region lib/shared/encrypt-info.ts
|
|
185
|
-
var EncryptInfo = class {
|
|
186
|
-
method = ENCRYPT_METHODS.NONE;
|
|
187
|
-
key;
|
|
188
|
-
iv;
|
|
189
|
-
constructor(method) {
|
|
190
|
-
this.method = this.parseMethod(method);
|
|
191
|
-
}
|
|
192
|
-
parseMethod(method) {
|
|
193
|
-
if (method) return ENCRYPT_METHODS[method.replace("-", "_")];
|
|
194
|
-
else return ENCRYPT_METHODS.UNKNOWN;
|
|
195
|
-
}
|
|
196
|
-
};
|
|
197
|
-
|
|
198
315
|
//#endregion
|
|
199
316
|
//#region lib/hls/hls-key-processor.ts
|
|
200
|
-
var DefaultHlsKeyProcessor = class
|
|
317
|
+
var DefaultHlsKeyProcessor = class {
|
|
201
318
|
canProcess(extractorType) {
|
|
202
319
|
return extractorType === EXTRACTOR_TYPES.HLS;
|
|
203
320
|
}
|
|
@@ -205,17 +322,16 @@ var DefaultHlsKeyProcessor = class extends KeyProcessor {
|
|
|
205
322
|
const iv = this.getAttribute(keyLine, "IV");
|
|
206
323
|
const method = this.getAttribute(keyLine, "METHOD");
|
|
207
324
|
const uri = this.getAttribute(keyLine, "URI");
|
|
208
|
-
console.debug(`METHOD:${method}, URI:${uri}, IV:${iv}`);
|
|
209
325
|
const encryptInfo = new EncryptInfo(method);
|
|
210
|
-
if (iv) encryptInfo.iv =
|
|
326
|
+
if (iv) encryptInfo.iv = barsic.b.hex().encode(iv);
|
|
211
327
|
if (parserConfig.customIv && parserConfig.customIv.length > 0) encryptInfo.iv = parserConfig.customIv;
|
|
212
328
|
try {
|
|
213
329
|
if (parserConfig.customKey && parserConfig.customKey.length > 0) encryptInfo.key = parserConfig.customKey;
|
|
214
330
|
else if (uri) {
|
|
215
331
|
const lowerUri = uri.toLowerCase();
|
|
216
|
-
if (lowerUri.startsWith("base64:")) encryptInfo.key =
|
|
217
|
-
else if (lowerUri.startsWith("data:;base64,")) encryptInfo.key =
|
|
218
|
-
else if (lowerUri.startsWith("data:text/plain;base64,")) encryptInfo.key =
|
|
332
|
+
if (lowerUri.startsWith("base64:")) encryptInfo.key = barsic.b.base64().encode(uri.slice(7));
|
|
333
|
+
else if (lowerUri.startsWith("data:;base64,")) encryptInfo.key = barsic.b.base64().encode(uri.slice(13));
|
|
334
|
+
else if (lowerUri.startsWith("data:text/plain;base64,")) encryptInfo.key = barsic.b.base64().encode(uri.slice(23));
|
|
219
335
|
else if ((0, node_fs.existsSync)(uri)) encryptInfo.key = (0, node_fs.readFileSync)(uri);
|
|
220
336
|
else {
|
|
221
337
|
const processedUrl = this.preProcessUrl(new URL(uri, m3u8Url).toString(), parserConfig);
|
|
@@ -233,15 +349,15 @@ var DefaultHlsKeyProcessor = class extends KeyProcessor {
|
|
|
233
349
|
return encryptInfo;
|
|
234
350
|
}
|
|
235
351
|
getAttribute(line, attrName) {
|
|
236
|
-
const regex = new RegExp(`${attrName}="([^"]+)"`, "i");
|
|
352
|
+
const regex = new RegExp(`${attrName}=(?:"([^"]+)"|([^,]+))`, "i");
|
|
237
353
|
const match = line.match(regex);
|
|
238
|
-
return match?.[1] ?? null;
|
|
354
|
+
return match?.[1] ?? match?.[2] ?? null;
|
|
239
355
|
}
|
|
240
356
|
async fetchKeyWithRetry(url, parserConfig) {
|
|
241
357
|
let retryCount = parserConfig.keyRetryCount ?? 3;
|
|
242
358
|
while (retryCount >= 0) try {
|
|
243
359
|
const response = await fetch(url, { headers: parserConfig.headers });
|
|
244
|
-
return
|
|
360
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
245
361
|
} catch (error) {
|
|
246
362
|
if (error.message.includes("scheme is not supported")) throw error;
|
|
247
363
|
console.warn(`Error fetching key: ${error.message}. Retries left: ${retryCount}`);
|
|
@@ -276,78 +392,6 @@ var ParserConfig = class {
|
|
|
276
392
|
keyRetryCount = 3;
|
|
277
393
|
};
|
|
278
394
|
|
|
279
|
-
//#endregion
|
|
280
|
-
//#region lib/shared/stream-spec.ts
|
|
281
|
-
var StreamSpec = class {
|
|
282
|
-
mediaType;
|
|
283
|
-
groupId = null;
|
|
284
|
-
language;
|
|
285
|
-
name;
|
|
286
|
-
default;
|
|
287
|
-
skippedDuration;
|
|
288
|
-
bandwidth;
|
|
289
|
-
codecs = null;
|
|
290
|
-
resolution;
|
|
291
|
-
frameRate;
|
|
292
|
-
channels = null;
|
|
293
|
-
extension = null;
|
|
294
|
-
role;
|
|
295
|
-
videoRange;
|
|
296
|
-
characteristics;
|
|
297
|
-
publishTime;
|
|
298
|
-
audioId;
|
|
299
|
-
videoId;
|
|
300
|
-
subtitleId;
|
|
301
|
-
periodId = null;
|
|
302
|
-
url = "";
|
|
303
|
-
originalUrl = "";
|
|
304
|
-
playlist;
|
|
305
|
-
get segmentsCount() {
|
|
306
|
-
return this.playlist?.mediaParts.reduce((sum, part) => sum + part.mediaSegments.length, 0) ?? 0;
|
|
307
|
-
}
|
|
308
|
-
toShortString() {
|
|
309
|
-
let prefixStr = "";
|
|
310
|
-
let returnStr = "";
|
|
311
|
-
const encStr = "";
|
|
312
|
-
const bandwidth = this.bandwidth ? `${this.bandwidth / 1e3} Kbps` : "";
|
|
313
|
-
const channels = this.channels ? `${this.channels}CH` : "";
|
|
314
|
-
if (this.mediaType === MEDIA_TYPES.AUDIO) {
|
|
315
|
-
prefixStr = `Aud ${encStr}`;
|
|
316
|
-
returnStr = [
|
|
317
|
-
this.groupId,
|
|
318
|
-
bandwidth,
|
|
319
|
-
this.name,
|
|
320
|
-
this.codecs,
|
|
321
|
-
this.language,
|
|
322
|
-
channels,
|
|
323
|
-
this.role
|
|
324
|
-
].filter(Boolean).join(" | ");
|
|
325
|
-
} else if (this.mediaType === MEDIA_TYPES.SUBTITLES) {
|
|
326
|
-
prefixStr = `Sub ${encStr}`;
|
|
327
|
-
returnStr = [
|
|
328
|
-
this.groupId,
|
|
329
|
-
this.language,
|
|
330
|
-
this.name,
|
|
331
|
-
this.codecs,
|
|
332
|
-
this.role
|
|
333
|
-
].filter(Boolean).join(" | ");
|
|
334
|
-
} else {
|
|
335
|
-
prefixStr = `Vid ${encStr}`;
|
|
336
|
-
returnStr = [
|
|
337
|
-
this.resolution,
|
|
338
|
-
bandwidth,
|
|
339
|
-
this.groupId,
|
|
340
|
-
this.frameRate,
|
|
341
|
-
this.codecs,
|
|
342
|
-
this.videoRange,
|
|
343
|
-
this.role
|
|
344
|
-
].filter(Boolean).join(" | ");
|
|
345
|
-
}
|
|
346
|
-
returnStr = `${prefixStr} | ${returnStr}`;
|
|
347
|
-
return returnStr.trim();
|
|
348
|
-
}
|
|
349
|
-
};
|
|
350
|
-
|
|
351
395
|
//#endregion
|
|
352
396
|
//#region lib/dash/dash-tags.ts
|
|
353
397
|
const DASH_TAGS = {
|
|
@@ -362,8 +406,7 @@ const DASH_TAGS = {
|
|
|
362
406
|
const combineUrl = (baseUrl, relativeUrl) => {
|
|
363
407
|
if (!baseUrl.trim()) return relativeUrl;
|
|
364
408
|
const url1 = new URL(baseUrl);
|
|
365
|
-
|
|
366
|
-
return url2.toString();
|
|
409
|
+
return new URL(relativeUrl, url1).toString();
|
|
367
410
|
};
|
|
368
411
|
const replaceVars = (text, dict) => {
|
|
369
412
|
let result = text;
|
|
@@ -402,7 +445,7 @@ const getAttribute = (line, key = "") => {
|
|
|
402
445
|
return result;
|
|
403
446
|
};
|
|
404
447
|
const distinctBy = (array, callbackfn) => {
|
|
405
|
-
const seen = new Set();
|
|
448
|
+
const seen = /* @__PURE__ */ new Set();
|
|
406
449
|
return array.filter((item) => {
|
|
407
450
|
const value = callbackfn(item);
|
|
408
451
|
if (seen.has(value)) return false;
|
|
@@ -427,51 +470,6 @@ var Playlist = class {
|
|
|
427
470
|
mediaParts = [];
|
|
428
471
|
};
|
|
429
472
|
|
|
430
|
-
//#endregion
|
|
431
|
-
//#region lib/shared/media-part.ts
|
|
432
|
-
var MediaPart = class {
|
|
433
|
-
mediaSegments = [];
|
|
434
|
-
constructor(segments) {
|
|
435
|
-
this.mediaSegments = segments || [];
|
|
436
|
-
}
|
|
437
|
-
};
|
|
438
|
-
|
|
439
|
-
//#endregion
|
|
440
|
-
//#region lib/shared/media-segment.ts
|
|
441
|
-
var MediaSegment = class MediaSegment {
|
|
442
|
-
index = NaN;
|
|
443
|
-
duration = NaN;
|
|
444
|
-
title;
|
|
445
|
-
dateTime;
|
|
446
|
-
startRange;
|
|
447
|
-
get stopRange() {
|
|
448
|
-
return this.startRange !== void 0 && this.expectLength !== void 0 ? this.startRange + this.expectLength - 1 : void 0;
|
|
449
|
-
}
|
|
450
|
-
expectLength;
|
|
451
|
-
encryptInfo = new EncryptInfo();
|
|
452
|
-
get isEncrypted() {
|
|
453
|
-
return this.encryptInfo.method !== ENCRYPT_METHODS.NONE;
|
|
454
|
-
}
|
|
455
|
-
url = "";
|
|
456
|
-
nameFromVar;
|
|
457
|
-
equals(segment) {
|
|
458
|
-
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;
|
|
459
|
-
else return false;
|
|
460
|
-
}
|
|
461
|
-
getHashCode() {
|
|
462
|
-
const payload = [
|
|
463
|
-
this.index,
|
|
464
|
-
this.duration,
|
|
465
|
-
this.title,
|
|
466
|
-
this.startRange,
|
|
467
|
-
this.stopRange,
|
|
468
|
-
this.expectLength,
|
|
469
|
-
this.url
|
|
470
|
-
].join("-");
|
|
471
|
-
return node_crypto.default.createHash("md5").update(payload).digest("hex");
|
|
472
|
-
}
|
|
473
|
-
};
|
|
474
|
-
|
|
475
473
|
//#endregion
|
|
476
474
|
//#region lib/dash/dash-utils.ts
|
|
477
475
|
/**
|
|
@@ -481,8 +479,7 @@ var MediaSegment = class MediaSegment {
|
|
|
481
479
|
*/
|
|
482
480
|
const parseRange = (range) => {
|
|
483
481
|
const [startRange, end] = range.split("-").map(Number);
|
|
484
|
-
|
|
485
|
-
return [startRange, expectLength];
|
|
482
|
+
return [startRange, end - startRange + 1];
|
|
486
483
|
};
|
|
487
484
|
|
|
488
485
|
//#endregion
|
|
@@ -518,11 +515,9 @@ var DashExtractor = class DashExtractor {
|
|
|
518
515
|
async extractStreams(rawText) {
|
|
519
516
|
const streamList = [];
|
|
520
517
|
this.#mpdContent = rawText;
|
|
521
|
-
const
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
const isLive = type === "dynamic";
|
|
525
|
-
const maxSegmentDuration = mpdElement.getAttribute("maxSegmentDuration");
|
|
518
|
+
const mpdElement = new __xmldom_xmldom.DOMParser().parseFromString(this.#mpdContent, "text/xml").getElementsByTagName("MPD")[0];
|
|
519
|
+
const isLive = mpdElement.getAttribute("type") === "dynamic";
|
|
520
|
+
mpdElement.getAttribute("maxSegmentDuration");
|
|
526
521
|
const availabilityStartTime = mpdElement.getAttribute("availabilityStartTime");
|
|
527
522
|
const timeShiftBufferDepth = mpdElement.getAttribute("timeShiftBufferDepth") || "PT1M";
|
|
528
523
|
const publishTime = mpdElement.getAttribute("publishTime");
|
|
@@ -581,8 +576,7 @@ var DashExtractor = class DashExtractor {
|
|
|
581
576
|
if (role) {
|
|
582
577
|
const roleValue = role.getAttribute("value");
|
|
583
578
|
const capitalize = (word) => word.charAt(0).toUpperCase() + word.slice(1);
|
|
584
|
-
const
|
|
585
|
-
const roleType = ROLE_TYPE[roleTypeKey];
|
|
579
|
+
const roleType = ROLE_TYPE[roleValue.split("-").map(capitalize).join("")];
|
|
586
580
|
streamSpec.role = roleType;
|
|
587
581
|
if (roleType === ROLE_TYPE.Subtitle) {
|
|
588
582
|
streamSpec.mediaType = MEDIA_TYPES.SUBTITLES;
|
|
@@ -713,8 +707,7 @@ var DashExtractor = class DashExtractor {
|
|
|
713
707
|
varDic[DASH_TAGS.TemplateNumber] = segNumber++;
|
|
714
708
|
const _hashTime = mediaTemplate?.includes(DASH_TAGS.TemplateTime);
|
|
715
709
|
const _media = replaceVars(mediaTemplate, varDic);
|
|
716
|
-
|
|
717
|
-
_mediaSegment.url = _mediaUrl;
|
|
710
|
+
_mediaSegment.url = combineUrl(segBaseUrl, _media);
|
|
718
711
|
_mediaSegment.index = segIndex++;
|
|
719
712
|
_mediaSegment.duration = _duration / timescale;
|
|
720
713
|
if (_hashTime) _mediaSegment.nameFromVar = currentTime.toString();
|
|
@@ -758,27 +751,37 @@ var DashExtractor = class DashExtractor {
|
|
|
758
751
|
mediaSegment.duration = periodDurationSeconds;
|
|
759
752
|
streamSpec.playlist.mediaParts[0].mediaSegments.push(mediaSegment);
|
|
760
753
|
}
|
|
761
|
-
const
|
|
762
|
-
|
|
754
|
+
const adaptationSetProtections = adaptationSet.getElementsByTagName("ContentProtection");
|
|
755
|
+
const representationProtections = representation.getElementsByTagName("ContentProtection");
|
|
756
|
+
const contentProtections = representationProtections[0] ? representationProtections : adaptationSetProtections;
|
|
757
|
+
if (contentProtections.length) {
|
|
763
758
|
const encryptInfo = new EncryptInfo();
|
|
764
759
|
encryptInfo.method = DashExtractor.#DEFAULT_METHOD;
|
|
760
|
+
const widevineSystemId = "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed";
|
|
761
|
+
const playreadySystemId = "9a04f079-9840-4286-ab92-e65be0885f95";
|
|
762
|
+
for (const contentProtection of contentProtections) {
|
|
763
|
+
const schemeIdUri = contentProtection.getAttribute("schemeIdUri");
|
|
764
|
+
const drmData = {
|
|
765
|
+
keyId: contentProtection.getAttribute("cenc:default_KID") || void 0,
|
|
766
|
+
pssh: contentProtection.getElementsByTagName("cenc:pssh")[0]?.textContent || void 0
|
|
767
|
+
};
|
|
768
|
+
if (schemeIdUri?.includes(widevineSystemId)) encryptInfo.drm.widevine = drmData;
|
|
769
|
+
else if (schemeIdUri?.includes(playreadySystemId)) encryptInfo.drm.playready = drmData;
|
|
770
|
+
else continue;
|
|
771
|
+
}
|
|
765
772
|
if (streamSpec.playlist.mediaInit) streamSpec.playlist.mediaInit.encryptInfo = encryptInfo;
|
|
766
773
|
const segments = streamSpec.playlist.mediaParts[0].mediaSegments;
|
|
767
774
|
for (const segment of segments) if (!segment.encryptInfo) segment.encryptInfo = encryptInfo;
|
|
768
775
|
}
|
|
769
776
|
const _index = streamList.findIndex((item) => item.periodId !== streamSpec.periodId && item.groupId === streamSpec.groupId && item.resolution === streamSpec.resolution && item.mediaType === streamSpec.mediaType);
|
|
770
|
-
if (_index > -1) if (isLive) {} else {
|
|
771
|
-
const
|
|
772
|
-
const
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
mediaPart.mediaSegments = streamList[_index].playlist.mediaParts[0].mediaSegments;
|
|
779
|
-
streamList[_index].playlist.mediaParts.push(mediaPart);
|
|
780
|
-
} else streamList[_index].playlist.mediaParts.at(-1).mediaSegments.at(-1).duration += streamSpec.playlist.mediaParts[0].mediaSegments.reduce((sum, segment) => sum + segment.duration, 0);
|
|
781
|
-
}
|
|
777
|
+
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) {
|
|
778
|
+
const startIndex = streamList[_index].playlist.mediaParts.at(-1).mediaSegments.at(-1).index + 1;
|
|
779
|
+
const segments = streamSpec.playlist.mediaParts[0].mediaSegments;
|
|
780
|
+
for (const segment of segments) segment.index += startIndex;
|
|
781
|
+
const mediaPart = new MediaPart();
|
|
782
|
+
mediaPart.mediaSegments = streamList[_index].playlist.mediaParts[0].mediaSegments;
|
|
783
|
+
streamList[_index].playlist.mediaParts.push(mediaPart);
|
|
784
|
+
} else streamList[_index].playlist.mediaParts.at(-1).mediaSegments.at(-1).duration += streamSpec.playlist.mediaParts[0].mediaSegments.reduce((sum, segment) => sum + segment.duration, 0);
|
|
782
785
|
else {
|
|
783
786
|
if (streamSpec.mediaType === MEDIA_TYPES.SUBTITLES && streamSpec.extension === "mp4") streamSpec.extension = "m4s";
|
|
784
787
|
if (streamSpec.mediaType !== MEDIA_TYPES.SUBTITLES && (streamSpec.extension == null || streamSpec.playlist.mediaParts.reduce((sum, part) => sum + part.mediaSegments.length, 0) > 1)) streamSpec.extension = "m4s";
|
|
@@ -793,8 +796,8 @@ var DashExtractor = class DashExtractor {
|
|
|
793
796
|
const subtitleList = streamList.filter((stream) => stream.mediaType === MEDIA_TYPES.SUBTITLES);
|
|
794
797
|
const videoList = streamList.filter((stream) => stream.mediaType === MEDIA_TYPES.VIDEO);
|
|
795
798
|
for (const video of videoList) {
|
|
796
|
-
const audioGroupId = audioList.toSorted((a, b) => (b.bandwidth || 0) - (a.bandwidth || 0)).at(0)?.groupId;
|
|
797
|
-
const subtitleGroupId = subtitleList.toSorted((a, b) => (b.bandwidth || 0) - (a.bandwidth || 0)).at(0)?.groupId;
|
|
799
|
+
const audioGroupId = audioList.toSorted((a, b$1) => (b$1.bandwidth || 0) - (a.bandwidth || 0)).at(0)?.groupId;
|
|
800
|
+
const subtitleGroupId = subtitleList.toSorted((a, b$1) => (b$1.bandwidth || 0) - (a.bandwidth || 0)).at(0)?.groupId;
|
|
798
801
|
if (audioGroupId) video.audioId = audioGroupId;
|
|
799
802
|
if (subtitleGroupId) video.subtitleId = subtitleGroupId;
|
|
800
803
|
}
|
|
@@ -804,17 +807,38 @@ var DashExtractor = class DashExtractor {
|
|
|
804
807
|
if (!v) return;
|
|
805
808
|
return v;
|
|
806
809
|
}
|
|
807
|
-
|
|
808
|
-
|
|
810
|
+
async refreshPlayList(streamSpecs) {
|
|
811
|
+
if (!streamSpecs.length) return;
|
|
812
|
+
const response = await fetch(this.#parserConfig.url, this.#parserConfig.headers).catch(() => fetch(this.#parserConfig.originalUrl, this.#parserConfig.headers));
|
|
813
|
+
const rawText = await response.text();
|
|
814
|
+
const url = response.url;
|
|
815
|
+
this.#parserConfig.url = url;
|
|
816
|
+
this.#setInitUrl();
|
|
817
|
+
const newStreams = await this.extractStreams(rawText);
|
|
818
|
+
for (const streamSpec of streamSpecs) {
|
|
819
|
+
let results = newStreams.filter((n) => n.toShortString() === streamSpec.toShortString());
|
|
820
|
+
if (!results.length) results = newStreams.filter((n) => n.playlist?.mediaInit?.url === streamSpec.playlist?.mediaInit?.url);
|
|
821
|
+
if (results.length) streamSpec.playlist.mediaParts = results.at(0).playlist.mediaParts;
|
|
822
|
+
}
|
|
823
|
+
await this.#processUrl(streamSpecs);
|
|
809
824
|
}
|
|
810
|
-
|
|
811
|
-
|
|
825
|
+
async #processUrl(streamSpecs) {
|
|
826
|
+
for (const spec of streamSpecs) {
|
|
827
|
+
const playlist = spec.playlist;
|
|
828
|
+
if (!playlist) continue;
|
|
829
|
+
if (playlist.mediaInit) playlist.mediaInit.url = this.preProcessUrl(playlist.mediaInit.url);
|
|
830
|
+
for (const part of playlist.mediaParts) for (const segment of part.mediaSegments) segment.url = this.preProcessUrl(segment.url);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
async fetchPlayList(streamSpecs) {
|
|
834
|
+
this.#processUrl(streamSpecs);
|
|
812
835
|
}
|
|
813
836
|
preProcessUrl(url) {
|
|
814
|
-
|
|
837
|
+
for (const processor of this.#parserConfig.urlProcessors) if (processor.canProcess(this.extractorType, url, this.#parserConfig)) url = processor.process(url, this.#parserConfig);
|
|
838
|
+
return url;
|
|
815
839
|
}
|
|
816
840
|
preProcessContent() {
|
|
817
|
-
|
|
841
|
+
for (const processor of this.#parserConfig.contentProcessors) if (processor.canProcess(this.extractorType, this.#mpdContent, this.#parserConfig)) this.#mpdContent = processor.process(this.#mpdContent, this.#parserConfig);
|
|
818
842
|
}
|
|
819
843
|
};
|
|
820
844
|
|
|
@@ -893,8 +917,7 @@ var HlsExtractor = class {
|
|
|
893
917
|
expectPlaylist = true;
|
|
894
918
|
} else if (line.startsWith(HLS_TAGS.extXMedia)) {
|
|
895
919
|
streamSpec = new StreamSpec();
|
|
896
|
-
const
|
|
897
|
-
const mediaType = MEDIA_TYPES[type];
|
|
920
|
+
const mediaType = MEDIA_TYPES[getAttribute(line, "TYPE").replace("-", "_")];
|
|
898
921
|
if (mediaType) streamSpec.mediaType = mediaType;
|
|
899
922
|
if (mediaType === MEDIA_TYPES.CLOSED_CAPTIONS) continue;
|
|
900
923
|
let url = getAttribute(line, "URI");
|
|
@@ -945,8 +968,7 @@ var HlsExtractor = class {
|
|
|
945
968
|
for (const line of lines) {
|
|
946
969
|
if (!line.trim()) continue;
|
|
947
970
|
if (line.startsWith(HLS_TAGS.extXByterange)) {
|
|
948
|
-
const
|
|
949
|
-
const [n, o] = getRange(p);
|
|
971
|
+
const [n, o] = getRange(getAttribute(line));
|
|
950
972
|
segment.expectLength = n;
|
|
951
973
|
segment.startRange = o || (segments.at(-1)?.startRange || 0) + (segments.at(-1)?.expectLength || 0);
|
|
952
974
|
expectSegment = true;
|
|
@@ -968,9 +990,7 @@ var HlsExtractor = class {
|
|
|
968
990
|
mediaParts.push(new MediaPart(segments));
|
|
969
991
|
segments = [];
|
|
970
992
|
} else if (line.startsWith(HLS_TAGS.extXKey)) {
|
|
971
|
-
|
|
972
|
-
const uriLast = getAttribute(lastKeyLine, "URI");
|
|
973
|
-
if (uri !== uriLast) {
|
|
993
|
+
if (getAttribute(line, "URI") !== getAttribute(lastKeyLine, "URI")) {
|
|
974
994
|
const parsedInfo = await this.#parseKey(line);
|
|
975
995
|
currentEncryptInfo.method = parsedInfo.method;
|
|
976
996
|
currentEncryptInfo.key = parsedInfo.key;
|
|
@@ -998,8 +1018,7 @@ var HlsExtractor = class {
|
|
|
998
1018
|
mediaSegment.index = -1;
|
|
999
1019
|
playlist.mediaInit = mediaSegment;
|
|
1000
1020
|
if (line.includes("BYTERANGE")) {
|
|
1001
|
-
const
|
|
1002
|
-
const [n, o] = getRange(p);
|
|
1021
|
+
const [n, o] = getRange(getAttribute(line, "BYTERANGE"));
|
|
1003
1022
|
mediaSegment.expectLength = n;
|
|
1004
1023
|
mediaSegment.startRange = o || 0;
|
|
1005
1024
|
}
|
|
@@ -1048,10 +1067,7 @@ var HlsExtractor = class {
|
|
|
1048
1067
|
async extractStreams(rawText) {
|
|
1049
1068
|
this.#m3u8Content = rawText;
|
|
1050
1069
|
this.preProcessContent();
|
|
1051
|
-
if (this.#m3u8Content.includes(HLS_TAGS.extXStreamInf))
|
|
1052
|
-
console.log("Master m3u8 found");
|
|
1053
|
-
return this.#parseMasterList().then((lists) => distinctBy(lists, (list) => list.url));
|
|
1054
|
-
}
|
|
1070
|
+
if (this.#m3u8Content.includes(HLS_TAGS.extXStreamInf)) return this.#parseMasterList().then((lists) => distinctBy(lists, (list) => list.url));
|
|
1055
1071
|
const playlist = await this.#parseList();
|
|
1056
1072
|
const streamSpec = new StreamSpec();
|
|
1057
1073
|
streamSpec.url = this.parserConfig.url;
|
|
@@ -1061,8 +1077,7 @@ var HlsExtractor = class {
|
|
|
1061
1077
|
}
|
|
1062
1078
|
async #loadM3u8FromUrl(url) {
|
|
1063
1079
|
if (url.startsWith("file:")) {
|
|
1064
|
-
const
|
|
1065
|
-
const filePath = uri.pathname;
|
|
1080
|
+
const filePath = new URL(url).pathname;
|
|
1066
1081
|
this.#m3u8Content = await (0, node_fs_promises.readFile)(filePath, "utf8");
|
|
1067
1082
|
} else if (url.startsWith("http")) try {
|
|
1068
1083
|
const response = await fetch(url, { headers: this.parserConfig.headers });
|
|
@@ -1102,9 +1117,9 @@ var HlsExtractor = class {
|
|
|
1102
1117
|
else list.playlist = newPlaylist;
|
|
1103
1118
|
if (list.mediaType === MEDIA_TYPES.SUBTITLES) {
|
|
1104
1119
|
const a = list.playlist.mediaParts.some((part) => part.mediaSegments.some((segment) => segment.url.includes(".ttml")));
|
|
1105
|
-
const b = list.playlist.mediaParts.some((part) => part.mediaSegments.some((segment) => segment.url.includes(".vtt") || segment.url.includes(".webvtt")));
|
|
1120
|
+
const b$1 = list.playlist.mediaParts.some((part) => part.mediaSegments.some((segment) => segment.url.includes(".vtt") || segment.url.includes(".webvtt")));
|
|
1106
1121
|
if (a) list.extension = "ttml";
|
|
1107
|
-
if (b) list.extension = "vtt";
|
|
1122
|
+
if (b$1) list.extension = "vtt";
|
|
1108
1123
|
} else list.extension = list.playlist.mediaInit ? "m4s" : "ts";
|
|
1109
1124
|
}
|
|
1110
1125
|
}
|
|
@@ -1132,8 +1147,7 @@ var StreamExtractor = class {
|
|
|
1132
1147
|
}
|
|
1133
1148
|
async loadSourceFromUrl(url) {
|
|
1134
1149
|
if (url.startsWith("file:")) {
|
|
1135
|
-
const
|
|
1136
|
-
const filePath = uri.pathname;
|
|
1150
|
+
const filePath = new URL(url).pathname;
|
|
1137
1151
|
this.#rawText = await (0, node_fs_promises.readFile)(filePath, "utf8");
|
|
1138
1152
|
this.#setUrl(url);
|
|
1139
1153
|
} else if (url.startsWith("http")) {
|
|
@@ -1160,8 +1174,11 @@ var StreamExtractor = class {
|
|
|
1160
1174
|
} else if (this.#rawText.includes("</MPD>") && this.#rawText.includes("<MPD")) {
|
|
1161
1175
|
this.#extractor = new DashExtractor(this.#parserConfig);
|
|
1162
1176
|
rawType = "mpd";
|
|
1163
|
-
} else if (this.#rawText.includes("</SmoothStreamingMedia>") && this.#rawText.includes("<SmoothStreamingMedia"))
|
|
1164
|
-
|
|
1177
|
+
} else if (this.#rawText.includes("</SmoothStreamingMedia>") && this.#rawText.includes("<SmoothStreamingMedia")) {
|
|
1178
|
+
rawType = "ism";
|
|
1179
|
+
throw new Error("Smooth Streaming is not supported yet");
|
|
1180
|
+
} else if (rawText === "<RE_LIVE_TS>") throw new Error("Live TS is not supported yet");
|
|
1181
|
+
else throw new Error("Unsupported stream type");
|
|
1165
1182
|
this.#rawFiles[`raw.${rawType}`] = rawText;
|
|
1166
1183
|
}
|
|
1167
1184
|
async extractStreams() {
|
|
@@ -1176,13 +1193,23 @@ var StreamExtractor = class {
|
|
|
1176
1193
|
};
|
|
1177
1194
|
|
|
1178
1195
|
//#endregion
|
|
1179
|
-
exports.
|
|
1180
|
-
exports.
|
|
1181
|
-
exports.
|
|
1182
|
-
exports.
|
|
1183
|
-
exports.
|
|
1184
|
-
exports.
|
|
1185
|
-
exports.
|
|
1186
|
-
exports.
|
|
1187
|
-
exports.
|
|
1188
|
-
exports.
|
|
1196
|
+
exports.DASH_TAGS = DASH_TAGS;
|
|
1197
|
+
exports.DashExtractor = DashExtractor;
|
|
1198
|
+
exports.DefaultDashContentProcessor = DefaultDashContentProcessor;
|
|
1199
|
+
exports.DefaultHlsContentProcessor = DefaultHlsContentProcessor;
|
|
1200
|
+
exports.DefaultHlsKeyProcessor = DefaultHlsKeyProcessor;
|
|
1201
|
+
exports.DefaultUrlProcessor = DefaultUrlProcessor;
|
|
1202
|
+
exports.ENCRYPT_METHODS = ENCRYPT_METHODS;
|
|
1203
|
+
exports.EXTRACTOR_TYPES = EXTRACTOR_TYPES;
|
|
1204
|
+
exports.EncryptInfo = EncryptInfo;
|
|
1205
|
+
exports.HLS_TAGS = HLS_TAGS;
|
|
1206
|
+
exports.HlsExtractor = HlsExtractor;
|
|
1207
|
+
exports.MEDIA_TYPES = MEDIA_TYPES;
|
|
1208
|
+
exports.MediaPart = MediaPart;
|
|
1209
|
+
exports.MediaSegment = MediaSegment;
|
|
1210
|
+
exports.ParserConfig = ParserConfig;
|
|
1211
|
+
exports.ROLE_TYPE = ROLE_TYPE;
|
|
1212
|
+
exports.StreamExtractor = StreamExtractor;
|
|
1213
|
+
exports.StreamSpec = StreamSpec;
|
|
1214
|
+
exports.getRange = getRange;
|
|
1215
|
+
exports.parseRange = parseRange;
|