dasha 4.0.0-alpha.2 → 4.0.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/dasha.cjs +120 -100
- package/dist/dasha.d.cts +27 -34
- package/dist/dasha.d.ts +27 -34
- package/dist/dasha.js +99 -83
- package/package.json +17 -20
package/README.md
CHANGED
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
Library for parsing MPEG-DASH (.mpd) and HLS (.m3u8) manifests. Made with the purpose of obtaining a simplified representation convenient for further downloading of segments.
|
|
8
8
|
|
|
9
9
|
> [!WARNING]
|
|
10
|
-
> This README is for the alpha version. Info about latest stable version is available on [NPM](https://www.npmjs.com/package/dasha/v/3.1.5) or [another GitHub branch](https://github.com/
|
|
10
|
+
> This README is for the alpha version. Info about latest stable version is available on [NPM](https://www.npmjs.com/package/dasha/v/3.1.5) or [another GitHub branch](https://github.com/azot-labs/dasha/tree/v3).
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
## Install
|
|
14
14
|
|
|
15
15
|
```shell
|
|
16
|
-
npm i dasha@
|
|
16
|
+
npm i dasha@alpha
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
## Usage
|
package/dist/dasha.cjs
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
//#region rolldown:runtime
|
|
3
2
|
var __create = Object.create;
|
|
4
3
|
var __defProp = Object.defineProperty;
|
|
@@ -22,13 +21,22 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
22
21
|
}) : target, mod));
|
|
23
22
|
|
|
24
23
|
//#endregion
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
let node_fs = require("node:fs");
|
|
25
|
+
node_fs = __toESM(node_fs);
|
|
26
|
+
let barsic = require("barsic");
|
|
27
|
+
barsic = __toESM(barsic);
|
|
28
|
+
let node_fs_promises = require("node:fs/promises");
|
|
29
|
+
node_fs_promises = __toESM(node_fs_promises);
|
|
30
|
+
let node_url = require("node:url");
|
|
31
|
+
node_url = __toESM(node_url);
|
|
32
|
+
let node_path = require("node:path");
|
|
33
|
+
node_path = __toESM(node_path);
|
|
34
|
+
let temporal_polyfill = require("temporal-polyfill");
|
|
35
|
+
temporal_polyfill = __toESM(temporal_polyfill);
|
|
36
|
+
let __xmldom_xmldom = require("@xmldom/xmldom");
|
|
37
|
+
__xmldom_xmldom = __toESM(__xmldom_xmldom);
|
|
38
|
+
let node_crypto = require("node:crypto");
|
|
39
|
+
node_crypto = __toESM(node_crypto);
|
|
32
40
|
|
|
33
41
|
//#region lib/shared/media-type.ts
|
|
34
42
|
const MEDIA_TYPES = {
|
|
@@ -41,14 +49,14 @@ const MEDIA_TYPES = {
|
|
|
41
49
|
//#endregion
|
|
42
50
|
//#region lib/shared/encrypt-method.ts
|
|
43
51
|
const ENCRYPT_METHODS = {
|
|
44
|
-
NONE:
|
|
45
|
-
AES_128:
|
|
46
|
-
AES_128_ECB:
|
|
47
|
-
SAMPLE_AES:
|
|
48
|
-
SAMPLE_AES_CTR:
|
|
49
|
-
CENC:
|
|
50
|
-
CHACHA20:
|
|
51
|
-
UNKNOWN:
|
|
52
|
+
NONE: "none",
|
|
53
|
+
AES_128: "aes-128",
|
|
54
|
+
AES_128_ECB: "aes-128-ecb",
|
|
55
|
+
SAMPLE_AES: "sample-aes",
|
|
56
|
+
SAMPLE_AES_CTR: "sample-aes-ctr",
|
|
57
|
+
CENC: "cenc",
|
|
58
|
+
CHACHA20: "chacha20",
|
|
59
|
+
UNKNOWN: "unknown"
|
|
52
60
|
};
|
|
53
61
|
|
|
54
62
|
//#endregion
|
|
@@ -77,17 +85,13 @@ const ROLE_TYPE = {
|
|
|
77
85
|
|
|
78
86
|
//#endregion
|
|
79
87
|
//#region lib/processor.ts
|
|
80
|
-
var
|
|
81
|
-
var KeyProcessor = class {};
|
|
82
|
-
var UrlProcessor = class {};
|
|
83
|
-
var DefaultUrlProcessor = class extends UrlProcessor {
|
|
88
|
+
var DefaultUrlProcessor = class {
|
|
84
89
|
canProcess(_extractorType, _originalUrl, parserConfig) {
|
|
85
90
|
return parserConfig.appendUrlParams;
|
|
86
91
|
}
|
|
87
92
|
process(url, parserConfig) {
|
|
88
93
|
if (!url.startsWith("http")) return url;
|
|
89
|
-
const
|
|
90
|
-
const urlFromConfigQuery = urlFromConfig.searchParams;
|
|
94
|
+
const urlFromConfigQuery = new URL(parserConfig.url).searchParams;
|
|
91
95
|
const oldUrl = new URL(url);
|
|
92
96
|
const newQuery = oldUrl.searchParams;
|
|
93
97
|
for (const [key, value] of urlFromConfigQuery) if (newQuery.has(key)) newQuery.set(key, value);
|
|
@@ -102,7 +106,7 @@ var DefaultUrlProcessor = class extends UrlProcessor {
|
|
|
102
106
|
|
|
103
107
|
//#endregion
|
|
104
108
|
//#region lib/dash/dash-content-processor.ts
|
|
105
|
-
var DefaultDashContentProcessor = class
|
|
109
|
+
var DefaultDashContentProcessor = class {
|
|
106
110
|
canProcess(extractorType, mpdContent) {
|
|
107
111
|
if (extractorType !== EXTRACTOR_TYPES.MPEG_DASH) return false;
|
|
108
112
|
return mpdContent.includes("<mas:") && !mpdContent.includes("xmlns:mas");
|
|
@@ -147,7 +151,7 @@ const HLS_TAGS = {
|
|
|
147
151
|
|
|
148
152
|
//#endregion
|
|
149
153
|
//#region lib/hls/hls-content-processor.ts
|
|
150
|
-
var DefaultHlsContentProcessor = class DefaultHlsContentProcessor
|
|
154
|
+
var DefaultHlsContentProcessor = class DefaultHlsContentProcessor {
|
|
151
155
|
static YkDVRegex = /#EXT-X-DISCONTINUITY\s+#EXT-X-MAP:URI="(.*?)",BYTERANGE="(.*?)"/g;
|
|
152
156
|
static DNSPRegex = /#EXT-X-MAP:URI=".*?BUMPER\/[\s\S]+?#EXT-X-DISCONTINUITY/;
|
|
153
157
|
static DNSPSubRegex = /#EXTINF:.*?,\s+.*BUMPER.*\s+#EXT-X-DISCONTINUITY/;
|
|
@@ -186,8 +190,10 @@ var EncryptInfo = class {
|
|
|
186
190
|
method = ENCRYPT_METHODS.NONE;
|
|
187
191
|
key;
|
|
188
192
|
iv;
|
|
193
|
+
drm;
|
|
189
194
|
constructor(method) {
|
|
190
195
|
this.method = this.parseMethod(method);
|
|
196
|
+
this.drm = {};
|
|
191
197
|
}
|
|
192
198
|
parseMethod(method) {
|
|
193
199
|
if (method) return ENCRYPT_METHODS[method.replace("-", "_")];
|
|
@@ -197,7 +203,7 @@ var EncryptInfo = class {
|
|
|
197
203
|
|
|
198
204
|
//#endregion
|
|
199
205
|
//#region lib/hls/hls-key-processor.ts
|
|
200
|
-
var DefaultHlsKeyProcessor = class
|
|
206
|
+
var DefaultHlsKeyProcessor = class {
|
|
201
207
|
canProcess(extractorType) {
|
|
202
208
|
return extractorType === EXTRACTOR_TYPES.HLS;
|
|
203
209
|
}
|
|
@@ -205,17 +211,16 @@ var DefaultHlsKeyProcessor = class extends KeyProcessor {
|
|
|
205
211
|
const iv = this.getAttribute(keyLine, "IV");
|
|
206
212
|
const method = this.getAttribute(keyLine, "METHOD");
|
|
207
213
|
const uri = this.getAttribute(keyLine, "URI");
|
|
208
|
-
console.debug(`METHOD:${method}, URI:${uri}, IV:${iv}`);
|
|
209
214
|
const encryptInfo = new EncryptInfo(method);
|
|
210
|
-
if (iv) encryptInfo.iv =
|
|
215
|
+
if (iv) encryptInfo.iv = barsic.b.hex().encode(iv);
|
|
211
216
|
if (parserConfig.customIv && parserConfig.customIv.length > 0) encryptInfo.iv = parserConfig.customIv;
|
|
212
217
|
try {
|
|
213
218
|
if (parserConfig.customKey && parserConfig.customKey.length > 0) encryptInfo.key = parserConfig.customKey;
|
|
214
219
|
else if (uri) {
|
|
215
220
|
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 =
|
|
221
|
+
if (lowerUri.startsWith("base64:")) encryptInfo.key = barsic.b.base64().encode(uri.slice(7));
|
|
222
|
+
else if (lowerUri.startsWith("data:;base64,")) encryptInfo.key = barsic.b.base64().encode(uri.slice(13));
|
|
223
|
+
else if (lowerUri.startsWith("data:text/plain;base64,")) encryptInfo.key = barsic.b.base64().encode(uri.slice(23));
|
|
219
224
|
else if ((0, node_fs.existsSync)(uri)) encryptInfo.key = (0, node_fs.readFileSync)(uri);
|
|
220
225
|
else {
|
|
221
226
|
const processedUrl = this.preProcessUrl(new URL(uri, m3u8Url).toString(), parserConfig);
|
|
@@ -233,15 +238,15 @@ var DefaultHlsKeyProcessor = class extends KeyProcessor {
|
|
|
233
238
|
return encryptInfo;
|
|
234
239
|
}
|
|
235
240
|
getAttribute(line, attrName) {
|
|
236
|
-
const regex = new RegExp(`${attrName}="([^"]+)"`, "i");
|
|
241
|
+
const regex = new RegExp(`${attrName}=(?:"([^"]+)"|([^,]+))`, "i");
|
|
237
242
|
const match = line.match(regex);
|
|
238
|
-
return match?.[1] ?? null;
|
|
243
|
+
return match?.[1] ?? match?.[2] ?? null;
|
|
239
244
|
}
|
|
240
245
|
async fetchKeyWithRetry(url, parserConfig) {
|
|
241
246
|
let retryCount = parserConfig.keyRetryCount ?? 3;
|
|
242
247
|
while (retryCount >= 0) try {
|
|
243
248
|
const response = await fetch(url, { headers: parserConfig.headers });
|
|
244
|
-
return
|
|
249
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
245
250
|
} catch (error) {
|
|
246
251
|
if (error.message.includes("scheme is not supported")) throw error;
|
|
247
252
|
console.warn(`Error fetching key: ${error.message}. Retries left: ${retryCount}`);
|
|
@@ -362,8 +367,7 @@ const DASH_TAGS = {
|
|
|
362
367
|
const combineUrl = (baseUrl, relativeUrl) => {
|
|
363
368
|
if (!baseUrl.trim()) return relativeUrl;
|
|
364
369
|
const url1 = new URL(baseUrl);
|
|
365
|
-
|
|
366
|
-
return url2.toString();
|
|
370
|
+
return new URL(relativeUrl, url1).toString();
|
|
367
371
|
};
|
|
368
372
|
const replaceVars = (text, dict) => {
|
|
369
373
|
let result = text;
|
|
@@ -402,7 +406,7 @@ const getAttribute = (line, key = "") => {
|
|
|
402
406
|
return result;
|
|
403
407
|
};
|
|
404
408
|
const distinctBy = (array, callbackfn) => {
|
|
405
|
-
const seen = new Set();
|
|
409
|
+
const seen = /* @__PURE__ */ new Set();
|
|
406
410
|
return array.filter((item) => {
|
|
407
411
|
const value = callbackfn(item);
|
|
408
412
|
if (seen.has(value)) return false;
|
|
@@ -481,8 +485,7 @@ var MediaSegment = class MediaSegment {
|
|
|
481
485
|
*/
|
|
482
486
|
const parseRange = (range) => {
|
|
483
487
|
const [startRange, end] = range.split("-").map(Number);
|
|
484
|
-
|
|
485
|
-
return [startRange, expectLength];
|
|
488
|
+
return [startRange, end - startRange + 1];
|
|
486
489
|
};
|
|
487
490
|
|
|
488
491
|
//#endregion
|
|
@@ -518,11 +521,9 @@ var DashExtractor = class DashExtractor {
|
|
|
518
521
|
async extractStreams(rawText) {
|
|
519
522
|
const streamList = [];
|
|
520
523
|
this.#mpdContent = rawText;
|
|
521
|
-
const
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
const isLive = type === "dynamic";
|
|
525
|
-
const maxSegmentDuration = mpdElement.getAttribute("maxSegmentDuration");
|
|
524
|
+
const mpdElement = new __xmldom_xmldom.DOMParser().parseFromString(this.#mpdContent, "text/xml").getElementsByTagName("MPD")[0];
|
|
525
|
+
const isLive = mpdElement.getAttribute("type") === "dynamic";
|
|
526
|
+
mpdElement.getAttribute("maxSegmentDuration");
|
|
526
527
|
const availabilityStartTime = mpdElement.getAttribute("availabilityStartTime");
|
|
527
528
|
const timeShiftBufferDepth = mpdElement.getAttribute("timeShiftBufferDepth") || "PT1M";
|
|
528
529
|
const publishTime = mpdElement.getAttribute("publishTime");
|
|
@@ -581,8 +582,7 @@ var DashExtractor = class DashExtractor {
|
|
|
581
582
|
if (role) {
|
|
582
583
|
const roleValue = role.getAttribute("value");
|
|
583
584
|
const capitalize = (word) => word.charAt(0).toUpperCase() + word.slice(1);
|
|
584
|
-
const
|
|
585
|
-
const roleType = ROLE_TYPE[roleTypeKey];
|
|
585
|
+
const roleType = ROLE_TYPE[roleValue.split("-").map(capitalize).join("")];
|
|
586
586
|
streamSpec.role = roleType;
|
|
587
587
|
if (roleType === ROLE_TYPE.Subtitle) {
|
|
588
588
|
streamSpec.mediaType = MEDIA_TYPES.SUBTITLES;
|
|
@@ -713,8 +713,7 @@ var DashExtractor = class DashExtractor {
|
|
|
713
713
|
varDic[DASH_TAGS.TemplateNumber] = segNumber++;
|
|
714
714
|
const _hashTime = mediaTemplate?.includes(DASH_TAGS.TemplateTime);
|
|
715
715
|
const _media = replaceVars(mediaTemplate, varDic);
|
|
716
|
-
|
|
717
|
-
_mediaSegment.url = _mediaUrl;
|
|
716
|
+
_mediaSegment.url = combineUrl(segBaseUrl, _media);
|
|
718
717
|
_mediaSegment.index = segIndex++;
|
|
719
718
|
_mediaSegment.duration = _duration / timescale;
|
|
720
719
|
if (_hashTime) _mediaSegment.nameFromVar = currentTime.toString();
|
|
@@ -758,27 +757,37 @@ var DashExtractor = class DashExtractor {
|
|
|
758
757
|
mediaSegment.duration = periodDurationSeconds;
|
|
759
758
|
streamSpec.playlist.mediaParts[0].mediaSegments.push(mediaSegment);
|
|
760
759
|
}
|
|
761
|
-
const
|
|
762
|
-
|
|
760
|
+
const adaptationSetProtections = adaptationSet.getElementsByTagName("ContentProtection");
|
|
761
|
+
const representationProtections = representation.getElementsByTagName("ContentProtection");
|
|
762
|
+
const contentProtections = representationProtections[0] ? representationProtections : adaptationSetProtections;
|
|
763
|
+
if (contentProtections.length) {
|
|
763
764
|
const encryptInfo = new EncryptInfo();
|
|
764
765
|
encryptInfo.method = DashExtractor.#DEFAULT_METHOD;
|
|
766
|
+
const widevineSystemId = "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed";
|
|
767
|
+
const playreadySystemId = "9a04f079-9840-4286-ab92-e65be0885f95";
|
|
768
|
+
for (const contentProtection of contentProtections) {
|
|
769
|
+
const schemeIdUri = contentProtection.getAttribute("schemeIdUri");
|
|
770
|
+
const drmData = {
|
|
771
|
+
keyId: contentProtection.getAttribute("cenc:default_KID") || void 0,
|
|
772
|
+
pssh: contentProtection.getElementsByTagName("cenc:pssh")[0]?.textContent || void 0
|
|
773
|
+
};
|
|
774
|
+
if (schemeIdUri?.includes(widevineSystemId)) encryptInfo.drm.widevine = drmData;
|
|
775
|
+
else if (schemeIdUri?.includes(playreadySystemId)) encryptInfo.drm.playready = drmData;
|
|
776
|
+
else continue;
|
|
777
|
+
}
|
|
765
778
|
if (streamSpec.playlist.mediaInit) streamSpec.playlist.mediaInit.encryptInfo = encryptInfo;
|
|
766
779
|
const segments = streamSpec.playlist.mediaParts[0].mediaSegments;
|
|
767
780
|
for (const segment of segments) if (!segment.encryptInfo) segment.encryptInfo = encryptInfo;
|
|
768
781
|
}
|
|
769
782
|
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
|
-
}
|
|
783
|
+
if (_index > -1) if (isLive) {} else if (streamList[_index].playlist.mediaParts.at(-1).mediaSegments.at(-1).url !== streamSpec.playlist.mediaParts[0].mediaSegments.at(-1)?.url) {
|
|
784
|
+
const startIndex = streamList[_index].playlist.mediaParts.at(-1).mediaSegments.at(-1).index + 1;
|
|
785
|
+
const segments = streamSpec.playlist.mediaParts[0].mediaSegments;
|
|
786
|
+
for (const segment of segments) segment.index += startIndex;
|
|
787
|
+
const mediaPart = new MediaPart();
|
|
788
|
+
mediaPart.mediaSegments = streamList[_index].playlist.mediaParts[0].mediaSegments;
|
|
789
|
+
streamList[_index].playlist.mediaParts.push(mediaPart);
|
|
790
|
+
} else streamList[_index].playlist.mediaParts.at(-1).mediaSegments.at(-1).duration += streamSpec.playlist.mediaParts[0].mediaSegments.reduce((sum, segment) => sum + segment.duration, 0);
|
|
782
791
|
else {
|
|
783
792
|
if (streamSpec.mediaType === MEDIA_TYPES.SUBTITLES && streamSpec.extension === "mp4") streamSpec.extension = "m4s";
|
|
784
793
|
if (streamSpec.mediaType !== MEDIA_TYPES.SUBTITLES && (streamSpec.extension == null || streamSpec.playlist.mediaParts.reduce((sum, part) => sum + part.mediaSegments.length, 0) > 1)) streamSpec.extension = "m4s";
|
|
@@ -793,8 +802,8 @@ var DashExtractor = class DashExtractor {
|
|
|
793
802
|
const subtitleList = streamList.filter((stream) => stream.mediaType === MEDIA_TYPES.SUBTITLES);
|
|
794
803
|
const videoList = streamList.filter((stream) => stream.mediaType === MEDIA_TYPES.VIDEO);
|
|
795
804
|
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;
|
|
805
|
+
const audioGroupId = audioList.toSorted((a, b$1) => (b$1.bandwidth || 0) - (a.bandwidth || 0)).at(0)?.groupId;
|
|
806
|
+
const subtitleGroupId = subtitleList.toSorted((a, b$1) => (b$1.bandwidth || 0) - (a.bandwidth || 0)).at(0)?.groupId;
|
|
798
807
|
if (audioGroupId) video.audioId = audioGroupId;
|
|
799
808
|
if (subtitleGroupId) video.subtitleId = subtitleGroupId;
|
|
800
809
|
}
|
|
@@ -804,17 +813,38 @@ var DashExtractor = class DashExtractor {
|
|
|
804
813
|
if (!v) return;
|
|
805
814
|
return v;
|
|
806
815
|
}
|
|
807
|
-
|
|
808
|
-
|
|
816
|
+
async refreshPlayList(streamSpecs) {
|
|
817
|
+
if (!streamSpecs.length) return;
|
|
818
|
+
const response = await fetch(this.#parserConfig.url, this.#parserConfig.headers).catch(() => fetch(this.#parserConfig.originalUrl, this.#parserConfig.headers));
|
|
819
|
+
const rawText = await response.text();
|
|
820
|
+
const url = response.url;
|
|
821
|
+
this.#parserConfig.url = url;
|
|
822
|
+
this.#setInitUrl();
|
|
823
|
+
const newStreams = await this.extractStreams(rawText);
|
|
824
|
+
for (const streamSpec of streamSpecs) {
|
|
825
|
+
let results = newStreams.filter((n) => n.toShortString() === streamSpec.toShortString());
|
|
826
|
+
if (!results.length) results = newStreams.filter((n) => n.playlist?.mediaInit?.url === streamSpec.playlist?.mediaInit?.url);
|
|
827
|
+
if (results.length) streamSpec.playlist.mediaParts = results.at(0).playlist.mediaParts;
|
|
828
|
+
}
|
|
829
|
+
await this.#processUrl(streamSpecs);
|
|
830
|
+
}
|
|
831
|
+
async #processUrl(streamSpecs) {
|
|
832
|
+
for (const spec of streamSpecs) {
|
|
833
|
+
const playlist = spec.playlist;
|
|
834
|
+
if (!playlist) continue;
|
|
835
|
+
if (playlist.mediaInit) playlist.mediaInit.url = this.preProcessUrl(playlist.mediaInit.url);
|
|
836
|
+
for (const part of playlist.mediaParts) for (const segment of part.mediaSegments) segment.url = this.preProcessUrl(segment.url);
|
|
837
|
+
}
|
|
809
838
|
}
|
|
810
|
-
|
|
811
|
-
|
|
839
|
+
async fetchPlayList(streamSpecs) {
|
|
840
|
+
this.#processUrl(streamSpecs);
|
|
812
841
|
}
|
|
813
842
|
preProcessUrl(url) {
|
|
814
|
-
|
|
843
|
+
for (const processor of this.#parserConfig.urlProcessors) if (processor.canProcess(this.extractorType, url, this.#parserConfig)) url = processor.process(url, this.#parserConfig);
|
|
844
|
+
return url;
|
|
815
845
|
}
|
|
816
846
|
preProcessContent() {
|
|
817
|
-
|
|
847
|
+
for (const processor of this.#parserConfig.contentProcessors) if (processor.canProcess(this.extractorType, this.#mpdContent, this.#parserConfig)) this.#mpdContent = processor.process(this.#mpdContent, this.#parserConfig);
|
|
818
848
|
}
|
|
819
849
|
};
|
|
820
850
|
|
|
@@ -893,8 +923,7 @@ var HlsExtractor = class {
|
|
|
893
923
|
expectPlaylist = true;
|
|
894
924
|
} else if (line.startsWith(HLS_TAGS.extXMedia)) {
|
|
895
925
|
streamSpec = new StreamSpec();
|
|
896
|
-
const
|
|
897
|
-
const mediaType = MEDIA_TYPES[type];
|
|
926
|
+
const mediaType = MEDIA_TYPES[getAttribute(line, "TYPE").replace("-", "_")];
|
|
898
927
|
if (mediaType) streamSpec.mediaType = mediaType;
|
|
899
928
|
if (mediaType === MEDIA_TYPES.CLOSED_CAPTIONS) continue;
|
|
900
929
|
let url = getAttribute(line, "URI");
|
|
@@ -945,8 +974,7 @@ var HlsExtractor = class {
|
|
|
945
974
|
for (const line of lines) {
|
|
946
975
|
if (!line.trim()) continue;
|
|
947
976
|
if (line.startsWith(HLS_TAGS.extXByterange)) {
|
|
948
|
-
const
|
|
949
|
-
const [n, o] = getRange(p);
|
|
977
|
+
const [n, o] = getRange(getAttribute(line));
|
|
950
978
|
segment.expectLength = n;
|
|
951
979
|
segment.startRange = o || (segments.at(-1)?.startRange || 0) + (segments.at(-1)?.expectLength || 0);
|
|
952
980
|
expectSegment = true;
|
|
@@ -968,9 +996,7 @@ var HlsExtractor = class {
|
|
|
968
996
|
mediaParts.push(new MediaPart(segments));
|
|
969
997
|
segments = [];
|
|
970
998
|
} else if (line.startsWith(HLS_TAGS.extXKey)) {
|
|
971
|
-
|
|
972
|
-
const uriLast = getAttribute(lastKeyLine, "URI");
|
|
973
|
-
if (uri !== uriLast) {
|
|
999
|
+
if (getAttribute(line, "URI") !== getAttribute(lastKeyLine, "URI")) {
|
|
974
1000
|
const parsedInfo = await this.#parseKey(line);
|
|
975
1001
|
currentEncryptInfo.method = parsedInfo.method;
|
|
976
1002
|
currentEncryptInfo.key = parsedInfo.key;
|
|
@@ -998,8 +1024,7 @@ var HlsExtractor = class {
|
|
|
998
1024
|
mediaSegment.index = -1;
|
|
999
1025
|
playlist.mediaInit = mediaSegment;
|
|
1000
1026
|
if (line.includes("BYTERANGE")) {
|
|
1001
|
-
const
|
|
1002
|
-
const [n, o] = getRange(p);
|
|
1027
|
+
const [n, o] = getRange(getAttribute(line, "BYTERANGE"));
|
|
1003
1028
|
mediaSegment.expectLength = n;
|
|
1004
1029
|
mediaSegment.startRange = o || 0;
|
|
1005
1030
|
}
|
|
@@ -1048,10 +1073,7 @@ var HlsExtractor = class {
|
|
|
1048
1073
|
async extractStreams(rawText) {
|
|
1049
1074
|
this.#m3u8Content = rawText;
|
|
1050
1075
|
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
|
-
}
|
|
1076
|
+
if (this.#m3u8Content.includes(HLS_TAGS.extXStreamInf)) return this.#parseMasterList().then((lists) => distinctBy(lists, (list) => list.url));
|
|
1055
1077
|
const playlist = await this.#parseList();
|
|
1056
1078
|
const streamSpec = new StreamSpec();
|
|
1057
1079
|
streamSpec.url = this.parserConfig.url;
|
|
@@ -1061,8 +1083,7 @@ var HlsExtractor = class {
|
|
|
1061
1083
|
}
|
|
1062
1084
|
async #loadM3u8FromUrl(url) {
|
|
1063
1085
|
if (url.startsWith("file:")) {
|
|
1064
|
-
const
|
|
1065
|
-
const filePath = uri.pathname;
|
|
1086
|
+
const filePath = new URL(url).pathname;
|
|
1066
1087
|
this.#m3u8Content = await (0, node_fs_promises.readFile)(filePath, "utf8");
|
|
1067
1088
|
} else if (url.startsWith("http")) try {
|
|
1068
1089
|
const response = await fetch(url, { headers: this.parserConfig.headers });
|
|
@@ -1102,9 +1123,9 @@ var HlsExtractor = class {
|
|
|
1102
1123
|
else list.playlist = newPlaylist;
|
|
1103
1124
|
if (list.mediaType === MEDIA_TYPES.SUBTITLES) {
|
|
1104
1125
|
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")));
|
|
1126
|
+
const b$1 = list.playlist.mediaParts.some((part) => part.mediaSegments.some((segment) => segment.url.includes(".vtt") || segment.url.includes(".webvtt")));
|
|
1106
1127
|
if (a) list.extension = "ttml";
|
|
1107
|
-
if (b) list.extension = "vtt";
|
|
1128
|
+
if (b$1) list.extension = "vtt";
|
|
1108
1129
|
} else list.extension = list.playlist.mediaInit ? "m4s" : "ts";
|
|
1109
1130
|
}
|
|
1110
1131
|
}
|
|
@@ -1132,8 +1153,7 @@ var StreamExtractor = class {
|
|
|
1132
1153
|
}
|
|
1133
1154
|
async loadSourceFromUrl(url) {
|
|
1134
1155
|
if (url.startsWith("file:")) {
|
|
1135
|
-
const
|
|
1136
|
-
const filePath = uri.pathname;
|
|
1156
|
+
const filePath = new URL(url).pathname;
|
|
1137
1157
|
this.#rawText = await (0, node_fs_promises.readFile)(filePath, "utf8");
|
|
1138
1158
|
this.#setUrl(url);
|
|
1139
1159
|
} else if (url.startsWith("http")) {
|
|
@@ -1160,8 +1180,11 @@ var StreamExtractor = class {
|
|
|
1160
1180
|
} else if (this.#rawText.includes("</MPD>") && this.#rawText.includes("<MPD")) {
|
|
1161
1181
|
this.#extractor = new DashExtractor(this.#parserConfig);
|
|
1162
1182
|
rawType = "mpd";
|
|
1163
|
-
} else if (this.#rawText.includes("</SmoothStreamingMedia>") && this.#rawText.includes("<SmoothStreamingMedia"))
|
|
1164
|
-
|
|
1183
|
+
} else if (this.#rawText.includes("</SmoothStreamingMedia>") && this.#rawText.includes("<SmoothStreamingMedia")) {
|
|
1184
|
+
rawType = "ism";
|
|
1185
|
+
throw new Error("Smooth Streaming is not supported yet");
|
|
1186
|
+
} else if (rawText === "<RE_LIVE_TS>") throw new Error("Live TS is not supported yet");
|
|
1187
|
+
else throw new Error("Unsupported stream type");
|
|
1165
1188
|
this.#rawFiles[`raw.${rawType}`] = rawText;
|
|
1166
1189
|
}
|
|
1167
1190
|
async extractStreams() {
|
|
@@ -1176,13 +1199,10 @@ var StreamExtractor = class {
|
|
|
1176
1199
|
};
|
|
1177
1200
|
|
|
1178
1201
|
//#endregion
|
|
1179
|
-
exports.
|
|
1180
|
-
exports.
|
|
1181
|
-
exports.
|
|
1182
|
-
exports.
|
|
1183
|
-
exports.
|
|
1184
|
-
exports.
|
|
1185
|
-
exports.
|
|
1186
|
-
exports.ROLE_TYPE = ROLE_TYPE
|
|
1187
|
-
exports.StreamExtractor = StreamExtractor
|
|
1188
|
-
exports.UrlProcessor = UrlProcessor
|
|
1202
|
+
exports.DefaultUrlProcessor = DefaultUrlProcessor;
|
|
1203
|
+
exports.ENCRYPT_METHODS = ENCRYPT_METHODS;
|
|
1204
|
+
exports.EXTRACTOR_TYPES = EXTRACTOR_TYPES;
|
|
1205
|
+
exports.MEDIA_TYPES = MEDIA_TYPES;
|
|
1206
|
+
exports.ParserConfig = ParserConfig;
|
|
1207
|
+
exports.ROLE_TYPE = ROLE_TYPE;
|
|
1208
|
+
exports.StreamExtractor = StreamExtractor;
|
package/dist/dasha.d.cts
CHANGED
|
@@ -6,21 +6,19 @@ declare const MEDIA_TYPES: {
|
|
|
6
6
|
readonly CLOSED_CAPTIONS: "closed-captions";
|
|
7
7
|
};
|
|
8
8
|
type MediaType = (typeof MEDIA_TYPES)[keyof typeof MEDIA_TYPES];
|
|
9
|
-
|
|
10
9
|
//#endregion
|
|
11
10
|
//#region lib/shared/encrypt-method.d.ts
|
|
12
11
|
declare const ENCRYPT_METHODS: {
|
|
13
|
-
NONE:
|
|
14
|
-
AES_128:
|
|
15
|
-
AES_128_ECB:
|
|
16
|
-
SAMPLE_AES:
|
|
17
|
-
SAMPLE_AES_CTR:
|
|
18
|
-
CENC:
|
|
19
|
-
CHACHA20:
|
|
20
|
-
UNKNOWN:
|
|
12
|
+
readonly NONE: "none";
|
|
13
|
+
readonly AES_128: "aes-128";
|
|
14
|
+
readonly AES_128_ECB: "aes-128-ecb";
|
|
15
|
+
readonly SAMPLE_AES: "sample-aes";
|
|
16
|
+
readonly SAMPLE_AES_CTR: "sample-aes-ctr";
|
|
17
|
+
readonly CENC: "cenc";
|
|
18
|
+
readonly CHACHA20: "chacha20";
|
|
19
|
+
readonly UNKNOWN: "unknown";
|
|
21
20
|
};
|
|
22
21
|
type EncryptMethod = (typeof ENCRYPT_METHODS)[keyof typeof ENCRYPT_METHODS];
|
|
23
|
-
|
|
24
22
|
//#endregion
|
|
25
23
|
//#region lib/shared/extractor-type.d.ts
|
|
26
24
|
declare const EXTRACTOR_TYPES: {
|
|
@@ -30,7 +28,6 @@ declare const EXTRACTOR_TYPES: {
|
|
|
30
28
|
readonly MSS: "MSS";
|
|
31
29
|
};
|
|
32
30
|
type ExtractorType = (typeof EXTRACTOR_TYPES)[keyof typeof EXTRACTOR_TYPES];
|
|
33
|
-
|
|
34
31
|
//#endregion
|
|
35
32
|
//#region lib/shared/role-type.d.ts
|
|
36
33
|
declare const ROLE_TYPE: {
|
|
@@ -46,36 +43,38 @@ declare const ROLE_TYPE: {
|
|
|
46
43
|
ForcedSubtitle: number;
|
|
47
44
|
};
|
|
48
45
|
type RoleType = (typeof ROLE_TYPE)[keyof typeof ROLE_TYPE];
|
|
49
|
-
|
|
50
46
|
//#endregion
|
|
51
47
|
//#region lib/shared/encrypt-info.d.ts
|
|
48
|
+
type DrmType = 'widevine' | 'playready' | 'fairplay';
|
|
52
49
|
declare class EncryptInfo {
|
|
53
50
|
method: EncryptMethod;
|
|
54
|
-
key?:
|
|
55
|
-
iv?:
|
|
51
|
+
key?: Uint8Array;
|
|
52
|
+
iv?: Uint8Array;
|
|
53
|
+
drm: { [key in DrmType]?: {
|
|
54
|
+
keyId?: string;
|
|
55
|
+
pssh?: string;
|
|
56
|
+
} };
|
|
56
57
|
constructor(method?: string | null);
|
|
57
58
|
parseMethod(method?: string | null): EncryptMethod;
|
|
58
59
|
}
|
|
59
|
-
|
|
60
60
|
//#endregion
|
|
61
61
|
//#region lib/processor.d.ts
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
interface ContentProcessor {
|
|
63
|
+
canProcess(extractorType: ExtractorType, rawText: string, parserConfig: ParserConfig): boolean;
|
|
64
|
+
process(rawText: string, parserConfig: ParserConfig): string;
|
|
65
65
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
interface KeyProcessor {
|
|
67
|
+
canProcess(extractorType: ExtractorType, keyLine: string, m3u8Url: string, m3u8Content: string, parserConfig: ParserConfig): boolean;
|
|
68
|
+
process(keyLine: string, m3u8Url: string, m3u8Content: string, parserConfig: ParserConfig): Promise<EncryptInfo>;
|
|
69
69
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
interface UrlProcessor {
|
|
71
|
+
canProcess(extractorType: ExtractorType, originalUrl: string, parserConfig: ParserConfig): boolean;
|
|
72
|
+
process(originalUrl: string, parserConfig: ParserConfig): string;
|
|
73
73
|
}
|
|
74
|
-
declare class DefaultUrlProcessor
|
|
74
|
+
declare class DefaultUrlProcessor implements UrlProcessor {
|
|
75
75
|
canProcess(_extractorType: ExtractorType, _originalUrl: string, parserConfig: ParserConfig): boolean;
|
|
76
76
|
process(url: string, parserConfig: ParserConfig): string;
|
|
77
77
|
}
|
|
78
|
-
|
|
79
78
|
//#endregion
|
|
80
79
|
//#region lib/parser-config.d.ts
|
|
81
80
|
declare class ParserConfig {
|
|
@@ -88,13 +87,12 @@ declare class ParserConfig {
|
|
|
88
87
|
urlProcessors: UrlProcessor[];
|
|
89
88
|
keyProcessors: KeyProcessor[];
|
|
90
89
|
customMethod?: EncryptMethod;
|
|
91
|
-
customKey?:
|
|
92
|
-
customIv?:
|
|
90
|
+
customKey?: Uint8Array;
|
|
91
|
+
customIv?: Uint8Array;
|
|
93
92
|
urlProcessorArgs?: string;
|
|
94
93
|
appendUrlParams: boolean;
|
|
95
94
|
keyRetryCount: number;
|
|
96
95
|
}
|
|
97
|
-
|
|
98
96
|
//#endregion
|
|
99
97
|
//#region lib/shared/media-segment.d.ts
|
|
100
98
|
declare class MediaSegment {
|
|
@@ -112,14 +110,12 @@ declare class MediaSegment {
|
|
|
112
110
|
equals(segment: unknown): boolean;
|
|
113
111
|
getHashCode(): string;
|
|
114
112
|
}
|
|
115
|
-
|
|
116
113
|
//#endregion
|
|
117
114
|
//#region lib/shared/media-part.d.ts
|
|
118
115
|
declare class MediaPart {
|
|
119
116
|
mediaSegments: MediaSegment[];
|
|
120
117
|
constructor(segments?: MediaSegment[]);
|
|
121
118
|
}
|
|
122
|
-
|
|
123
119
|
//#endregion
|
|
124
120
|
//#region lib/shared/playlist.d.ts
|
|
125
121
|
declare class Playlist {
|
|
@@ -131,7 +127,6 @@ declare class Playlist {
|
|
|
131
127
|
mediaInit?: MediaSegment;
|
|
132
128
|
mediaParts: MediaPart[];
|
|
133
129
|
}
|
|
134
|
-
|
|
135
130
|
//#endregion
|
|
136
131
|
//#region lib/shared/stream-spec.d.ts
|
|
137
132
|
declare class StreamSpec {
|
|
@@ -161,7 +156,6 @@ declare class StreamSpec {
|
|
|
161
156
|
get segmentsCount(): number;
|
|
162
157
|
toShortString(): string;
|
|
163
158
|
}
|
|
164
|
-
|
|
165
159
|
//#endregion
|
|
166
160
|
//#region lib/stream-extractor.d.ts
|
|
167
161
|
declare class StreamExtractor {
|
|
@@ -174,6 +168,5 @@ declare class StreamExtractor {
|
|
|
174
168
|
fetchPlayList(streamSpecs: StreamSpec[]): Promise<void>;
|
|
175
169
|
refreshPlayList(streamSpecs: StreamSpec[]): Promise<void>;
|
|
176
170
|
}
|
|
177
|
-
|
|
178
171
|
//#endregion
|
|
179
172
|
export { ContentProcessor, DefaultUrlProcessor, ENCRYPT_METHODS, EXTRACTOR_TYPES, EncryptMethod, ExtractorType, KeyProcessor, MEDIA_TYPES, MediaType, ParserConfig, ROLE_TYPE, RoleType, StreamExtractor, UrlProcessor };
|
package/dist/dasha.d.ts
CHANGED
|
@@ -6,21 +6,19 @@ declare const MEDIA_TYPES: {
|
|
|
6
6
|
readonly CLOSED_CAPTIONS: "closed-captions";
|
|
7
7
|
};
|
|
8
8
|
type MediaType = (typeof MEDIA_TYPES)[keyof typeof MEDIA_TYPES];
|
|
9
|
-
|
|
10
9
|
//#endregion
|
|
11
10
|
//#region lib/shared/encrypt-method.d.ts
|
|
12
11
|
declare const ENCRYPT_METHODS: {
|
|
13
|
-
NONE:
|
|
14
|
-
AES_128:
|
|
15
|
-
AES_128_ECB:
|
|
16
|
-
SAMPLE_AES:
|
|
17
|
-
SAMPLE_AES_CTR:
|
|
18
|
-
CENC:
|
|
19
|
-
CHACHA20:
|
|
20
|
-
UNKNOWN:
|
|
12
|
+
readonly NONE: "none";
|
|
13
|
+
readonly AES_128: "aes-128";
|
|
14
|
+
readonly AES_128_ECB: "aes-128-ecb";
|
|
15
|
+
readonly SAMPLE_AES: "sample-aes";
|
|
16
|
+
readonly SAMPLE_AES_CTR: "sample-aes-ctr";
|
|
17
|
+
readonly CENC: "cenc";
|
|
18
|
+
readonly CHACHA20: "chacha20";
|
|
19
|
+
readonly UNKNOWN: "unknown";
|
|
21
20
|
};
|
|
22
21
|
type EncryptMethod = (typeof ENCRYPT_METHODS)[keyof typeof ENCRYPT_METHODS];
|
|
23
|
-
|
|
24
22
|
//#endregion
|
|
25
23
|
//#region lib/shared/extractor-type.d.ts
|
|
26
24
|
declare const EXTRACTOR_TYPES: {
|
|
@@ -30,7 +28,6 @@ declare const EXTRACTOR_TYPES: {
|
|
|
30
28
|
readonly MSS: "MSS";
|
|
31
29
|
};
|
|
32
30
|
type ExtractorType = (typeof EXTRACTOR_TYPES)[keyof typeof EXTRACTOR_TYPES];
|
|
33
|
-
|
|
34
31
|
//#endregion
|
|
35
32
|
//#region lib/shared/role-type.d.ts
|
|
36
33
|
declare const ROLE_TYPE: {
|
|
@@ -46,36 +43,38 @@ declare const ROLE_TYPE: {
|
|
|
46
43
|
ForcedSubtitle: number;
|
|
47
44
|
};
|
|
48
45
|
type RoleType = (typeof ROLE_TYPE)[keyof typeof ROLE_TYPE];
|
|
49
|
-
|
|
50
46
|
//#endregion
|
|
51
47
|
//#region lib/shared/encrypt-info.d.ts
|
|
48
|
+
type DrmType = 'widevine' | 'playready' | 'fairplay';
|
|
52
49
|
declare class EncryptInfo {
|
|
53
50
|
method: EncryptMethod;
|
|
54
|
-
key?:
|
|
55
|
-
iv?:
|
|
51
|
+
key?: Uint8Array;
|
|
52
|
+
iv?: Uint8Array;
|
|
53
|
+
drm: { [key in DrmType]?: {
|
|
54
|
+
keyId?: string;
|
|
55
|
+
pssh?: string;
|
|
56
|
+
} };
|
|
56
57
|
constructor(method?: string | null);
|
|
57
58
|
parseMethod(method?: string | null): EncryptMethod;
|
|
58
59
|
}
|
|
59
|
-
|
|
60
60
|
//#endregion
|
|
61
61
|
//#region lib/processor.d.ts
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
interface ContentProcessor {
|
|
63
|
+
canProcess(extractorType: ExtractorType, rawText: string, parserConfig: ParserConfig): boolean;
|
|
64
|
+
process(rawText: string, parserConfig: ParserConfig): string;
|
|
65
65
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
interface KeyProcessor {
|
|
67
|
+
canProcess(extractorType: ExtractorType, keyLine: string, m3u8Url: string, m3u8Content: string, parserConfig: ParserConfig): boolean;
|
|
68
|
+
process(keyLine: string, m3u8Url: string, m3u8Content: string, parserConfig: ParserConfig): Promise<EncryptInfo>;
|
|
69
69
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
interface UrlProcessor {
|
|
71
|
+
canProcess(extractorType: ExtractorType, originalUrl: string, parserConfig: ParserConfig): boolean;
|
|
72
|
+
process(originalUrl: string, parserConfig: ParserConfig): string;
|
|
73
73
|
}
|
|
74
|
-
declare class DefaultUrlProcessor
|
|
74
|
+
declare class DefaultUrlProcessor implements UrlProcessor {
|
|
75
75
|
canProcess(_extractorType: ExtractorType, _originalUrl: string, parserConfig: ParserConfig): boolean;
|
|
76
76
|
process(url: string, parserConfig: ParserConfig): string;
|
|
77
77
|
}
|
|
78
|
-
|
|
79
78
|
//#endregion
|
|
80
79
|
//#region lib/parser-config.d.ts
|
|
81
80
|
declare class ParserConfig {
|
|
@@ -88,13 +87,12 @@ declare class ParserConfig {
|
|
|
88
87
|
urlProcessors: UrlProcessor[];
|
|
89
88
|
keyProcessors: KeyProcessor[];
|
|
90
89
|
customMethod?: EncryptMethod;
|
|
91
|
-
customKey?:
|
|
92
|
-
customIv?:
|
|
90
|
+
customKey?: Uint8Array;
|
|
91
|
+
customIv?: Uint8Array;
|
|
93
92
|
urlProcessorArgs?: string;
|
|
94
93
|
appendUrlParams: boolean;
|
|
95
94
|
keyRetryCount: number;
|
|
96
95
|
}
|
|
97
|
-
|
|
98
96
|
//#endregion
|
|
99
97
|
//#region lib/shared/media-segment.d.ts
|
|
100
98
|
declare class MediaSegment {
|
|
@@ -112,14 +110,12 @@ declare class MediaSegment {
|
|
|
112
110
|
equals(segment: unknown): boolean;
|
|
113
111
|
getHashCode(): string;
|
|
114
112
|
}
|
|
115
|
-
|
|
116
113
|
//#endregion
|
|
117
114
|
//#region lib/shared/media-part.d.ts
|
|
118
115
|
declare class MediaPart {
|
|
119
116
|
mediaSegments: MediaSegment[];
|
|
120
117
|
constructor(segments?: MediaSegment[]);
|
|
121
118
|
}
|
|
122
|
-
|
|
123
119
|
//#endregion
|
|
124
120
|
//#region lib/shared/playlist.d.ts
|
|
125
121
|
declare class Playlist {
|
|
@@ -131,7 +127,6 @@ declare class Playlist {
|
|
|
131
127
|
mediaInit?: MediaSegment;
|
|
132
128
|
mediaParts: MediaPart[];
|
|
133
129
|
}
|
|
134
|
-
|
|
135
130
|
//#endregion
|
|
136
131
|
//#region lib/shared/stream-spec.d.ts
|
|
137
132
|
declare class StreamSpec {
|
|
@@ -161,7 +156,6 @@ declare class StreamSpec {
|
|
|
161
156
|
get segmentsCount(): number;
|
|
162
157
|
toShortString(): string;
|
|
163
158
|
}
|
|
164
|
-
|
|
165
159
|
//#endregion
|
|
166
160
|
//#region lib/stream-extractor.d.ts
|
|
167
161
|
declare class StreamExtractor {
|
|
@@ -174,6 +168,5 @@ declare class StreamExtractor {
|
|
|
174
168
|
fetchPlayList(streamSpecs: StreamSpec[]): Promise<void>;
|
|
175
169
|
refreshPlayList(streamSpecs: StreamSpec[]): Promise<void>;
|
|
176
170
|
}
|
|
177
|
-
|
|
178
171
|
//#endregion
|
|
179
172
|
export { ContentProcessor, DefaultUrlProcessor, ENCRYPT_METHODS, EXTRACTOR_TYPES, EncryptMethod, ExtractorType, KeyProcessor, MEDIA_TYPES, MediaType, ParserConfig, ROLE_TYPE, RoleType, StreamExtractor, UrlProcessor };
|
package/dist/dasha.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { b } from "barsic";
|
|
2
3
|
import { readFile } from "node:fs/promises";
|
|
3
4
|
import { pathToFileURL } from "node:url";
|
|
4
5
|
import path from "node:path";
|
|
@@ -17,14 +18,14 @@ const MEDIA_TYPES = {
|
|
|
17
18
|
//#endregion
|
|
18
19
|
//#region lib/shared/encrypt-method.ts
|
|
19
20
|
const ENCRYPT_METHODS = {
|
|
20
|
-
NONE:
|
|
21
|
-
AES_128:
|
|
22
|
-
AES_128_ECB:
|
|
23
|
-
SAMPLE_AES:
|
|
24
|
-
SAMPLE_AES_CTR:
|
|
25
|
-
CENC:
|
|
26
|
-
CHACHA20:
|
|
27
|
-
UNKNOWN:
|
|
21
|
+
NONE: "none",
|
|
22
|
+
AES_128: "aes-128",
|
|
23
|
+
AES_128_ECB: "aes-128-ecb",
|
|
24
|
+
SAMPLE_AES: "sample-aes",
|
|
25
|
+
SAMPLE_AES_CTR: "sample-aes-ctr",
|
|
26
|
+
CENC: "cenc",
|
|
27
|
+
CHACHA20: "chacha20",
|
|
28
|
+
UNKNOWN: "unknown"
|
|
28
29
|
};
|
|
29
30
|
|
|
30
31
|
//#endregion
|
|
@@ -53,17 +54,13 @@ const ROLE_TYPE = {
|
|
|
53
54
|
|
|
54
55
|
//#endregion
|
|
55
56
|
//#region lib/processor.ts
|
|
56
|
-
var
|
|
57
|
-
var KeyProcessor = class {};
|
|
58
|
-
var UrlProcessor = class {};
|
|
59
|
-
var DefaultUrlProcessor = class extends UrlProcessor {
|
|
57
|
+
var DefaultUrlProcessor = class {
|
|
60
58
|
canProcess(_extractorType, _originalUrl, parserConfig) {
|
|
61
59
|
return parserConfig.appendUrlParams;
|
|
62
60
|
}
|
|
63
61
|
process(url, parserConfig) {
|
|
64
62
|
if (!url.startsWith("http")) return url;
|
|
65
|
-
const
|
|
66
|
-
const urlFromConfigQuery = urlFromConfig.searchParams;
|
|
63
|
+
const urlFromConfigQuery = new URL(parserConfig.url).searchParams;
|
|
67
64
|
const oldUrl = new URL(url);
|
|
68
65
|
const newQuery = oldUrl.searchParams;
|
|
69
66
|
for (const [key, value] of urlFromConfigQuery) if (newQuery.has(key)) newQuery.set(key, value);
|
|
@@ -78,7 +75,7 @@ var DefaultUrlProcessor = class extends UrlProcessor {
|
|
|
78
75
|
|
|
79
76
|
//#endregion
|
|
80
77
|
//#region lib/dash/dash-content-processor.ts
|
|
81
|
-
var DefaultDashContentProcessor = class
|
|
78
|
+
var DefaultDashContentProcessor = class {
|
|
82
79
|
canProcess(extractorType, mpdContent) {
|
|
83
80
|
if (extractorType !== EXTRACTOR_TYPES.MPEG_DASH) return false;
|
|
84
81
|
return mpdContent.includes("<mas:") && !mpdContent.includes("xmlns:mas");
|
|
@@ -123,7 +120,7 @@ const HLS_TAGS = {
|
|
|
123
120
|
|
|
124
121
|
//#endregion
|
|
125
122
|
//#region lib/hls/hls-content-processor.ts
|
|
126
|
-
var DefaultHlsContentProcessor = class DefaultHlsContentProcessor
|
|
123
|
+
var DefaultHlsContentProcessor = class DefaultHlsContentProcessor {
|
|
127
124
|
static YkDVRegex = /#EXT-X-DISCONTINUITY\s+#EXT-X-MAP:URI="(.*?)",BYTERANGE="(.*?)"/g;
|
|
128
125
|
static DNSPRegex = /#EXT-X-MAP:URI=".*?BUMPER\/[\s\S]+?#EXT-X-DISCONTINUITY/;
|
|
129
126
|
static DNSPSubRegex = /#EXTINF:.*?,\s+.*BUMPER.*\s+#EXT-X-DISCONTINUITY/;
|
|
@@ -162,8 +159,10 @@ var EncryptInfo = class {
|
|
|
162
159
|
method = ENCRYPT_METHODS.NONE;
|
|
163
160
|
key;
|
|
164
161
|
iv;
|
|
162
|
+
drm;
|
|
165
163
|
constructor(method) {
|
|
166
164
|
this.method = this.parseMethod(method);
|
|
165
|
+
this.drm = {};
|
|
167
166
|
}
|
|
168
167
|
parseMethod(method) {
|
|
169
168
|
if (method) return ENCRYPT_METHODS[method.replace("-", "_")];
|
|
@@ -173,7 +172,7 @@ var EncryptInfo = class {
|
|
|
173
172
|
|
|
174
173
|
//#endregion
|
|
175
174
|
//#region lib/hls/hls-key-processor.ts
|
|
176
|
-
var DefaultHlsKeyProcessor = class
|
|
175
|
+
var DefaultHlsKeyProcessor = class {
|
|
177
176
|
canProcess(extractorType) {
|
|
178
177
|
return extractorType === EXTRACTOR_TYPES.HLS;
|
|
179
178
|
}
|
|
@@ -181,17 +180,16 @@ var DefaultHlsKeyProcessor = class extends KeyProcessor {
|
|
|
181
180
|
const iv = this.getAttribute(keyLine, "IV");
|
|
182
181
|
const method = this.getAttribute(keyLine, "METHOD");
|
|
183
182
|
const uri = this.getAttribute(keyLine, "URI");
|
|
184
|
-
console.debug(`METHOD:${method}, URI:${uri}, IV:${iv}`);
|
|
185
183
|
const encryptInfo = new EncryptInfo(method);
|
|
186
|
-
if (iv) encryptInfo.iv =
|
|
184
|
+
if (iv) encryptInfo.iv = b.hex().encode(iv);
|
|
187
185
|
if (parserConfig.customIv && parserConfig.customIv.length > 0) encryptInfo.iv = parserConfig.customIv;
|
|
188
186
|
try {
|
|
189
187
|
if (parserConfig.customKey && parserConfig.customKey.length > 0) encryptInfo.key = parserConfig.customKey;
|
|
190
188
|
else if (uri) {
|
|
191
189
|
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 =
|
|
190
|
+
if (lowerUri.startsWith("base64:")) encryptInfo.key = b.base64().encode(uri.slice(7));
|
|
191
|
+
else if (lowerUri.startsWith("data:;base64,")) encryptInfo.key = b.base64().encode(uri.slice(13));
|
|
192
|
+
else if (lowerUri.startsWith("data:text/plain;base64,")) encryptInfo.key = b.base64().encode(uri.slice(23));
|
|
195
193
|
else if (existsSync(uri)) encryptInfo.key = readFileSync(uri);
|
|
196
194
|
else {
|
|
197
195
|
const processedUrl = this.preProcessUrl(new URL(uri, m3u8Url).toString(), parserConfig);
|
|
@@ -209,15 +207,15 @@ var DefaultHlsKeyProcessor = class extends KeyProcessor {
|
|
|
209
207
|
return encryptInfo;
|
|
210
208
|
}
|
|
211
209
|
getAttribute(line, attrName) {
|
|
212
|
-
const regex = new RegExp(`${attrName}="([^"]+)"`, "i");
|
|
210
|
+
const regex = new RegExp(`${attrName}=(?:"([^"]+)"|([^,]+))`, "i");
|
|
213
211
|
const match = line.match(regex);
|
|
214
|
-
return match?.[1] ?? null;
|
|
212
|
+
return match?.[1] ?? match?.[2] ?? null;
|
|
215
213
|
}
|
|
216
214
|
async fetchKeyWithRetry(url, parserConfig) {
|
|
217
215
|
let retryCount = parserConfig.keyRetryCount ?? 3;
|
|
218
216
|
while (retryCount >= 0) try {
|
|
219
217
|
const response = await fetch(url, { headers: parserConfig.headers });
|
|
220
|
-
return
|
|
218
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
221
219
|
} catch (error) {
|
|
222
220
|
if (error.message.includes("scheme is not supported")) throw error;
|
|
223
221
|
console.warn(`Error fetching key: ${error.message}. Retries left: ${retryCount}`);
|
|
@@ -338,8 +336,7 @@ const DASH_TAGS = {
|
|
|
338
336
|
const combineUrl = (baseUrl, relativeUrl) => {
|
|
339
337
|
if (!baseUrl.trim()) return relativeUrl;
|
|
340
338
|
const url1 = new URL(baseUrl);
|
|
341
|
-
|
|
342
|
-
return url2.toString();
|
|
339
|
+
return new URL(relativeUrl, url1).toString();
|
|
343
340
|
};
|
|
344
341
|
const replaceVars = (text, dict) => {
|
|
345
342
|
let result = text;
|
|
@@ -378,7 +375,7 @@ const getAttribute = (line, key = "") => {
|
|
|
378
375
|
return result;
|
|
379
376
|
};
|
|
380
377
|
const distinctBy = (array, callbackfn) => {
|
|
381
|
-
const seen = new Set();
|
|
378
|
+
const seen = /* @__PURE__ */ new Set();
|
|
382
379
|
return array.filter((item) => {
|
|
383
380
|
const value = callbackfn(item);
|
|
384
381
|
if (seen.has(value)) return false;
|
|
@@ -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);
|
|
799
|
+
}
|
|
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
|
+
}
|
|
785
807
|
}
|
|
786
|
-
|
|
787
|
-
|
|
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 { DefaultUrlProcessor, ENCRYPT_METHODS, EXTRACTOR_TYPES, MEDIA_TYPES, ParserConfig, ROLE_TYPE, StreamExtractor };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dasha",
|
|
3
|
-
"version": "4.0.0-alpha.
|
|
3
|
+
"version": "4.0.0-alpha.3",
|
|
4
4
|
"description": "Streaming manifest parser",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist"
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
},
|
|
31
31
|
"repository": {
|
|
32
32
|
"type": "git",
|
|
33
|
-
"url": "https://github.com/
|
|
33
|
+
"url": "https://github.com/azot-labs/dasha"
|
|
34
34
|
},
|
|
35
35
|
"keywords": [
|
|
36
36
|
"mpeg",
|
|
@@ -48,37 +48,34 @@
|
|
|
48
48
|
"license": "MIT",
|
|
49
49
|
"readmeFilename": "README.md",
|
|
50
50
|
"bugs": {
|
|
51
|
-
"url": "https://github.com/
|
|
51
|
+
"url": "https://github.com/azot-labs/dasha/issues",
|
|
52
52
|
"email": "vitalygashkov@vk.com"
|
|
53
53
|
},
|
|
54
54
|
"funding": [
|
|
55
55
|
{
|
|
56
56
|
"type": "individual",
|
|
57
|
-
"url": "https://
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
"type": "patreon",
|
|
61
|
-
"url": "https://www.patreon.com/vitalygashkov"
|
|
57
|
+
"url": "https://t.me/tribute/app?startapp=dqW2"
|
|
62
58
|
}
|
|
63
59
|
],
|
|
64
60
|
"engines": {
|
|
65
|
-
"node": "20
|
|
61
|
+
"node": ">=20"
|
|
66
62
|
},
|
|
67
63
|
"dependencies": {
|
|
68
64
|
"@xmldom/xmldom": "^0.9.8",
|
|
65
|
+
"barsic": "^0.2.0",
|
|
69
66
|
"temporal-polyfill": "^0.3.0"
|
|
70
67
|
},
|
|
71
68
|
"devDependencies": {
|
|
72
|
-
"@eslint/js": "^9.
|
|
73
|
-
"@types/node": "^
|
|
74
|
-
"eslint": "^9.
|
|
75
|
-
"eslint-config-prettier": "^10.1.
|
|
76
|
-
"eslint-plugin-prettier": "^5.
|
|
77
|
-
"globals": "^16.
|
|
78
|
-
"prettier": "^3.
|
|
79
|
-
"tsdown": "^0.
|
|
80
|
-
"typescript": "^5.
|
|
81
|
-
"typescript-eslint": "^8.
|
|
82
|
-
"vitest": "^
|
|
69
|
+
"@eslint/js": "^9.39.1",
|
|
70
|
+
"@types/node": "^24.10.0",
|
|
71
|
+
"eslint": "^9.39.1",
|
|
72
|
+
"eslint-config-prettier": "^10.1.8",
|
|
73
|
+
"eslint-plugin-prettier": "^5.5.4",
|
|
74
|
+
"globals": "^16.5.0",
|
|
75
|
+
"prettier": "^3.6.2",
|
|
76
|
+
"tsdown": "^0.15.12",
|
|
77
|
+
"typescript": "^5.9.3",
|
|
78
|
+
"typescript-eslint": "^8.46.3",
|
|
79
|
+
"vitest": "^4.0.6"
|
|
83
80
|
}
|
|
84
81
|
}
|