dasha 4.0.0-alpha.5 → 4.0.0-alpha.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dasha.cjs +597 -208
- package/dist/dasha.d.cts +139 -36
- package/dist/dasha.d.mts +139 -36
- package/dist/dasha.mjs +588 -207
- package/package.json +1 -1
package/dist/dasha.cjs
CHANGED
|
@@ -32,6 +32,65 @@ node_path = __toESM(node_path);
|
|
|
32
32
|
let temporal_polyfill = require("temporal-polyfill");
|
|
33
33
|
let __xmldom_xmldom = require("@xmldom/xmldom");
|
|
34
34
|
|
|
35
|
+
//#region lib/shared/codec.ts
|
|
36
|
+
/**
|
|
37
|
+
* List of known video codecs, ordered by encoding preference.
|
|
38
|
+
* @group Codecs
|
|
39
|
+
* @public
|
|
40
|
+
*/
|
|
41
|
+
const VIDEO_CODECS = [
|
|
42
|
+
"avc",
|
|
43
|
+
"hevc",
|
|
44
|
+
"vp8",
|
|
45
|
+
"vp9",
|
|
46
|
+
"av1",
|
|
47
|
+
"vc1"
|
|
48
|
+
];
|
|
49
|
+
/**
|
|
50
|
+
* List of known video dynamic ranges.
|
|
51
|
+
* @group Codecs
|
|
52
|
+
* @public
|
|
53
|
+
*/
|
|
54
|
+
const VIDEO_DYNAMIC_RANGES = [
|
|
55
|
+
"sdr",
|
|
56
|
+
"hlg",
|
|
57
|
+
"hdr10",
|
|
58
|
+
"hdr10+",
|
|
59
|
+
"dv"
|
|
60
|
+
];
|
|
61
|
+
/**
|
|
62
|
+
* List of known audio codecs, ordered by encoding preference.
|
|
63
|
+
* @group Codecs
|
|
64
|
+
* @public
|
|
65
|
+
*/
|
|
66
|
+
const AUDIO_CODECS = [
|
|
67
|
+
"aac",
|
|
68
|
+
"opus",
|
|
69
|
+
"mp3",
|
|
70
|
+
"vorbis",
|
|
71
|
+
"flac",
|
|
72
|
+
"alac",
|
|
73
|
+
"ac3",
|
|
74
|
+
"eac3",
|
|
75
|
+
"dts"
|
|
76
|
+
];
|
|
77
|
+
/**
|
|
78
|
+
* List of known subtitle codecs, ordered by encoding preference.
|
|
79
|
+
* @group Codecs
|
|
80
|
+
* @public
|
|
81
|
+
*/
|
|
82
|
+
const SUBTITLE_CODECS = [
|
|
83
|
+
"srt",
|
|
84
|
+
"vtt",
|
|
85
|
+
"ttml",
|
|
86
|
+
"dfxp",
|
|
87
|
+
"ssa",
|
|
88
|
+
"ass",
|
|
89
|
+
"stpp",
|
|
90
|
+
"wvtt"
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
//#endregion
|
|
35
94
|
//#region lib/shared/encrypt-method.ts
|
|
36
95
|
const ENCRYPT_METHODS = {
|
|
37
96
|
NONE: "none",
|
|
@@ -116,99 +175,166 @@ var MediaSegment = class MediaSegment {
|
|
|
116
175
|
};
|
|
117
176
|
|
|
118
177
|
//#endregion
|
|
119
|
-
//#region lib/shared/
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
178
|
+
//#region lib/shared/playlist.ts
|
|
179
|
+
var Playlist = class {
|
|
180
|
+
url = "";
|
|
181
|
+
isLive = false;
|
|
182
|
+
refreshIntervalMs = 15e3;
|
|
183
|
+
get totalDuration() {
|
|
184
|
+
let result = 0;
|
|
185
|
+
for (const part of this.mediaParts) for (const segment of part.mediaSegments) result += segment.duration;
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
targetDuration;
|
|
189
|
+
mediaInit;
|
|
190
|
+
mediaParts = [];
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
//#endregion
|
|
194
|
+
//#region lib/shared/role-type.ts
|
|
195
|
+
const ROLE_TYPE = {
|
|
196
|
+
Subtitle: 0,
|
|
197
|
+
Main: 1,
|
|
198
|
+
Alternate: 2,
|
|
199
|
+
Supplementary: 3,
|
|
200
|
+
Commentary: 4,
|
|
201
|
+
Dub: 5,
|
|
202
|
+
Description: 6,
|
|
203
|
+
Sign: 7,
|
|
204
|
+
Metadata: 8,
|
|
205
|
+
ForcedSubtitle: 9
|
|
125
206
|
};
|
|
126
207
|
|
|
127
208
|
//#endregion
|
|
128
|
-
//#region lib/shared/stream-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
209
|
+
//#region lib/shared/stream-info.ts
|
|
210
|
+
/**
|
|
211
|
+
* List of all stream types.
|
|
212
|
+
* @group Miscellaneous
|
|
213
|
+
* @public
|
|
214
|
+
*/
|
|
215
|
+
const ALL_STREAM_TYPES = [
|
|
216
|
+
"video",
|
|
217
|
+
"audio",
|
|
218
|
+
"subtitle"
|
|
219
|
+
];
|
|
220
|
+
var StreamInfo = class {
|
|
221
|
+
codec;
|
|
222
|
+
languageCode;
|
|
223
|
+
bitrate;
|
|
133
224
|
name;
|
|
225
|
+
url = "";
|
|
226
|
+
originalUrl = "";
|
|
227
|
+
playlist;
|
|
134
228
|
default;
|
|
135
229
|
skippedDuration;
|
|
136
|
-
bandwidth;
|
|
137
|
-
codecs = null;
|
|
138
|
-
resolution;
|
|
139
|
-
frameRate;
|
|
140
|
-
channels = null;
|
|
141
|
-
extension = null;
|
|
142
230
|
role;
|
|
143
231
|
videoRange;
|
|
144
232
|
characteristics;
|
|
145
233
|
publishTime;
|
|
234
|
+
groupId = null;
|
|
146
235
|
audioId;
|
|
147
236
|
videoId;
|
|
148
237
|
subtitleId;
|
|
149
238
|
periodId = null;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
239
|
+
extension = null;
|
|
240
|
+
/**
|
|
241
|
+
* @deprecated Use `codec`
|
|
242
|
+
*/
|
|
243
|
+
codecs = null;
|
|
244
|
+
/**
|
|
245
|
+
* @deprecated Use `numberOfChannels` in `AudioStreamInfo`
|
|
246
|
+
*/
|
|
247
|
+
channels = null;
|
|
248
|
+
/**
|
|
249
|
+
* @deprecated Use `width` and `height` in `VideoStreamInfo`
|
|
250
|
+
*/
|
|
251
|
+
resolution;
|
|
252
|
+
/**
|
|
253
|
+
* @deprecated Use `bitrate`
|
|
254
|
+
*/
|
|
255
|
+
bandwidth;
|
|
153
256
|
get segmentsCount() {
|
|
154
257
|
return this.playlist?.mediaParts.reduce((sum, part) => sum + part.mediaSegments.length, 0) ?? 0;
|
|
155
258
|
}
|
|
259
|
+
};
|
|
260
|
+
var VideoStreamInfo = class extends StreamInfo {
|
|
261
|
+
codec;
|
|
262
|
+
width;
|
|
263
|
+
height;
|
|
264
|
+
frameRate;
|
|
265
|
+
dynamicRange;
|
|
266
|
+
dolbyVisionProfile;
|
|
267
|
+
get type() {
|
|
268
|
+
return "video";
|
|
269
|
+
}
|
|
270
|
+
constructor(info) {
|
|
271
|
+
super();
|
|
272
|
+
this.codec = info?.codec;
|
|
273
|
+
}
|
|
156
274
|
toShortString() {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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();
|
|
275
|
+
const prefix = `Vid `;
|
|
276
|
+
const bitrate = this.bitrate ? `${this.bitrate / 1e3} Kbps` : "";
|
|
277
|
+
return `${prefix} | ${[
|
|
278
|
+
this.width ? `${this.width}x${this.height}` : "",
|
|
279
|
+
bitrate,
|
|
280
|
+
this.groupId,
|
|
281
|
+
this.frameRate,
|
|
282
|
+
this.codec ?? this.codecs,
|
|
283
|
+
this.videoRange,
|
|
284
|
+
this.role
|
|
285
|
+
].filter(Boolean).join(" | ")}`.trim();
|
|
196
286
|
}
|
|
197
287
|
};
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
288
|
+
var AudioStreamInfo = class extends StreamInfo {
|
|
289
|
+
codec;
|
|
290
|
+
numberOfChannels;
|
|
291
|
+
sampleRate;
|
|
292
|
+
atmos;
|
|
293
|
+
descriptive;
|
|
294
|
+
joc;
|
|
295
|
+
get type() {
|
|
296
|
+
return "audio";
|
|
297
|
+
}
|
|
298
|
+
constructor(info) {
|
|
299
|
+
super();
|
|
300
|
+
this.codec = info?.codec;
|
|
301
|
+
}
|
|
302
|
+
toShortString() {
|
|
303
|
+
const prefix = `Aud `;
|
|
304
|
+
const bitrate = this.bitrate ? `${this.bitrate / 1e3} Kbps` : "";
|
|
305
|
+
const channels = this.numberOfChannels || this.channels ? `${this.numberOfChannels ?? this.channels}CH` : "";
|
|
306
|
+
return `${prefix} | ${[
|
|
307
|
+
this.groupId,
|
|
308
|
+
bitrate,
|
|
309
|
+
this.name,
|
|
310
|
+
this.codec ?? this.codecs,
|
|
311
|
+
this.languageCode,
|
|
312
|
+
channels,
|
|
313
|
+
this.role
|
|
314
|
+
].filter(Boolean).join(" | ")}`.trim();
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
var SubtitleStreamInfo = class extends StreamInfo {
|
|
318
|
+
codec;
|
|
319
|
+
cc;
|
|
320
|
+
sdh;
|
|
321
|
+
forced;
|
|
322
|
+
get type() {
|
|
323
|
+
return "subtitle";
|
|
324
|
+
}
|
|
325
|
+
constructor(info) {
|
|
326
|
+
super();
|
|
327
|
+
this.codec = info?.codec;
|
|
328
|
+
}
|
|
329
|
+
toShortString() {
|
|
330
|
+
return `Sub | ${[
|
|
331
|
+
this.groupId,
|
|
332
|
+
this.languageCode,
|
|
333
|
+
this.name,
|
|
334
|
+
this.codecs,
|
|
335
|
+
this.role
|
|
336
|
+
].filter(Boolean).join(" | ")}`.trim();
|
|
337
|
+
}
|
|
212
338
|
};
|
|
213
339
|
|
|
214
340
|
//#endregion
|
|
@@ -453,22 +579,7 @@ const distinctBy = (array, callbackfn) => {
|
|
|
453
579
|
return true;
|
|
454
580
|
});
|
|
455
581
|
};
|
|
456
|
-
|
|
457
|
-
//#endregion
|
|
458
|
-
//#region lib/shared/playlist.ts
|
|
459
|
-
var Playlist = class {
|
|
460
|
-
url = "";
|
|
461
|
-
isLive = false;
|
|
462
|
-
refreshIntervalMs = 15e3;
|
|
463
|
-
get totalDuration() {
|
|
464
|
-
let result = 0;
|
|
465
|
-
for (const part of this.mediaParts) for (const segment of part.mediaSegments) result += segment.duration;
|
|
466
|
-
return result;
|
|
467
|
-
}
|
|
468
|
-
targetDuration;
|
|
469
|
-
mediaInit;
|
|
470
|
-
mediaParts = [];
|
|
471
|
-
};
|
|
582
|
+
const parseMimes = (codecs) => codecs.toLowerCase().split(",").map((codec) => codec.trim().split(".")[0]);
|
|
472
583
|
|
|
473
584
|
//#endregion
|
|
474
585
|
//#region lib/dash/dash-utils.ts
|
|
@@ -482,8 +593,251 @@ const parseRange = (range) => {
|
|
|
482
593
|
return [startRange, end - startRange + 1];
|
|
483
594
|
};
|
|
484
595
|
|
|
596
|
+
//#endregion
|
|
597
|
+
//#region lib/dash/dash-video.ts
|
|
598
|
+
const PRIMARIES = {
|
|
599
|
+
Unspecified: 0,
|
|
600
|
+
BT_709: 1,
|
|
601
|
+
BT_601_625: 5,
|
|
602
|
+
BT_601_525: 6,
|
|
603
|
+
BT_2020_and_2100: 9,
|
|
604
|
+
SMPTE_ST_2113_and_EG_4321: 12
|
|
605
|
+
};
|
|
606
|
+
const TRANSFER = {
|
|
607
|
+
Unspecified: 0,
|
|
608
|
+
BT_709: 1,
|
|
609
|
+
BT_601: 6,
|
|
610
|
+
BT_2020: 14,
|
|
611
|
+
BT_2100: 15,
|
|
612
|
+
BT_2100_PQ: 16,
|
|
613
|
+
BT_2100_HLG: 18
|
|
614
|
+
};
|
|
615
|
+
const MATRIX = {
|
|
616
|
+
RGB: 0,
|
|
617
|
+
YCbCr_BT_709: 1,
|
|
618
|
+
YCbCr_BT_601_625: 5,
|
|
619
|
+
YCbCr_BT_601_525: 6,
|
|
620
|
+
YCbCr_BT_2020_and_2100: 9,
|
|
621
|
+
ICtCp_BT_2100: 14
|
|
622
|
+
};
|
|
623
|
+
const parseVideoCodecFromMime = (mime) => {
|
|
624
|
+
const target = mime.toLowerCase().trim().split(".")[0];
|
|
625
|
+
const avc = [
|
|
626
|
+
"avc1",
|
|
627
|
+
"avc2",
|
|
628
|
+
"avc3",
|
|
629
|
+
"dva1",
|
|
630
|
+
"dvav"
|
|
631
|
+
];
|
|
632
|
+
const hevc = [
|
|
633
|
+
"hev1",
|
|
634
|
+
"hev2",
|
|
635
|
+
"hev3",
|
|
636
|
+
"hvc1",
|
|
637
|
+
"hvc2",
|
|
638
|
+
"hvc3",
|
|
639
|
+
"dvh1",
|
|
640
|
+
"dvhe",
|
|
641
|
+
"lhv1",
|
|
642
|
+
"lhe1"
|
|
643
|
+
];
|
|
644
|
+
const vc1 = ["vc-1"];
|
|
645
|
+
const vp8 = ["vp08", "vp8"];
|
|
646
|
+
const vp9 = ["vp09", "vp9"];
|
|
647
|
+
const av1 = ["av01"];
|
|
648
|
+
if (avc.includes(target)) return "avc";
|
|
649
|
+
if (hevc.includes(target)) return "hevc";
|
|
650
|
+
if (vc1.includes(target)) return "vc1";
|
|
651
|
+
if (vp8.includes(target)) return "vp8";
|
|
652
|
+
if (vp9.includes(target)) return "vp9";
|
|
653
|
+
if (av1.includes(target)) return "av1";
|
|
654
|
+
throw new Error(`The MIME ${mime} is not supported as video codec`);
|
|
655
|
+
};
|
|
656
|
+
const parseDynamicRangeFromCicp = (primaries, transfer, matrix) => {
|
|
657
|
+
if (transfer == 5) transfer = TRANSFER.BT_601;
|
|
658
|
+
if (primaries == PRIMARIES.Unspecified && transfer == TRANSFER.Unspecified && matrix == MATRIX.RGB) return "sdr";
|
|
659
|
+
else if ([PRIMARIES.BT_601_625, PRIMARIES.BT_601_525].includes(primaries)) return "sdr";
|
|
660
|
+
else if (TRANSFER.BT_2100_PQ === transfer) return "hdr10";
|
|
661
|
+
else if (TRANSFER.BT_2100_HLG === transfer) return "hlg";
|
|
662
|
+
else return "sdr";
|
|
663
|
+
};
|
|
664
|
+
const parseVideoCodec = (codecs) => {
|
|
665
|
+
for (const codec of codecs.toLowerCase().split(",")) {
|
|
666
|
+
const mime = codec.trim().split(".")[0];
|
|
667
|
+
try {
|
|
668
|
+
return parseVideoCodecFromMime(mime);
|
|
669
|
+
} catch (e) {
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
throw new Error(`No MIME types matched any supported Video Codecs in ${codecs}`);
|
|
674
|
+
};
|
|
675
|
+
const tryParseVideoCodec = (codecs) => {
|
|
676
|
+
try {
|
|
677
|
+
return parseVideoCodec(codecs);
|
|
678
|
+
} catch (e) {
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
const parseDynamicRange = (codecs, supplementalProps = [], essentialProps = []) => {
|
|
683
|
+
if ([
|
|
684
|
+
"dva1",
|
|
685
|
+
"dvav",
|
|
686
|
+
"dvhe",
|
|
687
|
+
"dvh1"
|
|
688
|
+
].some((value) => codecs.startsWith(value))) return "dv";
|
|
689
|
+
const primariesScheme = "urn:mpeg:mpegB:cicp:ColourPrimaries";
|
|
690
|
+
const transferScheme = "urn:mpeg:mpegB:cicp:TransferCharacteristics";
|
|
691
|
+
const matrixScheme = "urn:mpeg:mpegB:cicp:MatrixCoefficients";
|
|
692
|
+
const allProps = [...essentialProps, ...supplementalProps];
|
|
693
|
+
const getValues = (schemeIdUri) => allProps.filter((prop) => prop.schemeIdUri === schemeIdUri).map((prop) => parseInt(prop.value));
|
|
694
|
+
return parseDynamicRangeFromCicp(getValues(primariesScheme).reduce((acc, current) => acc + current, 0), getValues(transferScheme).reduce((acc, current) => acc + current, 0), getValues(matrixScheme).reduce((acc, current) => acc + current, 0));
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
//#endregion
|
|
698
|
+
//#region lib/dash/dash-subtitle.ts
|
|
699
|
+
const parseSubtitleCodecFromMime = (mime) => {
|
|
700
|
+
switch (mime.toLowerCase().trim().split(".")[0]) {
|
|
701
|
+
case "srt":
|
|
702
|
+
case "x-subrip": return "srt";
|
|
703
|
+
case "ssa": return "ssa";
|
|
704
|
+
case "ass": return "ass";
|
|
705
|
+
case "ttml": return "ttml";
|
|
706
|
+
case "vtt": return "vtt";
|
|
707
|
+
case "stpp": return "stpp";
|
|
708
|
+
case "wvtt": return "wvtt";
|
|
709
|
+
default: throw new Error(`The MIME ${mime} is not supported as subtitle codec`);
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
const parseSubtitleCodec = (codecs) => {
|
|
713
|
+
const mimes = parseMimes(codecs);
|
|
714
|
+
for (const mime of mimes) try {
|
|
715
|
+
return parseSubtitleCodecFromMime(mime);
|
|
716
|
+
} catch (e) {
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
throw new Error(`No MIME types matched any supported Subtitle Codecs in ${codecs}`);
|
|
720
|
+
};
|
|
721
|
+
const tryParseSubtitleCodec = (codecs) => {
|
|
722
|
+
try {
|
|
723
|
+
return parseSubtitleCodec(codecs);
|
|
724
|
+
} catch (e) {
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
const checkIsClosedCaption = (roles = []) => {
|
|
729
|
+
for (const role of roles) if (role.schemeIdUri === "urn:mpeg:dash:role:2011" && role.value === "caption") return true;
|
|
730
|
+
return false;
|
|
731
|
+
};
|
|
732
|
+
const checkIsSdh = (accessibilities = []) => {
|
|
733
|
+
for (const accessibility of accessibilities) {
|
|
734
|
+
const { schemeIdUri, value } = accessibility;
|
|
735
|
+
if (schemeIdUri === "urn:tva:metadata:cs:AudioPurposeCS:2007" && value === "2") return true;
|
|
736
|
+
}
|
|
737
|
+
return false;
|
|
738
|
+
};
|
|
739
|
+
|
|
740
|
+
//#endregion
|
|
741
|
+
//#region lib/dash/dash-audio.ts
|
|
742
|
+
const parseAudioCodecFromMime = (mime) => {
|
|
743
|
+
switch (mime.toLowerCase().trim().split(".")[0]) {
|
|
744
|
+
case "mp4a": return "aac";
|
|
745
|
+
case "ac-3": return "ac3";
|
|
746
|
+
case "ec-3": return "eac3";
|
|
747
|
+
case "opus": return "opus";
|
|
748
|
+
case "dtsc": return "dts";
|
|
749
|
+
case "alac": return "alac";
|
|
750
|
+
case "flac": return "flac";
|
|
751
|
+
default: throw new Error(`The MIME ${mime} is not supported as audio codec`);
|
|
752
|
+
}
|
|
753
|
+
};
|
|
754
|
+
const parseAudioCodec = (codecs) => {
|
|
755
|
+
const mimes = parseMimes(codecs);
|
|
756
|
+
for (const mime of mimes) try {
|
|
757
|
+
return parseAudioCodecFromMime(mime);
|
|
758
|
+
} catch (e) {
|
|
759
|
+
continue;
|
|
760
|
+
}
|
|
761
|
+
throw new Error(`No MIME types matched any supported Audio Codecs in ${codecs}`);
|
|
762
|
+
};
|
|
763
|
+
const tryParseAudioCodec = (codecs) => {
|
|
764
|
+
try {
|
|
765
|
+
return parseAudioCodec(codecs);
|
|
766
|
+
} catch (e) {
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
const getDolbyDigitalPlusComplexityIndex = (supplementalProps = []) => {
|
|
771
|
+
const targetScheme = "tag:dolby.com,2018:dash:EC3_ExtensionComplexityIndex:2018";
|
|
772
|
+
for (const prop of supplementalProps) if (prop.schemeIdUri === targetScheme) return parseInt(prop.value);
|
|
773
|
+
};
|
|
774
|
+
const checkIsDescriptive = (accessibilities = []) => {
|
|
775
|
+
for (const accessibility of accessibilities) {
|
|
776
|
+
const { schemeIdUri, value } = accessibility;
|
|
777
|
+
if (schemeIdUri == "urn:mpeg:dash:role:2011" && value === "descriptive" || schemeIdUri == "urn:tva:metadata:cs:AudioPurposeCS:2007" && value === "1") return true;
|
|
778
|
+
}
|
|
779
|
+
return false;
|
|
780
|
+
};
|
|
781
|
+
const parseChannels = (channels) => {
|
|
782
|
+
const isDigit = (char) => char >= "0" && char <= "9";
|
|
783
|
+
if (typeof channels === "string") {
|
|
784
|
+
if (channels.toUpperCase() == "A000") return 2;
|
|
785
|
+
else if (channels.toUpperCase() == "F801") return 5.1;
|
|
786
|
+
else if (isDigit(channels.replace("ch", "").replace(".", "")[0])) return parseFloat(channels.replace("ch", ""));
|
|
787
|
+
throw new Error(`Unsupported audio channels value, '${channels}'`);
|
|
788
|
+
}
|
|
789
|
+
return parseFloat(channels);
|
|
790
|
+
};
|
|
791
|
+
|
|
792
|
+
//#endregion
|
|
793
|
+
//#region lib/shared/pipe.ts
|
|
794
|
+
const pipe = (...fns) => (value) => fns.reduce((acc, fn) => fn(acc), value);
|
|
795
|
+
|
|
485
796
|
//#endregion
|
|
486
797
|
//#region lib/dash/dash-extractor.ts
|
|
798
|
+
const createMediaStreamInfo = (params) => {
|
|
799
|
+
const codecs = params.contentType === "text" && !params.mimeType?.includes("mp4") ? params.mimeType?.split("/")[1] : params.codecs;
|
|
800
|
+
if (!params.codecs && codecs) params.codecs = codecs;
|
|
801
|
+
if (params.codecs) {
|
|
802
|
+
const videoCodec = tryParseVideoCodec(params.codecs);
|
|
803
|
+
if (videoCodec) return new VideoStreamInfo({ codec: videoCodec });
|
|
804
|
+
const audioCodec = tryParseAudioCodec(params.codecs);
|
|
805
|
+
if (audioCodec) return new AudioStreamInfo({ codec: audioCodec });
|
|
806
|
+
const subtitleCodec = tryParseSubtitleCodec(params.codecs);
|
|
807
|
+
if (subtitleCodec) return new SubtitleStreamInfo({ codec: subtitleCodec });
|
|
808
|
+
} else {
|
|
809
|
+
const type = params.contentType || params.mimeType?.split("/")[0];
|
|
810
|
+
if (type === "video") return new VideoStreamInfo();
|
|
811
|
+
if (type === "audio") return new AudioStreamInfo();
|
|
812
|
+
if (type === "text") return new SubtitleStreamInfo();
|
|
813
|
+
}
|
|
814
|
+
throw new Error("Unable to determine the type of a track, cannot continue...");
|
|
815
|
+
};
|
|
816
|
+
const selectNonEmpty = (args) => {
|
|
817
|
+
for (const element of args.elements) {
|
|
818
|
+
const results = element.getElementsByTagName(args.tag);
|
|
819
|
+
if (results.length) return results;
|
|
820
|
+
}
|
|
821
|
+
};
|
|
822
|
+
const toSchemeValueArray = (elements) => {
|
|
823
|
+
const results = [];
|
|
824
|
+
if (!elements) return results;
|
|
825
|
+
for (const element of elements) {
|
|
826
|
+
const schemeIdUri = element.getAttribute("schemeIdUri");
|
|
827
|
+
const value = element.getAttribute("value");
|
|
828
|
+
results.push({
|
|
829
|
+
schemeIdUri,
|
|
830
|
+
value
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
return results;
|
|
834
|
+
};
|
|
835
|
+
const getTagAttrs = (tag, ...elements) => {
|
|
836
|
+
return pipe(selectNonEmpty, toSchemeValueArray)({
|
|
837
|
+
tag,
|
|
838
|
+
elements
|
|
839
|
+
});
|
|
840
|
+
};
|
|
487
841
|
var DashExtractor = class DashExtractor {
|
|
488
842
|
static #DEFAULT_METHOD = ENCRYPT_METHODS.CENC;
|
|
489
843
|
get extractorType() {
|
|
@@ -513,11 +867,10 @@ var DashExtractor = class DashExtractor {
|
|
|
513
867
|
return Number(d.toFixed(3));
|
|
514
868
|
}
|
|
515
869
|
async extractStreams(rawText) {
|
|
516
|
-
const
|
|
870
|
+
const streamInfos = [];
|
|
517
871
|
this.#mpdContent = rawText;
|
|
518
872
|
const mpdElement = new __xmldom_xmldom.DOMParser().parseFromString(this.#mpdContent, "text/xml").getElementsByTagName("MPD")[0];
|
|
519
873
|
const isLive = mpdElement.getAttribute("type") === "dynamic";
|
|
520
|
-
mpdElement.getAttribute("maxSegmentDuration");
|
|
521
874
|
const availabilityStartTime = mpdElement.getAttribute("availabilityStartTime");
|
|
522
875
|
const timeShiftBufferDepth = mpdElement.getAttribute("timeShiftBufferDepth") || "PT1M";
|
|
523
876
|
const publishTime = mpdElement.getAttribute("publishTime");
|
|
@@ -539,55 +892,70 @@ var DashExtractor = class DashExtractor {
|
|
|
539
892
|
for (const adaptationSet of adaptationSets) {
|
|
540
893
|
segBaseUrl = this.#extendBaseUrl(adaptationSet, segBaseUrl);
|
|
541
894
|
const representationsBaseUrl = segBaseUrl;
|
|
542
|
-
let
|
|
895
|
+
let contentType = adaptationSet.getAttribute("contentType");
|
|
896
|
+
let mimeType = adaptationSet.getAttribute("mimeType");
|
|
543
897
|
const frameRate = this.#getFrameRate(adaptationSet);
|
|
544
898
|
const representations = adaptationSet.getElementsByTagName("Representation");
|
|
545
899
|
for (const representation of representations) {
|
|
546
900
|
segBaseUrl = this.#extendBaseUrl(representation, segBaseUrl);
|
|
547
|
-
if (!
|
|
548
|
-
|
|
549
|
-
const
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
if (
|
|
565
|
-
|
|
566
|
-
|
|
901
|
+
if (!contentType) contentType = representation.getAttribute("contentType");
|
|
902
|
+
if (!mimeType) mimeType = representation.getAttribute("mimeType");
|
|
903
|
+
const codecs = representation.getAttribute("codecs") || adaptationSet.getAttribute("codecs");
|
|
904
|
+
const widthParameterString = representation.getAttribute("width");
|
|
905
|
+
const heightParameterString = representation.getAttribute("height");
|
|
906
|
+
const roles = getTagAttrs("Role", representation, adaptationSet);
|
|
907
|
+
const supplementalProps = getTagAttrs("SupplementalProperty", representation, adaptationSet);
|
|
908
|
+
const essentialProps = getTagAttrs("EssentialProperty", representation, adaptationSet);
|
|
909
|
+
const accessibilities = getTagAttrs("Accessibility", representation, adaptationSet);
|
|
910
|
+
const channelsString = getTagAttrs("AudioChannelConfiguration", adaptationSet, representation)[0]?.value;
|
|
911
|
+
const streamInfo = createMediaStreamInfo({
|
|
912
|
+
codecs,
|
|
913
|
+
contentType,
|
|
914
|
+
mimeType
|
|
915
|
+
});
|
|
916
|
+
const bitrate = Number(representation.getAttribute("bandwidth") ?? "");
|
|
917
|
+
streamInfo.languageCode = this.#filterLanguage(representation.getAttribute("lang") || adaptationSet.getAttribute("lang"));
|
|
918
|
+
if (streamInfo.type === "video") {
|
|
919
|
+
streamInfo.bitrate = bitrate;
|
|
920
|
+
streamInfo.width = Number(widthParameterString);
|
|
921
|
+
streamInfo.height = Number(heightParameterString);
|
|
922
|
+
streamInfo.frameRate = frameRate || this.#getFrameRate(representation);
|
|
923
|
+
if (supplementalProps && essentialProps) streamInfo.dynamicRange = parseDynamicRange(codecs, supplementalProps, essentialProps);
|
|
924
|
+
} else if (streamInfo.type === "audio") {
|
|
925
|
+
streamInfo.bitrate = bitrate;
|
|
926
|
+
if (accessibilities) streamInfo.descriptive = checkIsDescriptive(accessibilities);
|
|
927
|
+
if (supplementalProps) streamInfo.joc = getDolbyDigitalPlusComplexityIndex(supplementalProps);
|
|
928
|
+
if (channelsString) {
|
|
929
|
+
streamInfo.numberOfChannels = parseChannels(channelsString);
|
|
930
|
+
streamInfo.channels = channelsString;
|
|
931
|
+
}
|
|
932
|
+
} else if (streamInfo.type === "subtitle") {
|
|
933
|
+
if (roles) streamInfo.cc = checkIsClosedCaption(roles);
|
|
934
|
+
if (accessibilities) streamInfo.sdh = checkIsSdh(accessibilities);
|
|
935
|
+
}
|
|
936
|
+
streamInfo.url = this.#mpdUrl;
|
|
937
|
+
streamInfo.originalUrl = this.#parserConfig.originalUrl;
|
|
938
|
+
streamInfo.playlist = new Playlist();
|
|
939
|
+
streamInfo.playlist.mediaParts.push(new MediaPart());
|
|
940
|
+
streamInfo.periodId = periodId;
|
|
941
|
+
streamInfo.groupId = representation.getAttribute("id");
|
|
942
|
+
streamInfo.codecs = codecs;
|
|
567
943
|
const volumeAdjust = representation.getAttribute("volumeAdjust");
|
|
568
|
-
if (volumeAdjust)
|
|
944
|
+
if (volumeAdjust) streamInfo.groupId = streamInfo.groupId + "-" + volumeAdjust;
|
|
569
945
|
const mType = representation.getAttribute("mimeType") || adaptationSet.getAttribute("mimeType");
|
|
570
946
|
if (mType) {
|
|
571
947
|
const mTypeSplit = mType.split("/");
|
|
572
|
-
|
|
948
|
+
streamInfo.extension = mTypeSplit.length === 2 ? mTypeSplit[1] : null;
|
|
573
949
|
}
|
|
574
|
-
|
|
575
|
-
const role = representation.getElementsByTagName("Role")[0] || adaptationSet.getElementsByTagName("Role")[0];
|
|
950
|
+
const role = roles?.[0];
|
|
576
951
|
if (role) {
|
|
577
|
-
const roleValue = role.
|
|
952
|
+
const roleValue = role.value;
|
|
578
953
|
const capitalize = (word) => word.charAt(0).toUpperCase() + word.slice(1);
|
|
579
|
-
|
|
580
|
-
streamSpec.role = roleType;
|
|
581
|
-
if (roleType === ROLE_TYPE.Subtitle) {
|
|
582
|
-
streamSpec.mediaType = MEDIA_TYPES.SUBTITLES;
|
|
583
|
-
if (mType?.includes("ttml")) streamSpec.extension = "ttml";
|
|
584
|
-
} else if (roleType === ROLE_TYPE.ForcedSubtitle) streamSpec.mediaType = MEDIA_TYPES.SUBTITLES;
|
|
954
|
+
streamInfo.role = ROLE_TYPE[roleValue.split("-").map(capitalize).join("")];
|
|
585
955
|
}
|
|
586
|
-
|
|
587
|
-
if (timeShiftBufferDepth)
|
|
588
|
-
|
|
589
|
-
if (audioChannelConfiguration) streamSpec.channels = audioChannelConfiguration.getAttribute("value");
|
|
590
|
-
if (publishTime) streamSpec.publishTime = new Date(publishTime);
|
|
956
|
+
streamInfo.playlist.isLive = isLive;
|
|
957
|
+
if (timeShiftBufferDepth) streamInfo.playlist.refreshIntervalMs = temporal_polyfill.Temporal.Duration.from(timeShiftBufferDepth).total("milliseconds") / 2;
|
|
958
|
+
if (publishTime) streamInfo.publishTime = new Date(publishTime);
|
|
591
959
|
const segmentBaseElement = representation.getElementsByTagName("SegmentBase")[0];
|
|
592
960
|
if (segmentBaseElement) {
|
|
593
961
|
const initialization = segmentBaseElement.getElementsByTagName("Initialization")[0];
|
|
@@ -598,7 +966,7 @@ var DashExtractor = class DashExtractor {
|
|
|
598
966
|
mediaSegment.index = 0;
|
|
599
967
|
mediaSegment.url = segBaseUrl;
|
|
600
968
|
mediaSegment.duration = periodDurationSeconds;
|
|
601
|
-
|
|
969
|
+
streamInfo.playlist.mediaParts[0].mediaSegments.push(mediaSegment);
|
|
602
970
|
} else {
|
|
603
971
|
const initUrl = combineUrl(segBaseUrl, sourceUrl);
|
|
604
972
|
const initRange = initialization.getAttribute("range");
|
|
@@ -610,7 +978,7 @@ var DashExtractor = class DashExtractor {
|
|
|
610
978
|
initSegment.startRange = start;
|
|
611
979
|
initSegment.expectLength = expect;
|
|
612
980
|
}
|
|
613
|
-
|
|
981
|
+
streamInfo.playlist.mediaInit = initSegment;
|
|
614
982
|
}
|
|
615
983
|
}
|
|
616
984
|
}
|
|
@@ -630,7 +998,7 @@ var DashExtractor = class DashExtractor {
|
|
|
630
998
|
initSegment.startRange = start;
|
|
631
999
|
initSegment.expectLength = expect;
|
|
632
1000
|
}
|
|
633
|
-
|
|
1001
|
+
streamInfo.playlist.mediaInit = initSegment;
|
|
634
1002
|
}
|
|
635
1003
|
const segmentUrls = segmentList.getElementsByTagName("SegmentURL");
|
|
636
1004
|
const timescaleStr = segmentList.getAttribute("timescale") || "1";
|
|
@@ -649,7 +1017,7 @@ var DashExtractor = class DashExtractor {
|
|
|
649
1017
|
segment.startRange = start;
|
|
650
1018
|
segment.expectLength = expect;
|
|
651
1019
|
}
|
|
652
|
-
|
|
1020
|
+
streamInfo.playlist.mediaParts[0].mediaSegments.push(segment);
|
|
653
1021
|
}
|
|
654
1022
|
}
|
|
655
1023
|
const segmentTemplateElementsOuter = adaptationSet.getElementsByTagName("SegmentTemplate");
|
|
@@ -658,8 +1026,8 @@ var DashExtractor = class DashExtractor {
|
|
|
658
1026
|
const segmentTemplate = segmentTemplateElements[0] || segmentTemplateElementsOuter[0];
|
|
659
1027
|
const segmentTemplateOuter = segmentTemplateElementsOuter[0] || segmentTemplateElements[0];
|
|
660
1028
|
const varDic = {};
|
|
661
|
-
varDic[DASH_TAGS.TemplateRepresentationID] =
|
|
662
|
-
varDic[DASH_TAGS.TemplateBandwidth] =
|
|
1029
|
+
varDic[DASH_TAGS.TemplateRepresentationID] = streamInfo.groupId;
|
|
1030
|
+
varDic[DASH_TAGS.TemplateBandwidth] = bitrate;
|
|
663
1031
|
const presentationTimeOffsetStr = segmentTemplate.getAttribute("presentationTimeOffset") || segmentTemplateOuter.getAttribute("presentationTimeOffset") || "0";
|
|
664
1032
|
const timescaleStr = segmentTemplate.getAttribute("timescale") || segmentTemplateOuter.getAttribute("timescale") || "1";
|
|
665
1033
|
const durationStr = segmentTemplate.getAttribute("duration") || segmentTemplateOuter.getAttribute("duration");
|
|
@@ -671,7 +1039,7 @@ var DashExtractor = class DashExtractor {
|
|
|
671
1039
|
const mediaSegment = new MediaSegment();
|
|
672
1040
|
mediaSegment.index = -1;
|
|
673
1041
|
mediaSegment.url = initUrl;
|
|
674
|
-
|
|
1042
|
+
streamInfo.playlist.mediaInit = mediaSegment;
|
|
675
1043
|
}
|
|
676
1044
|
const mediaTemplate = segmentTemplate.getAttribute("media") || segmentTemplateOuter.getAttribute("media");
|
|
677
1045
|
const segmentTimeline = segmentTemplate.getElementsByTagName("SegmentTimeline")[0];
|
|
@@ -698,7 +1066,7 @@ var DashExtractor = class DashExtractor {
|
|
|
698
1066
|
if (hasTime) mediaSegment.nameFromVar = currentTime.toString();
|
|
699
1067
|
mediaSegment.duration = _duration / timescale;
|
|
700
1068
|
mediaSegment.index = segIndex++;
|
|
701
|
-
|
|
1069
|
+
streamInfo.playlist.mediaParts[0].mediaSegments.push(mediaSegment);
|
|
702
1070
|
if (_repeatCount < 0) _repeatCount = Math.ceil(periodDurationSeconds * timescale / _duration) - 1;
|
|
703
1071
|
for (let i = 0; i < _repeatCount; i++) {
|
|
704
1072
|
currentTime += _duration;
|
|
@@ -711,7 +1079,7 @@ var DashExtractor = class DashExtractor {
|
|
|
711
1079
|
_mediaSegment.index = segIndex++;
|
|
712
1080
|
_mediaSegment.duration = _duration / timescale;
|
|
713
1081
|
if (_hashTime) _mediaSegment.nameFromVar = currentTime.toString();
|
|
714
|
-
|
|
1082
|
+
streamInfo.playlist.mediaParts[0].mediaSegments.push(_mediaSegment);
|
|
715
1083
|
}
|
|
716
1084
|
currentTime += _duration;
|
|
717
1085
|
}
|
|
@@ -740,16 +1108,16 @@ var DashExtractor = class DashExtractor {
|
|
|
740
1108
|
if (hasNumber) mediaSegment.nameFromVar = index.toString();
|
|
741
1109
|
mediaSegment.index = isLive ? index : segIndex;
|
|
742
1110
|
mediaSegment.duration = duration / timescale;
|
|
743
|
-
|
|
1111
|
+
streamInfo.playlist.mediaParts[0].mediaSegments.push(mediaSegment);
|
|
744
1112
|
}
|
|
745
1113
|
}
|
|
746
1114
|
}
|
|
747
|
-
if (
|
|
1115
|
+
if (streamInfo.playlist.mediaParts[0].mediaSegments.length === 0) {
|
|
748
1116
|
const mediaSegment = new MediaSegment();
|
|
749
1117
|
mediaSegment.index = 0;
|
|
750
1118
|
mediaSegment.url = segBaseUrl;
|
|
751
1119
|
mediaSegment.duration = periodDurationSeconds;
|
|
752
|
-
|
|
1120
|
+
streamInfo.playlist.mediaParts[0].mediaSegments.push(mediaSegment);
|
|
753
1121
|
}
|
|
754
1122
|
const adaptationSetProtections = adaptationSet.getElementsByTagName("ContentProtection");
|
|
755
1123
|
const representationProtections = representation.getElementsByTagName("ContentProtection");
|
|
@@ -769,69 +1137,69 @@ var DashExtractor = class DashExtractor {
|
|
|
769
1137
|
else if (schemeIdUri?.includes(playreadySystemId)) encryptInfo.drm.playready = drmData;
|
|
770
1138
|
else continue;
|
|
771
1139
|
}
|
|
772
|
-
if (
|
|
773
|
-
const segments =
|
|
1140
|
+
if (streamInfo.playlist.mediaInit) streamInfo.playlist.mediaInit.encryptInfo = encryptInfo;
|
|
1141
|
+
const segments = streamInfo.playlist.mediaParts[0].mediaSegments;
|
|
774
1142
|
for (const segment of segments) if (!segment.encryptInfo) segment.encryptInfo = encryptInfo;
|
|
775
1143
|
}
|
|
776
|
-
const _index =
|
|
777
|
-
if (_index > -1) if (isLive) {} else if (
|
|
778
|
-
const startIndex =
|
|
779
|
-
const segments =
|
|
1144
|
+
const _index = streamInfos.findIndex((item) => item.type === streamInfo.type && item.periodId !== streamInfo.periodId && item.groupId === streamInfo.groupId && (item.type === "video" && streamInfo.type === "video" ? item.width === streamInfo.width && item.height === streamInfo.height : true));
|
|
1145
|
+
if (_index > -1) if (isLive) {} else if (streamInfos[_index].playlist.mediaParts.at(-1).mediaSegments.at(-1).url !== streamInfo.playlist.mediaParts[0].mediaSegments.at(-1)?.url) {
|
|
1146
|
+
const startIndex = streamInfos[_index].playlist.mediaParts.at(-1).mediaSegments.at(-1).index + 1;
|
|
1147
|
+
const segments = streamInfo.playlist.mediaParts[0].mediaSegments;
|
|
780
1148
|
for (const segment of segments) segment.index += startIndex;
|
|
781
1149
|
const mediaPart = new MediaPart();
|
|
782
|
-
mediaPart.mediaSegments =
|
|
783
|
-
|
|
784
|
-
} else
|
|
1150
|
+
mediaPart.mediaSegments = streamInfos[_index].playlist.mediaParts[0].mediaSegments;
|
|
1151
|
+
streamInfos[_index].playlist.mediaParts.push(mediaPart);
|
|
1152
|
+
} else streamInfos[_index].playlist.mediaParts.at(-1).mediaSegments.at(-1).duration += streamInfo.playlist.mediaParts[0].mediaSegments.reduce((sum, segment) => sum + segment.duration, 0);
|
|
785
1153
|
else {
|
|
786
|
-
if (
|
|
787
|
-
if (
|
|
788
|
-
|
|
1154
|
+
if (streamInfo.type === "subtitle" && streamInfo.extension === "mp4") streamInfo.extension = "m4s";
|
|
1155
|
+
if (streamInfo.type !== "subtitle" && (streamInfo.extension == null || streamInfo.playlist.mediaParts.reduce((sum, part) => sum + part.mediaSegments.length, 0) > 1)) streamInfo.extension = "m4s";
|
|
1156
|
+
streamInfos.push(streamInfo);
|
|
789
1157
|
}
|
|
790
1158
|
segBaseUrl = representationsBaseUrl;
|
|
791
1159
|
}
|
|
792
1160
|
segBaseUrl = adaptationSetsBaseUrl;
|
|
793
1161
|
}
|
|
794
1162
|
}
|
|
795
|
-
const audioList =
|
|
796
|
-
const subtitleList =
|
|
797
|
-
const videoList =
|
|
1163
|
+
const audioList = streamInfos.filter((stream) => stream.type === "audio");
|
|
1164
|
+
const subtitleList = streamInfos.filter((stream) => stream.type === "subtitle");
|
|
1165
|
+
const videoList = streamInfos.filter((stream) => stream.type === "video");
|
|
798
1166
|
for (const video of videoList) {
|
|
799
|
-
const audioGroupId = audioList.toSorted((a, b$1) => (b$1.
|
|
800
|
-
const subtitleGroupId = subtitleList.toSorted((a, b$1) => (b$1.
|
|
1167
|
+
const audioGroupId = audioList.toSorted((a, b$1) => (b$1.bitrate || 0) - (a.bitrate || 0)).at(0)?.groupId;
|
|
1168
|
+
const subtitleGroupId = subtitleList.toSorted((a, b$1) => (b$1.bitrate || 0) - (a.bitrate || 0)).at(0)?.groupId;
|
|
801
1169
|
if (audioGroupId) video.audioId = audioGroupId;
|
|
802
1170
|
if (subtitleGroupId) video.subtitleId = subtitleGroupId;
|
|
803
1171
|
}
|
|
804
|
-
return
|
|
1172
|
+
return streamInfos;
|
|
805
1173
|
}
|
|
806
1174
|
#filterLanguage(v) {
|
|
807
1175
|
if (!v) return;
|
|
808
1176
|
return v;
|
|
809
1177
|
}
|
|
810
|
-
async refreshPlayList(
|
|
811
|
-
if (!
|
|
1178
|
+
async refreshPlayList(streamInfos) {
|
|
1179
|
+
if (!streamInfos.length) return;
|
|
812
1180
|
const response = await fetch(this.#parserConfig.url, this.#parserConfig.headers).catch(() => fetch(this.#parserConfig.originalUrl, this.#parserConfig.headers));
|
|
813
1181
|
const rawText = await response.text();
|
|
814
1182
|
const url = response.url;
|
|
815
1183
|
this.#parserConfig.url = url;
|
|
816
1184
|
this.#setInitUrl();
|
|
817
1185
|
const newStreams = await this.extractStreams(rawText);
|
|
818
|
-
for (const
|
|
819
|
-
let results = newStreams.filter((n) => n.toShortString() ===
|
|
820
|
-
if (!results.length) results = newStreams.filter((n) => n.playlist?.mediaInit?.url ===
|
|
821
|
-
if (results.length)
|
|
1186
|
+
for (const streamInfo of streamInfos) {
|
|
1187
|
+
let results = newStreams.filter((n) => n.toShortString() === streamInfo.toShortString());
|
|
1188
|
+
if (!results.length) results = newStreams.filter((n) => n.playlist?.mediaInit?.url === streamInfo.playlist?.mediaInit?.url);
|
|
1189
|
+
if (results.length) streamInfo.playlist.mediaParts = results.at(0).playlist.mediaParts;
|
|
822
1190
|
}
|
|
823
|
-
await this.#processUrl(
|
|
1191
|
+
await this.#processUrl(streamInfos);
|
|
824
1192
|
}
|
|
825
|
-
async #processUrl(
|
|
826
|
-
for (const spec of
|
|
1193
|
+
async #processUrl(streamInfos) {
|
|
1194
|
+
for (const spec of streamInfos) {
|
|
827
1195
|
const playlist = spec.playlist;
|
|
828
1196
|
if (!playlist) continue;
|
|
829
1197
|
if (playlist.mediaInit) playlist.mediaInit.url = this.preProcessUrl(playlist.mediaInit.url);
|
|
830
1198
|
for (const part of playlist.mediaParts) for (const segment of part.mediaSegments) segment.url = this.preProcessUrl(segment.url);
|
|
831
1199
|
}
|
|
832
1200
|
}
|
|
833
|
-
async fetchPlayList(
|
|
834
|
-
this.#processUrl(
|
|
1201
|
+
async fetchPlayList(streamInfos) {
|
|
1202
|
+
this.#processUrl(streamInfos);
|
|
835
1203
|
}
|
|
836
1204
|
preProcessUrl(url) {
|
|
837
1205
|
for (const processor of this.#parserConfig.urlProcessors) if (processor.canProcess(this.extractorType, url, this.#parserConfig)) url = processor.process(url, this.#parserConfig);
|
|
@@ -890,62 +1258,75 @@ var HlsExtractor = class {
|
|
|
890
1258
|
}
|
|
891
1259
|
async #parseMasterList() {
|
|
892
1260
|
this.#masterM3u8Flag = true;
|
|
893
|
-
const
|
|
1261
|
+
const streamInfos = [];
|
|
894
1262
|
let expectPlaylist = false;
|
|
895
|
-
let
|
|
1263
|
+
let streamInfo = new VideoStreamInfo();
|
|
896
1264
|
const lines = this.#m3u8Content.split("\n");
|
|
897
1265
|
for (const line of lines) {
|
|
898
1266
|
if (!line.trim()) continue;
|
|
899
1267
|
if (line.startsWith(HLS_TAGS.extXStreamInf)) {
|
|
900
|
-
|
|
901
|
-
|
|
1268
|
+
streamInfo = new VideoStreamInfo();
|
|
1269
|
+
streamInfo.originalUrl = this.parserConfig.originalUrl;
|
|
902
1270
|
const bandwidth = getAttribute(line, "AVERAGE-BANDWIDTH") || getAttribute(line, "BANDWIDTH");
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
1271
|
+
streamInfo.bitrate = Number(bandwidth || 0);
|
|
1272
|
+
streamInfo.codecs = getAttribute(line, "CODECS");
|
|
1273
|
+
const resolution = getAttribute(line, "RESOLUTION");
|
|
1274
|
+
const [widthString, heightString] = resolution.split("x");
|
|
1275
|
+
streamInfo.width = parseInt(widthString);
|
|
1276
|
+
streamInfo.height = parseInt(heightString);
|
|
1277
|
+
streamInfo.resolution = resolution;
|
|
906
1278
|
const frameRate = getAttribute(line, "FRAME-RATE");
|
|
907
|
-
if (frameRate)
|
|
1279
|
+
if (frameRate) streamInfo.frameRate = Number(frameRate);
|
|
908
1280
|
const audioId = getAttribute(line, "AUDIO");
|
|
909
|
-
if (audioId)
|
|
1281
|
+
if (audioId) streamInfo.audioId = audioId;
|
|
910
1282
|
const videoId = getAttribute(line, "VIDEO");
|
|
911
|
-
if (videoId)
|
|
1283
|
+
if (videoId) streamInfo.videoId = videoId;
|
|
912
1284
|
const subtitleId = getAttribute(line, "SUBTITLES");
|
|
913
|
-
if (subtitleId)
|
|
1285
|
+
if (subtitleId) streamInfo.subtitleId = subtitleId;
|
|
914
1286
|
const videoRange = getAttribute(line, "VIDEO-RANGE");
|
|
915
|
-
if (videoRange)
|
|
916
|
-
if (
|
|
1287
|
+
if (videoRange) streamInfo.videoRange = videoRange;
|
|
1288
|
+
if (streamInfo.codecs && streamInfo.audioId) streamInfo.codecs = streamInfo.codecs.split(",")[0];
|
|
917
1289
|
expectPlaylist = true;
|
|
918
1290
|
} else if (line.startsWith(HLS_TAGS.extXMedia)) {
|
|
919
|
-
|
|
920
|
-
const
|
|
921
|
-
if (
|
|
922
|
-
if (
|
|
1291
|
+
streamInfo = new VideoStreamInfo();
|
|
1292
|
+
const type = getAttribute(line, "TYPE");
|
|
1293
|
+
if (type === "VIDEO") streamInfo = new VideoStreamInfo();
|
|
1294
|
+
if (type === "AUDIO") streamInfo = new AudioStreamInfo();
|
|
1295
|
+
if (type === "SUBTITLES") streamInfo = new SubtitleStreamInfo();
|
|
1296
|
+
if (type === "CLOSED-CAPTIONS") {
|
|
1297
|
+
streamInfo = new SubtitleStreamInfo();
|
|
1298
|
+
streamInfo.cc = true;
|
|
1299
|
+
continue;
|
|
1300
|
+
}
|
|
923
1301
|
let url = getAttribute(line, "URI");
|
|
924
1302
|
if (!url) continue;
|
|
925
1303
|
url = combineUrl(this.#baseUrl, url);
|
|
926
|
-
|
|
1304
|
+
streamInfo.url = this.preProcessUrl(url);
|
|
927
1305
|
const groupId = getAttribute(line, "GROUP-ID");
|
|
928
|
-
if (groupId)
|
|
1306
|
+
if (groupId) streamInfo.groupId = groupId;
|
|
929
1307
|
const language = getAttribute(line, "LANGUAGE");
|
|
930
|
-
if (language)
|
|
1308
|
+
if (language) streamInfo.languageCode = language;
|
|
931
1309
|
const name = getAttribute(line, "NAME");
|
|
932
|
-
if (name)
|
|
1310
|
+
if (name) streamInfo.name = name;
|
|
933
1311
|
const defaultFlag = getAttribute(line, "DEFAULT");
|
|
934
|
-
if (defaultFlag)
|
|
935
|
-
const
|
|
936
|
-
if (
|
|
1312
|
+
if (defaultFlag) streamInfo.default = defaultFlag.toLowerCase() === "yes";
|
|
1313
|
+
const channelsString = getAttribute(line, "CHANNELS");
|
|
1314
|
+
if (channelsString) {
|
|
1315
|
+
streamInfo.channels = channelsString;
|
|
1316
|
+
if (streamInfo.type === "audio") streamInfo.numberOfChannels = parseFloat(channelsString);
|
|
1317
|
+
}
|
|
937
1318
|
const characteristics = getAttribute(line, "CHARACTERISTICS");
|
|
938
|
-
if (characteristics)
|
|
939
|
-
|
|
1319
|
+
if (characteristics) streamInfo.characteristics = characteristics.split(",").at(-1)?.split(".").at(-1);
|
|
1320
|
+
streamInfos.push(streamInfo);
|
|
940
1321
|
} else if (line.startsWith("#")) continue;
|
|
941
1322
|
else if (expectPlaylist) {
|
|
942
1323
|
const url = combineUrl(this.#baseUrl, line);
|
|
943
|
-
|
|
1324
|
+
streamInfo.url = this.preProcessUrl(url);
|
|
944
1325
|
expectPlaylist = false;
|
|
945
|
-
|
|
1326
|
+
streamInfos.push(streamInfo);
|
|
946
1327
|
}
|
|
947
1328
|
}
|
|
948
|
-
return
|
|
1329
|
+
return streamInfos;
|
|
949
1330
|
}
|
|
950
1331
|
async #parseList() {
|
|
951
1332
|
let hasAd = false;
|
|
@@ -1069,7 +1450,7 @@ var HlsExtractor = class {
|
|
|
1069
1450
|
this.preProcessContent();
|
|
1070
1451
|
if (this.#m3u8Content.includes(HLS_TAGS.extXStreamInf)) return this.#parseMasterList().then((lists) => distinctBy(lists, (list) => list.url));
|
|
1071
1452
|
const playlist = await this.#parseList();
|
|
1072
|
-
const streamSpec = new
|
|
1453
|
+
const streamSpec = new VideoStreamInfo();
|
|
1073
1454
|
streamSpec.url = this.parserConfig.url;
|
|
1074
1455
|
streamSpec.playlist = playlist;
|
|
1075
1456
|
streamSpec.extension = playlist.mediaInit ? "mp4" : "ts";
|
|
@@ -1115,7 +1496,7 @@ var HlsExtractor = class {
|
|
|
1115
1496
|
const newPlaylist = await this.#parseList();
|
|
1116
1497
|
if (list.playlist?.mediaInit) list.playlist.mediaParts = newPlaylist.mediaParts;
|
|
1117
1498
|
else list.playlist = newPlaylist;
|
|
1118
|
-
if (list.
|
|
1499
|
+
if (list.type === "subtitle") {
|
|
1119
1500
|
const a = list.playlist.mediaParts.some((part) => part.mediaSegments.some((segment) => segment.url.includes(".ttml")));
|
|
1120
1501
|
const b$1 = list.playlist.mediaParts.some((part) => part.mediaSegments.some((segment) => segment.url.includes(".vtt") || segment.url.includes(".webvtt")));
|
|
1121
1502
|
if (a) list.extension = "ttml";
|
|
@@ -1123,8 +1504,8 @@ var HlsExtractor = class {
|
|
|
1123
1504
|
} else list.extension = list.playlist.mediaInit ? "m4s" : "ts";
|
|
1124
1505
|
}
|
|
1125
1506
|
}
|
|
1126
|
-
async refreshPlayList(
|
|
1127
|
-
await this.fetchPlayList(
|
|
1507
|
+
async refreshPlayList(streamInfos) {
|
|
1508
|
+
await this.fetchPlayList(streamInfos);
|
|
1128
1509
|
}
|
|
1129
1510
|
};
|
|
1130
1511
|
|
|
@@ -1184,15 +1565,18 @@ var StreamExtractor = class {
|
|
|
1184
1565
|
async extractStreams() {
|
|
1185
1566
|
return this.#extractor.extractStreams(this.#rawText);
|
|
1186
1567
|
}
|
|
1187
|
-
async fetchPlayList(
|
|
1188
|
-
return this.#extractor.fetchPlayList(
|
|
1568
|
+
async fetchPlayList(streamInfos) {
|
|
1569
|
+
return this.#extractor.fetchPlayList(streamInfos);
|
|
1189
1570
|
}
|
|
1190
|
-
async refreshPlayList(
|
|
1191
|
-
return this.#extractor.refreshPlayList(
|
|
1571
|
+
async refreshPlayList(streamInfos) {
|
|
1572
|
+
return this.#extractor.refreshPlayList(streamInfos);
|
|
1192
1573
|
}
|
|
1193
1574
|
};
|
|
1194
1575
|
|
|
1195
1576
|
//#endregion
|
|
1577
|
+
exports.ALL_STREAM_TYPES = ALL_STREAM_TYPES;
|
|
1578
|
+
exports.AUDIO_CODECS = AUDIO_CODECS;
|
|
1579
|
+
exports.AudioStreamInfo = AudioStreamInfo;
|
|
1196
1580
|
exports.DASH_TAGS = DASH_TAGS;
|
|
1197
1581
|
exports.DashExtractor = DashExtractor;
|
|
1198
1582
|
exports.DefaultDashContentProcessor = DefaultDashContentProcessor;
|
|
@@ -1204,12 +1588,17 @@ exports.EXTRACTOR_TYPES = EXTRACTOR_TYPES;
|
|
|
1204
1588
|
exports.EncryptInfo = EncryptInfo;
|
|
1205
1589
|
exports.HLS_TAGS = HLS_TAGS;
|
|
1206
1590
|
exports.HlsExtractor = HlsExtractor;
|
|
1207
|
-
exports.MEDIA_TYPES = MEDIA_TYPES;
|
|
1208
1591
|
exports.MediaPart = MediaPart;
|
|
1209
1592
|
exports.MediaSegment = MediaSegment;
|
|
1210
1593
|
exports.ParserConfig = ParserConfig;
|
|
1594
|
+
exports.Playlist = Playlist;
|
|
1211
1595
|
exports.ROLE_TYPE = ROLE_TYPE;
|
|
1596
|
+
exports.SUBTITLE_CODECS = SUBTITLE_CODECS;
|
|
1212
1597
|
exports.StreamExtractor = StreamExtractor;
|
|
1213
|
-
exports.
|
|
1598
|
+
exports.StreamInfo = StreamInfo;
|
|
1599
|
+
exports.SubtitleStreamInfo = SubtitleStreamInfo;
|
|
1600
|
+
exports.VIDEO_CODECS = VIDEO_CODECS;
|
|
1601
|
+
exports.VIDEO_DYNAMIC_RANGES = VIDEO_DYNAMIC_RANGES;
|
|
1602
|
+
exports.VideoStreamInfo = VideoStreamInfo;
|
|
1214
1603
|
exports.getRange = getRange;
|
|
1215
1604
|
exports.parseRange = parseRange;
|