dasha 4.0.0-alpha.3 → 4.0.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dasha.cjs +159 -152
- package/dist/dasha.d.cts +186 -69
- package/dist/{dasha.d.ts → dasha.d.mts} +186 -69
- package/dist/{dasha.js → dasha.mjs} +145 -145
- package/package.json +3 -3
package/dist/dasha.cjs
CHANGED
|
@@ -21,32 +21,17 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
21
21
|
}) : target, mod));
|
|
22
22
|
|
|
23
23
|
//#endregion
|
|
24
|
+
let node_crypto = require("node:crypto");
|
|
25
|
+
node_crypto = __toESM(node_crypto);
|
|
24
26
|
let node_fs = require("node:fs");
|
|
25
|
-
node_fs = __toESM(node_fs);
|
|
26
27
|
let barsic = require("barsic");
|
|
27
|
-
barsic = __toESM(barsic);
|
|
28
28
|
let node_fs_promises = require("node:fs/promises");
|
|
29
|
-
node_fs_promises = __toESM(node_fs_promises);
|
|
30
29
|
let node_url = require("node:url");
|
|
31
|
-
node_url = __toESM(node_url);
|
|
32
30
|
let node_path = require("node:path");
|
|
33
31
|
node_path = __toESM(node_path);
|
|
34
32
|
let temporal_polyfill = require("temporal-polyfill");
|
|
35
|
-
temporal_polyfill = __toESM(temporal_polyfill);
|
|
36
33
|
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);
|
|
40
34
|
|
|
41
|
-
//#region lib/shared/media-type.ts
|
|
42
|
-
const MEDIA_TYPES = {
|
|
43
|
-
VIDEO: "video",
|
|
44
|
-
AUDIO: "audio",
|
|
45
|
-
SUBTITLES: "subtitle",
|
|
46
|
-
CLOSED_CAPTIONS: "closed-captions"
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
//#endregion
|
|
50
35
|
//#region lib/shared/encrypt-method.ts
|
|
51
36
|
const ENCRYPT_METHODS = {
|
|
52
37
|
NONE: "none",
|
|
@@ -59,6 +44,23 @@ const ENCRYPT_METHODS = {
|
|
|
59
44
|
UNKNOWN: "unknown"
|
|
60
45
|
};
|
|
61
46
|
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region lib/shared/encrypt-info.ts
|
|
49
|
+
var EncryptInfo = class {
|
|
50
|
+
method = ENCRYPT_METHODS.NONE;
|
|
51
|
+
key;
|
|
52
|
+
iv;
|
|
53
|
+
drm;
|
|
54
|
+
constructor(method) {
|
|
55
|
+
this.method = this.parseMethod(method);
|
|
56
|
+
this.drm = {};
|
|
57
|
+
}
|
|
58
|
+
parseMethod(method) {
|
|
59
|
+
if (method) return ENCRYPT_METHODS[method.replace("-", "_")];
|
|
60
|
+
else return ENCRYPT_METHODS.UNKNOWN;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
62
64
|
//#endregion
|
|
63
65
|
//#region lib/shared/extractor-type.ts
|
|
64
66
|
const EXTRACTOR_TYPES = {
|
|
@@ -68,6 +70,132 @@ const EXTRACTOR_TYPES = {
|
|
|
68
70
|
MSS: "MSS"
|
|
69
71
|
};
|
|
70
72
|
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region lib/shared/media-part.ts
|
|
75
|
+
var MediaPart = class {
|
|
76
|
+
mediaSegments = [];
|
|
77
|
+
constructor(segments) {
|
|
78
|
+
this.mediaSegments = segments || [];
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
//#endregion
|
|
83
|
+
//#region lib/shared/media-segment.ts
|
|
84
|
+
var MediaSegment = class MediaSegment {
|
|
85
|
+
index = NaN;
|
|
86
|
+
duration = NaN;
|
|
87
|
+
title;
|
|
88
|
+
dateTime;
|
|
89
|
+
startRange;
|
|
90
|
+
get stopRange() {
|
|
91
|
+
return this.startRange !== void 0 && this.expectLength !== void 0 ? this.startRange + this.expectLength - 1 : void 0;
|
|
92
|
+
}
|
|
93
|
+
expectLength;
|
|
94
|
+
encryptInfo = new EncryptInfo();
|
|
95
|
+
get isEncrypted() {
|
|
96
|
+
return this.encryptInfo.method !== ENCRYPT_METHODS.NONE;
|
|
97
|
+
}
|
|
98
|
+
url = "";
|
|
99
|
+
nameFromVar;
|
|
100
|
+
equals(segment) {
|
|
101
|
+
if (segment instanceof MediaSegment) return this.index == segment.index && Math.abs(this.duration - segment.duration) < .001 && this.title == segment.title && this.startRange == segment.startRange && this.stopRange == segment.stopRange && this.expectLength == segment.expectLength && this.url == segment.url;
|
|
102
|
+
else return false;
|
|
103
|
+
}
|
|
104
|
+
getHashCode() {
|
|
105
|
+
const payload = [
|
|
106
|
+
this.index,
|
|
107
|
+
this.duration,
|
|
108
|
+
this.title,
|
|
109
|
+
this.startRange,
|
|
110
|
+
this.stopRange,
|
|
111
|
+
this.expectLength,
|
|
112
|
+
this.url
|
|
113
|
+
].join("-");
|
|
114
|
+
return node_crypto.default.createHash("md5").update(payload).digest("hex");
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
//#endregion
|
|
119
|
+
//#region lib/shared/media-type.ts
|
|
120
|
+
const MEDIA_TYPES = {
|
|
121
|
+
VIDEO: "video",
|
|
122
|
+
AUDIO: "audio",
|
|
123
|
+
SUBTITLES: "subtitle",
|
|
124
|
+
CLOSED_CAPTIONS: "closed-captions"
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region lib/shared/stream-spec.ts
|
|
129
|
+
var StreamSpec = class {
|
|
130
|
+
mediaType;
|
|
131
|
+
groupId = null;
|
|
132
|
+
language;
|
|
133
|
+
name;
|
|
134
|
+
default;
|
|
135
|
+
skippedDuration;
|
|
136
|
+
bandwidth;
|
|
137
|
+
codecs = null;
|
|
138
|
+
resolution;
|
|
139
|
+
frameRate;
|
|
140
|
+
channels = null;
|
|
141
|
+
extension = null;
|
|
142
|
+
role;
|
|
143
|
+
videoRange;
|
|
144
|
+
characteristics;
|
|
145
|
+
publishTime;
|
|
146
|
+
audioId;
|
|
147
|
+
videoId;
|
|
148
|
+
subtitleId;
|
|
149
|
+
periodId = null;
|
|
150
|
+
url = "";
|
|
151
|
+
originalUrl = "";
|
|
152
|
+
playlist;
|
|
153
|
+
get segmentsCount() {
|
|
154
|
+
return this.playlist?.mediaParts.reduce((sum, part) => sum + part.mediaSegments.length, 0) ?? 0;
|
|
155
|
+
}
|
|
156
|
+
toShortString() {
|
|
157
|
+
let prefixStr = "";
|
|
158
|
+
let returnStr = "";
|
|
159
|
+
const encStr = "";
|
|
160
|
+
const bandwidth = this.bandwidth ? `${this.bandwidth / 1e3} Kbps` : "";
|
|
161
|
+
const channels = this.channels ? `${this.channels}CH` : "";
|
|
162
|
+
if (this.mediaType === MEDIA_TYPES.AUDIO) {
|
|
163
|
+
prefixStr = `Aud ${encStr}`;
|
|
164
|
+
returnStr = [
|
|
165
|
+
this.groupId,
|
|
166
|
+
bandwidth,
|
|
167
|
+
this.name,
|
|
168
|
+
this.codecs,
|
|
169
|
+
this.language,
|
|
170
|
+
channels,
|
|
171
|
+
this.role
|
|
172
|
+
].filter(Boolean).join(" | ");
|
|
173
|
+
} else if (this.mediaType === MEDIA_TYPES.SUBTITLES) {
|
|
174
|
+
prefixStr = `Sub ${encStr}`;
|
|
175
|
+
returnStr = [
|
|
176
|
+
this.groupId,
|
|
177
|
+
this.language,
|
|
178
|
+
this.name,
|
|
179
|
+
this.codecs,
|
|
180
|
+
this.role
|
|
181
|
+
].filter(Boolean).join(" | ");
|
|
182
|
+
} else {
|
|
183
|
+
prefixStr = `Vid ${encStr}`;
|
|
184
|
+
returnStr = [
|
|
185
|
+
this.resolution,
|
|
186
|
+
bandwidth,
|
|
187
|
+
this.groupId,
|
|
188
|
+
this.frameRate,
|
|
189
|
+
this.codecs,
|
|
190
|
+
this.videoRange,
|
|
191
|
+
this.role
|
|
192
|
+
].filter(Boolean).join(" | ");
|
|
193
|
+
}
|
|
194
|
+
returnStr = `${prefixStr} | ${returnStr}`;
|
|
195
|
+
return returnStr.trim();
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
71
199
|
//#endregion
|
|
72
200
|
//#region lib/shared/role-type.ts
|
|
73
201
|
const ROLE_TYPE = {
|
|
@@ -184,23 +312,6 @@ var DefaultHlsContentProcessor = class DefaultHlsContentProcessor {
|
|
|
184
312
|
}
|
|
185
313
|
};
|
|
186
314
|
|
|
187
|
-
//#endregion
|
|
188
|
-
//#region lib/shared/encrypt-info.ts
|
|
189
|
-
var EncryptInfo = class {
|
|
190
|
-
method = ENCRYPT_METHODS.NONE;
|
|
191
|
-
key;
|
|
192
|
-
iv;
|
|
193
|
-
drm;
|
|
194
|
-
constructor(method) {
|
|
195
|
-
this.method = this.parseMethod(method);
|
|
196
|
-
this.drm = {};
|
|
197
|
-
}
|
|
198
|
-
parseMethod(method) {
|
|
199
|
-
if (method) return ENCRYPT_METHODS[method.replace("-", "_")];
|
|
200
|
-
else return ENCRYPT_METHODS.UNKNOWN;
|
|
201
|
-
}
|
|
202
|
-
};
|
|
203
|
-
|
|
204
315
|
//#endregion
|
|
205
316
|
//#region lib/hls/hls-key-processor.ts
|
|
206
317
|
var DefaultHlsKeyProcessor = class {
|
|
@@ -281,78 +392,6 @@ var ParserConfig = class {
|
|
|
281
392
|
keyRetryCount = 3;
|
|
282
393
|
};
|
|
283
394
|
|
|
284
|
-
//#endregion
|
|
285
|
-
//#region lib/shared/stream-spec.ts
|
|
286
|
-
var StreamSpec = class {
|
|
287
|
-
mediaType;
|
|
288
|
-
groupId = null;
|
|
289
|
-
language;
|
|
290
|
-
name;
|
|
291
|
-
default;
|
|
292
|
-
skippedDuration;
|
|
293
|
-
bandwidth;
|
|
294
|
-
codecs = null;
|
|
295
|
-
resolution;
|
|
296
|
-
frameRate;
|
|
297
|
-
channels = null;
|
|
298
|
-
extension = null;
|
|
299
|
-
role;
|
|
300
|
-
videoRange;
|
|
301
|
-
characteristics;
|
|
302
|
-
publishTime;
|
|
303
|
-
audioId;
|
|
304
|
-
videoId;
|
|
305
|
-
subtitleId;
|
|
306
|
-
periodId = null;
|
|
307
|
-
url = "";
|
|
308
|
-
originalUrl = "";
|
|
309
|
-
playlist;
|
|
310
|
-
get segmentsCount() {
|
|
311
|
-
return this.playlist?.mediaParts.reduce((sum, part) => sum + part.mediaSegments.length, 0) ?? 0;
|
|
312
|
-
}
|
|
313
|
-
toShortString() {
|
|
314
|
-
let prefixStr = "";
|
|
315
|
-
let returnStr = "";
|
|
316
|
-
const encStr = "";
|
|
317
|
-
const bandwidth = this.bandwidth ? `${this.bandwidth / 1e3} Kbps` : "";
|
|
318
|
-
const channels = this.channels ? `${this.channels}CH` : "";
|
|
319
|
-
if (this.mediaType === MEDIA_TYPES.AUDIO) {
|
|
320
|
-
prefixStr = `Aud ${encStr}`;
|
|
321
|
-
returnStr = [
|
|
322
|
-
this.groupId,
|
|
323
|
-
bandwidth,
|
|
324
|
-
this.name,
|
|
325
|
-
this.codecs,
|
|
326
|
-
this.language,
|
|
327
|
-
channels,
|
|
328
|
-
this.role
|
|
329
|
-
].filter(Boolean).join(" | ");
|
|
330
|
-
} else if (this.mediaType === MEDIA_TYPES.SUBTITLES) {
|
|
331
|
-
prefixStr = `Sub ${encStr}`;
|
|
332
|
-
returnStr = [
|
|
333
|
-
this.groupId,
|
|
334
|
-
this.language,
|
|
335
|
-
this.name,
|
|
336
|
-
this.codecs,
|
|
337
|
-
this.role
|
|
338
|
-
].filter(Boolean).join(" | ");
|
|
339
|
-
} else {
|
|
340
|
-
prefixStr = `Vid ${encStr}`;
|
|
341
|
-
returnStr = [
|
|
342
|
-
this.resolution,
|
|
343
|
-
bandwidth,
|
|
344
|
-
this.groupId,
|
|
345
|
-
this.frameRate,
|
|
346
|
-
this.codecs,
|
|
347
|
-
this.videoRange,
|
|
348
|
-
this.role
|
|
349
|
-
].filter(Boolean).join(" | ");
|
|
350
|
-
}
|
|
351
|
-
returnStr = `${prefixStr} | ${returnStr}`;
|
|
352
|
-
return returnStr.trim();
|
|
353
|
-
}
|
|
354
|
-
};
|
|
355
|
-
|
|
356
395
|
//#endregion
|
|
357
396
|
//#region lib/dash/dash-tags.ts
|
|
358
397
|
const DASH_TAGS = {
|
|
@@ -431,51 +470,6 @@ var Playlist = class {
|
|
|
431
470
|
mediaParts = [];
|
|
432
471
|
};
|
|
433
472
|
|
|
434
|
-
//#endregion
|
|
435
|
-
//#region lib/shared/media-part.ts
|
|
436
|
-
var MediaPart = class {
|
|
437
|
-
mediaSegments = [];
|
|
438
|
-
constructor(segments) {
|
|
439
|
-
this.mediaSegments = segments || [];
|
|
440
|
-
}
|
|
441
|
-
};
|
|
442
|
-
|
|
443
|
-
//#endregion
|
|
444
|
-
//#region lib/shared/media-segment.ts
|
|
445
|
-
var MediaSegment = class MediaSegment {
|
|
446
|
-
index = NaN;
|
|
447
|
-
duration = NaN;
|
|
448
|
-
title;
|
|
449
|
-
dateTime;
|
|
450
|
-
startRange;
|
|
451
|
-
get stopRange() {
|
|
452
|
-
return this.startRange !== void 0 && this.expectLength !== void 0 ? this.startRange + this.expectLength - 1 : void 0;
|
|
453
|
-
}
|
|
454
|
-
expectLength;
|
|
455
|
-
encryptInfo = new EncryptInfo();
|
|
456
|
-
get isEncrypted() {
|
|
457
|
-
return this.encryptInfo.method !== ENCRYPT_METHODS.NONE;
|
|
458
|
-
}
|
|
459
|
-
url = "";
|
|
460
|
-
nameFromVar;
|
|
461
|
-
equals(segment) {
|
|
462
|
-
if (segment instanceof MediaSegment) return this.index == segment.index && Math.abs(this.duration - segment.duration) < .001 && this.title == segment.title && this.startRange == segment.startRange && this.stopRange == segment.stopRange && this.expectLength == segment.expectLength && this.url == segment.url;
|
|
463
|
-
else return false;
|
|
464
|
-
}
|
|
465
|
-
getHashCode() {
|
|
466
|
-
const payload = [
|
|
467
|
-
this.index,
|
|
468
|
-
this.duration,
|
|
469
|
-
this.title,
|
|
470
|
-
this.startRange,
|
|
471
|
-
this.stopRange,
|
|
472
|
-
this.expectLength,
|
|
473
|
-
this.url
|
|
474
|
-
].join("-");
|
|
475
|
-
return node_crypto.default.createHash("md5").update(payload).digest("hex");
|
|
476
|
-
}
|
|
477
|
-
};
|
|
478
|
-
|
|
479
473
|
//#endregion
|
|
480
474
|
//#region lib/dash/dash-utils.ts
|
|
481
475
|
/**
|
|
@@ -1199,10 +1193,23 @@ var StreamExtractor = class {
|
|
|
1199
1193
|
};
|
|
1200
1194
|
|
|
1201
1195
|
//#endregion
|
|
1196
|
+
exports.DASH_TAGS = DASH_TAGS;
|
|
1197
|
+
exports.DashExtractor = DashExtractor;
|
|
1198
|
+
exports.DefaultDashContentProcessor = DefaultDashContentProcessor;
|
|
1199
|
+
exports.DefaultHlsContentProcessor = DefaultHlsContentProcessor;
|
|
1200
|
+
exports.DefaultHlsKeyProcessor = DefaultHlsKeyProcessor;
|
|
1202
1201
|
exports.DefaultUrlProcessor = DefaultUrlProcessor;
|
|
1203
1202
|
exports.ENCRYPT_METHODS = ENCRYPT_METHODS;
|
|
1204
1203
|
exports.EXTRACTOR_TYPES = EXTRACTOR_TYPES;
|
|
1204
|
+
exports.EncryptInfo = EncryptInfo;
|
|
1205
|
+
exports.HLS_TAGS = HLS_TAGS;
|
|
1206
|
+
exports.HlsExtractor = HlsExtractor;
|
|
1205
1207
|
exports.MEDIA_TYPES = MEDIA_TYPES;
|
|
1208
|
+
exports.MediaPart = MediaPart;
|
|
1209
|
+
exports.MediaSegment = MediaSegment;
|
|
1206
1210
|
exports.ParserConfig = ParserConfig;
|
|
1207
1211
|
exports.ROLE_TYPE = ROLE_TYPE;
|
|
1208
|
-
exports.StreamExtractor = StreamExtractor;
|
|
1212
|
+
exports.StreamExtractor = StreamExtractor;
|
|
1213
|
+
exports.StreamSpec = StreamSpec;
|
|
1214
|
+
exports.getRange = getRange;
|
|
1215
|
+
exports.parseRange = parseRange;
|
package/dist/dasha.d.cts
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
//#region lib/shared/media-type.d.ts
|
|
2
|
-
declare const MEDIA_TYPES: {
|
|
3
|
-
readonly VIDEO: "video";
|
|
4
|
-
readonly AUDIO: "audio";
|
|
5
|
-
readonly SUBTITLES: "subtitle";
|
|
6
|
-
readonly CLOSED_CAPTIONS: "closed-captions";
|
|
7
|
-
};
|
|
8
|
-
type MediaType = (typeof MEDIA_TYPES)[keyof typeof MEDIA_TYPES];
|
|
9
|
-
//#endregion
|
|
10
1
|
//#region lib/shared/encrypt-method.d.ts
|
|
11
2
|
declare const ENCRYPT_METHODS: {
|
|
12
3
|
readonly NONE: "none";
|
|
@@ -20,30 +11,6 @@ declare const ENCRYPT_METHODS: {
|
|
|
20
11
|
};
|
|
21
12
|
type EncryptMethod = (typeof ENCRYPT_METHODS)[keyof typeof ENCRYPT_METHODS];
|
|
22
13
|
//#endregion
|
|
23
|
-
//#region lib/shared/extractor-type.d.ts
|
|
24
|
-
declare const EXTRACTOR_TYPES: {
|
|
25
|
-
readonly MPEG_DASH: "MPEG_DASH";
|
|
26
|
-
readonly HLS: "HLS";
|
|
27
|
-
readonly HTTP_LIVE: "HTTP_LIVE";
|
|
28
|
-
readonly MSS: "MSS";
|
|
29
|
-
};
|
|
30
|
-
type ExtractorType = (typeof EXTRACTOR_TYPES)[keyof typeof EXTRACTOR_TYPES];
|
|
31
|
-
//#endregion
|
|
32
|
-
//#region lib/shared/role-type.d.ts
|
|
33
|
-
declare const ROLE_TYPE: {
|
|
34
|
-
Subtitle: number;
|
|
35
|
-
Main: number;
|
|
36
|
-
Alternate: number;
|
|
37
|
-
Supplementary: number;
|
|
38
|
-
Commentary: number;
|
|
39
|
-
Dub: number;
|
|
40
|
-
Description: number;
|
|
41
|
-
Sign: number;
|
|
42
|
-
Metadata: number;
|
|
43
|
-
ForcedSubtitle: number;
|
|
44
|
-
};
|
|
45
|
-
type RoleType = (typeof ROLE_TYPE)[keyof typeof ROLE_TYPE];
|
|
46
|
-
//#endregion
|
|
47
14
|
//#region lib/shared/encrypt-info.d.ts
|
|
48
15
|
type DrmType = 'widevine' | 'playready' | 'fairplay';
|
|
49
16
|
declare class EncryptInfo {
|
|
@@ -58,41 +25,14 @@ declare class EncryptInfo {
|
|
|
58
25
|
parseMethod(method?: string | null): EncryptMethod;
|
|
59
26
|
}
|
|
60
27
|
//#endregion
|
|
61
|
-
//#region lib/
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
interface UrlProcessor {
|
|
71
|
-
canProcess(extractorType: ExtractorType, originalUrl: string, parserConfig: ParserConfig): boolean;
|
|
72
|
-
process(originalUrl: string, parserConfig: ParserConfig): string;
|
|
73
|
-
}
|
|
74
|
-
declare class DefaultUrlProcessor implements UrlProcessor {
|
|
75
|
-
canProcess(_extractorType: ExtractorType, _originalUrl: string, parserConfig: ParserConfig): boolean;
|
|
76
|
-
process(url: string, parserConfig: ParserConfig): string;
|
|
77
|
-
}
|
|
78
|
-
//#endregion
|
|
79
|
-
//#region lib/parser-config.d.ts
|
|
80
|
-
declare class ParserConfig {
|
|
81
|
-
url: string;
|
|
82
|
-
originalUrl: string;
|
|
83
|
-
baseUrl?: string;
|
|
84
|
-
customParserArgs: Record<string, string>;
|
|
85
|
-
headers: Record<string, string>;
|
|
86
|
-
contentProcessors: ContentProcessor[];
|
|
87
|
-
urlProcessors: UrlProcessor[];
|
|
88
|
-
keyProcessors: KeyProcessor[];
|
|
89
|
-
customMethod?: EncryptMethod;
|
|
90
|
-
customKey?: Uint8Array;
|
|
91
|
-
customIv?: Uint8Array;
|
|
92
|
-
urlProcessorArgs?: string;
|
|
93
|
-
appendUrlParams: boolean;
|
|
94
|
-
keyRetryCount: number;
|
|
95
|
-
}
|
|
28
|
+
//#region lib/shared/extractor-type.d.ts
|
|
29
|
+
declare const EXTRACTOR_TYPES: {
|
|
30
|
+
readonly MPEG_DASH: "MPEG_DASH";
|
|
31
|
+
readonly HLS: "HLS";
|
|
32
|
+
readonly HTTP_LIVE: "HTTP_LIVE";
|
|
33
|
+
readonly MSS: "MSS";
|
|
34
|
+
};
|
|
35
|
+
type ExtractorType = (typeof EXTRACTOR_TYPES)[keyof typeof EXTRACTOR_TYPES];
|
|
96
36
|
//#endregion
|
|
97
37
|
//#region lib/shared/media-segment.d.ts
|
|
98
38
|
declare class MediaSegment {
|
|
@@ -117,6 +57,15 @@ declare class MediaPart {
|
|
|
117
57
|
constructor(segments?: MediaSegment[]);
|
|
118
58
|
}
|
|
119
59
|
//#endregion
|
|
60
|
+
//#region lib/shared/media-type.d.ts
|
|
61
|
+
declare const MEDIA_TYPES: {
|
|
62
|
+
readonly VIDEO: "video";
|
|
63
|
+
readonly AUDIO: "audio";
|
|
64
|
+
readonly SUBTITLES: "subtitle";
|
|
65
|
+
readonly CLOSED_CAPTIONS: "closed-captions";
|
|
66
|
+
};
|
|
67
|
+
type MediaType = (typeof MEDIA_TYPES)[keyof typeof MEDIA_TYPES];
|
|
68
|
+
//#endregion
|
|
120
69
|
//#region lib/shared/playlist.d.ts
|
|
121
70
|
declare class Playlist {
|
|
122
71
|
url: string;
|
|
@@ -128,6 +77,21 @@ declare class Playlist {
|
|
|
128
77
|
mediaParts: MediaPart[];
|
|
129
78
|
}
|
|
130
79
|
//#endregion
|
|
80
|
+
//#region lib/shared/role-type.d.ts
|
|
81
|
+
declare const ROLE_TYPE: {
|
|
82
|
+
Subtitle: number;
|
|
83
|
+
Main: number;
|
|
84
|
+
Alternate: number;
|
|
85
|
+
Supplementary: number;
|
|
86
|
+
Commentary: number;
|
|
87
|
+
Dub: number;
|
|
88
|
+
Description: number;
|
|
89
|
+
Sign: number;
|
|
90
|
+
Metadata: number;
|
|
91
|
+
ForcedSubtitle: number;
|
|
92
|
+
};
|
|
93
|
+
type RoleType = (typeof ROLE_TYPE)[keyof typeof ROLE_TYPE];
|
|
94
|
+
//#endregion
|
|
131
95
|
//#region lib/shared/stream-spec.d.ts
|
|
132
96
|
declare class StreamSpec {
|
|
133
97
|
mediaType?: MediaType;
|
|
@@ -157,6 +121,52 @@ declare class StreamSpec {
|
|
|
157
121
|
toShortString(): string;
|
|
158
122
|
}
|
|
159
123
|
//#endregion
|
|
124
|
+
//#region lib/extractor.d.ts
|
|
125
|
+
interface Extractor {
|
|
126
|
+
extractorType: ExtractorType;
|
|
127
|
+
extractStreams(rawText: string): Promise<StreamSpec[]>;
|
|
128
|
+
fetchPlayList(streamSpecs: StreamSpec[]): Promise<void>;
|
|
129
|
+
refreshPlayList(streamSpecs: StreamSpec[]): Promise<void>;
|
|
130
|
+
preProcessUrl(url: string): string;
|
|
131
|
+
preProcessContent(): void;
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region lib/processor.d.ts
|
|
135
|
+
interface ContentProcessor {
|
|
136
|
+
canProcess(extractorType: ExtractorType, rawText: string, parserConfig: ParserConfig): boolean;
|
|
137
|
+
process(rawText: string, parserConfig: ParserConfig): string;
|
|
138
|
+
}
|
|
139
|
+
interface KeyProcessor {
|
|
140
|
+
canProcess(extractorType: ExtractorType, keyLine: string, m3u8Url: string, m3u8Content: string, parserConfig: ParserConfig): boolean;
|
|
141
|
+
process(keyLine: string, m3u8Url: string, m3u8Content: string, parserConfig: ParserConfig): Promise<EncryptInfo>;
|
|
142
|
+
}
|
|
143
|
+
interface UrlProcessor {
|
|
144
|
+
canProcess(extractorType: ExtractorType, originalUrl: string, parserConfig: ParserConfig): boolean;
|
|
145
|
+
process(originalUrl: string, parserConfig: ParserConfig): string;
|
|
146
|
+
}
|
|
147
|
+
declare class DefaultUrlProcessor implements UrlProcessor {
|
|
148
|
+
canProcess(_extractorType: ExtractorType, _originalUrl: string, parserConfig: ParserConfig): boolean;
|
|
149
|
+
process(url: string, parserConfig: ParserConfig): string;
|
|
150
|
+
}
|
|
151
|
+
//#endregion
|
|
152
|
+
//#region lib/parser-config.d.ts
|
|
153
|
+
declare class ParserConfig {
|
|
154
|
+
url: string;
|
|
155
|
+
originalUrl: string;
|
|
156
|
+
baseUrl?: string;
|
|
157
|
+
customParserArgs: Record<string, string>;
|
|
158
|
+
headers: Record<string, string>;
|
|
159
|
+
contentProcessors: ContentProcessor[];
|
|
160
|
+
urlProcessors: UrlProcessor[];
|
|
161
|
+
keyProcessors: KeyProcessor[];
|
|
162
|
+
customMethod?: EncryptMethod;
|
|
163
|
+
customKey?: Uint8Array;
|
|
164
|
+
customIv?: Uint8Array;
|
|
165
|
+
urlProcessorArgs?: string;
|
|
166
|
+
appendUrlParams: boolean;
|
|
167
|
+
keyRetryCount: number;
|
|
168
|
+
}
|
|
169
|
+
//#endregion
|
|
160
170
|
//#region lib/stream-extractor.d.ts
|
|
161
171
|
declare class StreamExtractor {
|
|
162
172
|
#private;
|
|
@@ -169,4 +179,111 @@ declare class StreamExtractor {
|
|
|
169
179
|
refreshPlayList(streamSpecs: StreamSpec[]): Promise<void>;
|
|
170
180
|
}
|
|
171
181
|
//#endregion
|
|
172
|
-
|
|
182
|
+
//#region lib/dash/dash-content-processor.d.ts
|
|
183
|
+
declare class DefaultDashContentProcessor implements ContentProcessor {
|
|
184
|
+
canProcess(extractorType: ExtractorType, mpdContent: string): boolean;
|
|
185
|
+
process(mpdContent: string): string;
|
|
186
|
+
}
|
|
187
|
+
//#endregion
|
|
188
|
+
//#region lib/dash/dash-extractor.d.ts
|
|
189
|
+
declare class DashExtractor implements Extractor {
|
|
190
|
+
#private;
|
|
191
|
+
get extractorType(): ExtractorType;
|
|
192
|
+
constructor(parserConfig: ParserConfig);
|
|
193
|
+
extractStreams(rawText: string): Promise<StreamSpec[]>;
|
|
194
|
+
refreshPlayList(streamSpecs: StreamSpec[]): Promise<void>;
|
|
195
|
+
fetchPlayList(streamSpecs: StreamSpec[]): Promise<void>;
|
|
196
|
+
preProcessUrl(url: string): string;
|
|
197
|
+
preProcessContent(): void;
|
|
198
|
+
}
|
|
199
|
+
//#endregion
|
|
200
|
+
//#region lib/dash/dash-tags.d.ts
|
|
201
|
+
declare const DASH_TAGS: {
|
|
202
|
+
TemplateRepresentationID: string;
|
|
203
|
+
TemplateBandwidth: string;
|
|
204
|
+
TemplateNumber: string;
|
|
205
|
+
TemplateTime: string;
|
|
206
|
+
};
|
|
207
|
+
//#endregion
|
|
208
|
+
//#region lib/dash/dash-utils.d.ts
|
|
209
|
+
/**
|
|
210
|
+
* Extracts StartRange and ExpectLength information from a string like "100-300"
|
|
211
|
+
* @param range - The range string in the format "start-end"
|
|
212
|
+
* @returns A tuple containing [StartRange, ExpectLength]
|
|
213
|
+
*/
|
|
214
|
+
declare const parseRange: (range: string) => [number, number];
|
|
215
|
+
//#endregion
|
|
216
|
+
//#region lib/hls/hls-content-processor.d.ts
|
|
217
|
+
declare class DefaultHlsContentProcessor implements ContentProcessor {
|
|
218
|
+
private static readonly YkDVRegex;
|
|
219
|
+
private static readonly DNSPRegex;
|
|
220
|
+
private static readonly DNSPSubRegex;
|
|
221
|
+
private static readonly OrderFixRegex;
|
|
222
|
+
private static readonly ATVRegex;
|
|
223
|
+
private static readonly ATVRegex2;
|
|
224
|
+
canProcess(extractorType: ExtractorType): boolean;
|
|
225
|
+
process(m3u8Content: string, parserConfig: ParserConfig): string;
|
|
226
|
+
private applyRegexReplacement;
|
|
227
|
+
}
|
|
228
|
+
//#endregion
|
|
229
|
+
//#region lib/hls/hls-extractor.d.ts
|
|
230
|
+
declare class HlsExtractor implements Extractor {
|
|
231
|
+
#private;
|
|
232
|
+
get extractorType(): ExtractorType;
|
|
233
|
+
parserConfig: ParserConfig;
|
|
234
|
+
constructor(parserConfig: ParserConfig);
|
|
235
|
+
preProcessContent(): void;
|
|
236
|
+
preProcessUrl(url: string): string;
|
|
237
|
+
extractStreams(rawText: string): Promise<StreamSpec[]>;
|
|
238
|
+
fetchPlayList(lists: StreamSpec[]): Promise<void>;
|
|
239
|
+
refreshPlayList(streamSpecs: StreamSpec[]): Promise<void>;
|
|
240
|
+
}
|
|
241
|
+
//#endregion
|
|
242
|
+
//#region lib/hls/hls-key-processor.d.ts
|
|
243
|
+
declare class DefaultHlsKeyProcessor implements KeyProcessor {
|
|
244
|
+
canProcess(extractorType: ExtractorType): boolean;
|
|
245
|
+
process(keyLine: string, m3u8Url: string, _m3u8Content: string, parserConfig: ParserConfig): Promise<EncryptInfo>;
|
|
246
|
+
private getAttribute;
|
|
247
|
+
private fetchKeyWithRetry;
|
|
248
|
+
private preProcessUrl;
|
|
249
|
+
}
|
|
250
|
+
//#endregion
|
|
251
|
+
//#region lib/hls/hls-tags.d.ts
|
|
252
|
+
declare const HLS_TAGS: {
|
|
253
|
+
extM3u: string;
|
|
254
|
+
extXTargetDuration: string;
|
|
255
|
+
extXMediaSequence: string;
|
|
256
|
+
extXDiscontinuitySequence: string;
|
|
257
|
+
extXProgramDateTime: string;
|
|
258
|
+
extXMedia: string;
|
|
259
|
+
extXPlaylistType: string;
|
|
260
|
+
extXKey: string;
|
|
261
|
+
extXStreamInf: string;
|
|
262
|
+
extXVersion: string;
|
|
263
|
+
extXAllowCache: string;
|
|
264
|
+
extXEndlist: string;
|
|
265
|
+
extInf: string;
|
|
266
|
+
extIframesOnly: string;
|
|
267
|
+
extXByterange: string;
|
|
268
|
+
extXIframeStreamInf: string;
|
|
269
|
+
extXDiscontinuity: string;
|
|
270
|
+
extXCueOutStart: string;
|
|
271
|
+
extXCueOut: string;
|
|
272
|
+
extIsIndependentSegments: string;
|
|
273
|
+
extXScte35: string;
|
|
274
|
+
extXCueStart: string;
|
|
275
|
+
extXCueEnd: string;
|
|
276
|
+
extXCueSpan: string;
|
|
277
|
+
extXMap: string;
|
|
278
|
+
extXStart: string;
|
|
279
|
+
};
|
|
280
|
+
//#endregion
|
|
281
|
+
//#region lib/hls/hls-utils.d.ts
|
|
282
|
+
/**
|
|
283
|
+
* Extracts length and optional start values from a string formatted as "n[@o]".
|
|
284
|
+
* @param input - The input string.
|
|
285
|
+
* @returns A tuple containing [n (length), o (start)].
|
|
286
|
+
*/
|
|
287
|
+
declare function getRange(input: string): [number, number | null];
|
|
288
|
+
//#endregion
|
|
289
|
+
export { ContentProcessor, DASH_TAGS, DashExtractor, DefaultDashContentProcessor, DefaultHlsContentProcessor, DefaultHlsKeyProcessor, DefaultUrlProcessor, ENCRYPT_METHODS, EXTRACTOR_TYPES, EncryptInfo, EncryptMethod, Extractor, ExtractorType, HLS_TAGS, HlsExtractor, KeyProcessor, MEDIA_TYPES, MediaPart, MediaSegment, MediaType, ParserConfig, ROLE_TYPE, RoleType, StreamExtractor, StreamSpec, UrlProcessor, getRange, parseRange };
|
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
//#region lib/shared/media-type.d.ts
|
|
2
|
-
declare const MEDIA_TYPES: {
|
|
3
|
-
readonly VIDEO: "video";
|
|
4
|
-
readonly AUDIO: "audio";
|
|
5
|
-
readonly SUBTITLES: "subtitle";
|
|
6
|
-
readonly CLOSED_CAPTIONS: "closed-captions";
|
|
7
|
-
};
|
|
8
|
-
type MediaType = (typeof MEDIA_TYPES)[keyof typeof MEDIA_TYPES];
|
|
9
|
-
//#endregion
|
|
10
1
|
//#region lib/shared/encrypt-method.d.ts
|
|
11
2
|
declare const ENCRYPT_METHODS: {
|
|
12
3
|
readonly NONE: "none";
|
|
@@ -20,30 +11,6 @@ declare const ENCRYPT_METHODS: {
|
|
|
20
11
|
};
|
|
21
12
|
type EncryptMethod = (typeof ENCRYPT_METHODS)[keyof typeof ENCRYPT_METHODS];
|
|
22
13
|
//#endregion
|
|
23
|
-
//#region lib/shared/extractor-type.d.ts
|
|
24
|
-
declare const EXTRACTOR_TYPES: {
|
|
25
|
-
readonly MPEG_DASH: "MPEG_DASH";
|
|
26
|
-
readonly HLS: "HLS";
|
|
27
|
-
readonly HTTP_LIVE: "HTTP_LIVE";
|
|
28
|
-
readonly MSS: "MSS";
|
|
29
|
-
};
|
|
30
|
-
type ExtractorType = (typeof EXTRACTOR_TYPES)[keyof typeof EXTRACTOR_TYPES];
|
|
31
|
-
//#endregion
|
|
32
|
-
//#region lib/shared/role-type.d.ts
|
|
33
|
-
declare const ROLE_TYPE: {
|
|
34
|
-
Subtitle: number;
|
|
35
|
-
Main: number;
|
|
36
|
-
Alternate: number;
|
|
37
|
-
Supplementary: number;
|
|
38
|
-
Commentary: number;
|
|
39
|
-
Dub: number;
|
|
40
|
-
Description: number;
|
|
41
|
-
Sign: number;
|
|
42
|
-
Metadata: number;
|
|
43
|
-
ForcedSubtitle: number;
|
|
44
|
-
};
|
|
45
|
-
type RoleType = (typeof ROLE_TYPE)[keyof typeof ROLE_TYPE];
|
|
46
|
-
//#endregion
|
|
47
14
|
//#region lib/shared/encrypt-info.d.ts
|
|
48
15
|
type DrmType = 'widevine' | 'playready' | 'fairplay';
|
|
49
16
|
declare class EncryptInfo {
|
|
@@ -58,41 +25,14 @@ declare class EncryptInfo {
|
|
|
58
25
|
parseMethod(method?: string | null): EncryptMethod;
|
|
59
26
|
}
|
|
60
27
|
//#endregion
|
|
61
|
-
//#region lib/
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
interface UrlProcessor {
|
|
71
|
-
canProcess(extractorType: ExtractorType, originalUrl: string, parserConfig: ParserConfig): boolean;
|
|
72
|
-
process(originalUrl: string, parserConfig: ParserConfig): string;
|
|
73
|
-
}
|
|
74
|
-
declare class DefaultUrlProcessor implements UrlProcessor {
|
|
75
|
-
canProcess(_extractorType: ExtractorType, _originalUrl: string, parserConfig: ParserConfig): boolean;
|
|
76
|
-
process(url: string, parserConfig: ParserConfig): string;
|
|
77
|
-
}
|
|
78
|
-
//#endregion
|
|
79
|
-
//#region lib/parser-config.d.ts
|
|
80
|
-
declare class ParserConfig {
|
|
81
|
-
url: string;
|
|
82
|
-
originalUrl: string;
|
|
83
|
-
baseUrl?: string;
|
|
84
|
-
customParserArgs: Record<string, string>;
|
|
85
|
-
headers: Record<string, string>;
|
|
86
|
-
contentProcessors: ContentProcessor[];
|
|
87
|
-
urlProcessors: UrlProcessor[];
|
|
88
|
-
keyProcessors: KeyProcessor[];
|
|
89
|
-
customMethod?: EncryptMethod;
|
|
90
|
-
customKey?: Uint8Array;
|
|
91
|
-
customIv?: Uint8Array;
|
|
92
|
-
urlProcessorArgs?: string;
|
|
93
|
-
appendUrlParams: boolean;
|
|
94
|
-
keyRetryCount: number;
|
|
95
|
-
}
|
|
28
|
+
//#region lib/shared/extractor-type.d.ts
|
|
29
|
+
declare const EXTRACTOR_TYPES: {
|
|
30
|
+
readonly MPEG_DASH: "MPEG_DASH";
|
|
31
|
+
readonly HLS: "HLS";
|
|
32
|
+
readonly HTTP_LIVE: "HTTP_LIVE";
|
|
33
|
+
readonly MSS: "MSS";
|
|
34
|
+
};
|
|
35
|
+
type ExtractorType = (typeof EXTRACTOR_TYPES)[keyof typeof EXTRACTOR_TYPES];
|
|
96
36
|
//#endregion
|
|
97
37
|
//#region lib/shared/media-segment.d.ts
|
|
98
38
|
declare class MediaSegment {
|
|
@@ -117,6 +57,15 @@ declare class MediaPart {
|
|
|
117
57
|
constructor(segments?: MediaSegment[]);
|
|
118
58
|
}
|
|
119
59
|
//#endregion
|
|
60
|
+
//#region lib/shared/media-type.d.ts
|
|
61
|
+
declare const MEDIA_TYPES: {
|
|
62
|
+
readonly VIDEO: "video";
|
|
63
|
+
readonly AUDIO: "audio";
|
|
64
|
+
readonly SUBTITLES: "subtitle";
|
|
65
|
+
readonly CLOSED_CAPTIONS: "closed-captions";
|
|
66
|
+
};
|
|
67
|
+
type MediaType = (typeof MEDIA_TYPES)[keyof typeof MEDIA_TYPES];
|
|
68
|
+
//#endregion
|
|
120
69
|
//#region lib/shared/playlist.d.ts
|
|
121
70
|
declare class Playlist {
|
|
122
71
|
url: string;
|
|
@@ -128,6 +77,21 @@ declare class Playlist {
|
|
|
128
77
|
mediaParts: MediaPart[];
|
|
129
78
|
}
|
|
130
79
|
//#endregion
|
|
80
|
+
//#region lib/shared/role-type.d.ts
|
|
81
|
+
declare const ROLE_TYPE: {
|
|
82
|
+
Subtitle: number;
|
|
83
|
+
Main: number;
|
|
84
|
+
Alternate: number;
|
|
85
|
+
Supplementary: number;
|
|
86
|
+
Commentary: number;
|
|
87
|
+
Dub: number;
|
|
88
|
+
Description: number;
|
|
89
|
+
Sign: number;
|
|
90
|
+
Metadata: number;
|
|
91
|
+
ForcedSubtitle: number;
|
|
92
|
+
};
|
|
93
|
+
type RoleType = (typeof ROLE_TYPE)[keyof typeof ROLE_TYPE];
|
|
94
|
+
//#endregion
|
|
131
95
|
//#region lib/shared/stream-spec.d.ts
|
|
132
96
|
declare class StreamSpec {
|
|
133
97
|
mediaType?: MediaType;
|
|
@@ -157,6 +121,52 @@ declare class StreamSpec {
|
|
|
157
121
|
toShortString(): string;
|
|
158
122
|
}
|
|
159
123
|
//#endregion
|
|
124
|
+
//#region lib/extractor.d.ts
|
|
125
|
+
interface Extractor {
|
|
126
|
+
extractorType: ExtractorType;
|
|
127
|
+
extractStreams(rawText: string): Promise<StreamSpec[]>;
|
|
128
|
+
fetchPlayList(streamSpecs: StreamSpec[]): Promise<void>;
|
|
129
|
+
refreshPlayList(streamSpecs: StreamSpec[]): Promise<void>;
|
|
130
|
+
preProcessUrl(url: string): string;
|
|
131
|
+
preProcessContent(): void;
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region lib/processor.d.ts
|
|
135
|
+
interface ContentProcessor {
|
|
136
|
+
canProcess(extractorType: ExtractorType, rawText: string, parserConfig: ParserConfig): boolean;
|
|
137
|
+
process(rawText: string, parserConfig: ParserConfig): string;
|
|
138
|
+
}
|
|
139
|
+
interface KeyProcessor {
|
|
140
|
+
canProcess(extractorType: ExtractorType, keyLine: string, m3u8Url: string, m3u8Content: string, parserConfig: ParserConfig): boolean;
|
|
141
|
+
process(keyLine: string, m3u8Url: string, m3u8Content: string, parserConfig: ParserConfig): Promise<EncryptInfo>;
|
|
142
|
+
}
|
|
143
|
+
interface UrlProcessor {
|
|
144
|
+
canProcess(extractorType: ExtractorType, originalUrl: string, parserConfig: ParserConfig): boolean;
|
|
145
|
+
process(originalUrl: string, parserConfig: ParserConfig): string;
|
|
146
|
+
}
|
|
147
|
+
declare class DefaultUrlProcessor implements UrlProcessor {
|
|
148
|
+
canProcess(_extractorType: ExtractorType, _originalUrl: string, parserConfig: ParserConfig): boolean;
|
|
149
|
+
process(url: string, parserConfig: ParserConfig): string;
|
|
150
|
+
}
|
|
151
|
+
//#endregion
|
|
152
|
+
//#region lib/parser-config.d.ts
|
|
153
|
+
declare class ParserConfig {
|
|
154
|
+
url: string;
|
|
155
|
+
originalUrl: string;
|
|
156
|
+
baseUrl?: string;
|
|
157
|
+
customParserArgs: Record<string, string>;
|
|
158
|
+
headers: Record<string, string>;
|
|
159
|
+
contentProcessors: ContentProcessor[];
|
|
160
|
+
urlProcessors: UrlProcessor[];
|
|
161
|
+
keyProcessors: KeyProcessor[];
|
|
162
|
+
customMethod?: EncryptMethod;
|
|
163
|
+
customKey?: Uint8Array;
|
|
164
|
+
customIv?: Uint8Array;
|
|
165
|
+
urlProcessorArgs?: string;
|
|
166
|
+
appendUrlParams: boolean;
|
|
167
|
+
keyRetryCount: number;
|
|
168
|
+
}
|
|
169
|
+
//#endregion
|
|
160
170
|
//#region lib/stream-extractor.d.ts
|
|
161
171
|
declare class StreamExtractor {
|
|
162
172
|
#private;
|
|
@@ -169,4 +179,111 @@ declare class StreamExtractor {
|
|
|
169
179
|
refreshPlayList(streamSpecs: StreamSpec[]): Promise<void>;
|
|
170
180
|
}
|
|
171
181
|
//#endregion
|
|
172
|
-
|
|
182
|
+
//#region lib/dash/dash-content-processor.d.ts
|
|
183
|
+
declare class DefaultDashContentProcessor implements ContentProcessor {
|
|
184
|
+
canProcess(extractorType: ExtractorType, mpdContent: string): boolean;
|
|
185
|
+
process(mpdContent: string): string;
|
|
186
|
+
}
|
|
187
|
+
//#endregion
|
|
188
|
+
//#region lib/dash/dash-extractor.d.ts
|
|
189
|
+
declare class DashExtractor implements Extractor {
|
|
190
|
+
#private;
|
|
191
|
+
get extractorType(): ExtractorType;
|
|
192
|
+
constructor(parserConfig: ParserConfig);
|
|
193
|
+
extractStreams(rawText: string): Promise<StreamSpec[]>;
|
|
194
|
+
refreshPlayList(streamSpecs: StreamSpec[]): Promise<void>;
|
|
195
|
+
fetchPlayList(streamSpecs: StreamSpec[]): Promise<void>;
|
|
196
|
+
preProcessUrl(url: string): string;
|
|
197
|
+
preProcessContent(): void;
|
|
198
|
+
}
|
|
199
|
+
//#endregion
|
|
200
|
+
//#region lib/dash/dash-tags.d.ts
|
|
201
|
+
declare const DASH_TAGS: {
|
|
202
|
+
TemplateRepresentationID: string;
|
|
203
|
+
TemplateBandwidth: string;
|
|
204
|
+
TemplateNumber: string;
|
|
205
|
+
TemplateTime: string;
|
|
206
|
+
};
|
|
207
|
+
//#endregion
|
|
208
|
+
//#region lib/dash/dash-utils.d.ts
|
|
209
|
+
/**
|
|
210
|
+
* Extracts StartRange and ExpectLength information from a string like "100-300"
|
|
211
|
+
* @param range - The range string in the format "start-end"
|
|
212
|
+
* @returns A tuple containing [StartRange, ExpectLength]
|
|
213
|
+
*/
|
|
214
|
+
declare const parseRange: (range: string) => [number, number];
|
|
215
|
+
//#endregion
|
|
216
|
+
//#region lib/hls/hls-content-processor.d.ts
|
|
217
|
+
declare class DefaultHlsContentProcessor implements ContentProcessor {
|
|
218
|
+
private static readonly YkDVRegex;
|
|
219
|
+
private static readonly DNSPRegex;
|
|
220
|
+
private static readonly DNSPSubRegex;
|
|
221
|
+
private static readonly OrderFixRegex;
|
|
222
|
+
private static readonly ATVRegex;
|
|
223
|
+
private static readonly ATVRegex2;
|
|
224
|
+
canProcess(extractorType: ExtractorType): boolean;
|
|
225
|
+
process(m3u8Content: string, parserConfig: ParserConfig): string;
|
|
226
|
+
private applyRegexReplacement;
|
|
227
|
+
}
|
|
228
|
+
//#endregion
|
|
229
|
+
//#region lib/hls/hls-extractor.d.ts
|
|
230
|
+
declare class HlsExtractor implements Extractor {
|
|
231
|
+
#private;
|
|
232
|
+
get extractorType(): ExtractorType;
|
|
233
|
+
parserConfig: ParserConfig;
|
|
234
|
+
constructor(parserConfig: ParserConfig);
|
|
235
|
+
preProcessContent(): void;
|
|
236
|
+
preProcessUrl(url: string): string;
|
|
237
|
+
extractStreams(rawText: string): Promise<StreamSpec[]>;
|
|
238
|
+
fetchPlayList(lists: StreamSpec[]): Promise<void>;
|
|
239
|
+
refreshPlayList(streamSpecs: StreamSpec[]): Promise<void>;
|
|
240
|
+
}
|
|
241
|
+
//#endregion
|
|
242
|
+
//#region lib/hls/hls-key-processor.d.ts
|
|
243
|
+
declare class DefaultHlsKeyProcessor implements KeyProcessor {
|
|
244
|
+
canProcess(extractorType: ExtractorType): boolean;
|
|
245
|
+
process(keyLine: string, m3u8Url: string, _m3u8Content: string, parserConfig: ParserConfig): Promise<EncryptInfo>;
|
|
246
|
+
private getAttribute;
|
|
247
|
+
private fetchKeyWithRetry;
|
|
248
|
+
private preProcessUrl;
|
|
249
|
+
}
|
|
250
|
+
//#endregion
|
|
251
|
+
//#region lib/hls/hls-tags.d.ts
|
|
252
|
+
declare const HLS_TAGS: {
|
|
253
|
+
extM3u: string;
|
|
254
|
+
extXTargetDuration: string;
|
|
255
|
+
extXMediaSequence: string;
|
|
256
|
+
extXDiscontinuitySequence: string;
|
|
257
|
+
extXProgramDateTime: string;
|
|
258
|
+
extXMedia: string;
|
|
259
|
+
extXPlaylistType: string;
|
|
260
|
+
extXKey: string;
|
|
261
|
+
extXStreamInf: string;
|
|
262
|
+
extXVersion: string;
|
|
263
|
+
extXAllowCache: string;
|
|
264
|
+
extXEndlist: string;
|
|
265
|
+
extInf: string;
|
|
266
|
+
extIframesOnly: string;
|
|
267
|
+
extXByterange: string;
|
|
268
|
+
extXIframeStreamInf: string;
|
|
269
|
+
extXDiscontinuity: string;
|
|
270
|
+
extXCueOutStart: string;
|
|
271
|
+
extXCueOut: string;
|
|
272
|
+
extIsIndependentSegments: string;
|
|
273
|
+
extXScte35: string;
|
|
274
|
+
extXCueStart: string;
|
|
275
|
+
extXCueEnd: string;
|
|
276
|
+
extXCueSpan: string;
|
|
277
|
+
extXMap: string;
|
|
278
|
+
extXStart: string;
|
|
279
|
+
};
|
|
280
|
+
//#endregion
|
|
281
|
+
//#region lib/hls/hls-utils.d.ts
|
|
282
|
+
/**
|
|
283
|
+
* Extracts length and optional start values from a string formatted as "n[@o]".
|
|
284
|
+
* @param input - The input string.
|
|
285
|
+
* @returns A tuple containing [n (length), o (start)].
|
|
286
|
+
*/
|
|
287
|
+
declare function getRange(input: string): [number, number | null];
|
|
288
|
+
//#endregion
|
|
289
|
+
export { ContentProcessor, DASH_TAGS, DashExtractor, DefaultDashContentProcessor, DefaultHlsContentProcessor, DefaultHlsKeyProcessor, DefaultUrlProcessor, ENCRYPT_METHODS, EXTRACTOR_TYPES, EncryptInfo, EncryptMethod, Extractor, ExtractorType, HLS_TAGS, HlsExtractor, KeyProcessor, MEDIA_TYPES, MediaPart, MediaSegment, MediaType, ParserConfig, ROLE_TYPE, RoleType, StreamExtractor, StreamSpec, UrlProcessor, getRange, parseRange };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
1
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
3
|
import { b } from "barsic";
|
|
3
4
|
import { readFile } from "node:fs/promises";
|
|
@@ -5,17 +6,7 @@ import { pathToFileURL } from "node:url";
|
|
|
5
6
|
import path from "node:path";
|
|
6
7
|
import { Temporal } from "temporal-polyfill";
|
|
7
8
|
import { DOMParser } from "@xmldom/xmldom";
|
|
8
|
-
import crypto from "node:crypto";
|
|
9
|
-
|
|
10
|
-
//#region lib/shared/media-type.ts
|
|
11
|
-
const MEDIA_TYPES = {
|
|
12
|
-
VIDEO: "video",
|
|
13
|
-
AUDIO: "audio",
|
|
14
|
-
SUBTITLES: "subtitle",
|
|
15
|
-
CLOSED_CAPTIONS: "closed-captions"
|
|
16
|
-
};
|
|
17
9
|
|
|
18
|
-
//#endregion
|
|
19
10
|
//#region lib/shared/encrypt-method.ts
|
|
20
11
|
const ENCRYPT_METHODS = {
|
|
21
12
|
NONE: "none",
|
|
@@ -28,6 +19,23 @@ const ENCRYPT_METHODS = {
|
|
|
28
19
|
UNKNOWN: "unknown"
|
|
29
20
|
};
|
|
30
21
|
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region lib/shared/encrypt-info.ts
|
|
24
|
+
var EncryptInfo = class {
|
|
25
|
+
method = ENCRYPT_METHODS.NONE;
|
|
26
|
+
key;
|
|
27
|
+
iv;
|
|
28
|
+
drm;
|
|
29
|
+
constructor(method) {
|
|
30
|
+
this.method = this.parseMethod(method);
|
|
31
|
+
this.drm = {};
|
|
32
|
+
}
|
|
33
|
+
parseMethod(method) {
|
|
34
|
+
if (method) return ENCRYPT_METHODS[method.replace("-", "_")];
|
|
35
|
+
else return ENCRYPT_METHODS.UNKNOWN;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
31
39
|
//#endregion
|
|
32
40
|
//#region lib/shared/extractor-type.ts
|
|
33
41
|
const EXTRACTOR_TYPES = {
|
|
@@ -37,6 +45,132 @@ const EXTRACTOR_TYPES = {
|
|
|
37
45
|
MSS: "MSS"
|
|
38
46
|
};
|
|
39
47
|
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region lib/shared/media-part.ts
|
|
50
|
+
var MediaPart = class {
|
|
51
|
+
mediaSegments = [];
|
|
52
|
+
constructor(segments) {
|
|
53
|
+
this.mediaSegments = segments || [];
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region lib/shared/media-segment.ts
|
|
59
|
+
var MediaSegment = class MediaSegment {
|
|
60
|
+
index = NaN;
|
|
61
|
+
duration = NaN;
|
|
62
|
+
title;
|
|
63
|
+
dateTime;
|
|
64
|
+
startRange;
|
|
65
|
+
get stopRange() {
|
|
66
|
+
return this.startRange !== void 0 && this.expectLength !== void 0 ? this.startRange + this.expectLength - 1 : void 0;
|
|
67
|
+
}
|
|
68
|
+
expectLength;
|
|
69
|
+
encryptInfo = new EncryptInfo();
|
|
70
|
+
get isEncrypted() {
|
|
71
|
+
return this.encryptInfo.method !== ENCRYPT_METHODS.NONE;
|
|
72
|
+
}
|
|
73
|
+
url = "";
|
|
74
|
+
nameFromVar;
|
|
75
|
+
equals(segment) {
|
|
76
|
+
if (segment instanceof MediaSegment) return this.index == segment.index && Math.abs(this.duration - segment.duration) < .001 && this.title == segment.title && this.startRange == segment.startRange && this.stopRange == segment.stopRange && this.expectLength == segment.expectLength && this.url == segment.url;
|
|
77
|
+
else return false;
|
|
78
|
+
}
|
|
79
|
+
getHashCode() {
|
|
80
|
+
const payload = [
|
|
81
|
+
this.index,
|
|
82
|
+
this.duration,
|
|
83
|
+
this.title,
|
|
84
|
+
this.startRange,
|
|
85
|
+
this.stopRange,
|
|
86
|
+
this.expectLength,
|
|
87
|
+
this.url
|
|
88
|
+
].join("-");
|
|
89
|
+
return crypto.createHash("md5").update(payload).digest("hex");
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region lib/shared/media-type.ts
|
|
95
|
+
const MEDIA_TYPES = {
|
|
96
|
+
VIDEO: "video",
|
|
97
|
+
AUDIO: "audio",
|
|
98
|
+
SUBTITLES: "subtitle",
|
|
99
|
+
CLOSED_CAPTIONS: "closed-captions"
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region lib/shared/stream-spec.ts
|
|
104
|
+
var StreamSpec = class {
|
|
105
|
+
mediaType;
|
|
106
|
+
groupId = null;
|
|
107
|
+
language;
|
|
108
|
+
name;
|
|
109
|
+
default;
|
|
110
|
+
skippedDuration;
|
|
111
|
+
bandwidth;
|
|
112
|
+
codecs = null;
|
|
113
|
+
resolution;
|
|
114
|
+
frameRate;
|
|
115
|
+
channels = null;
|
|
116
|
+
extension = null;
|
|
117
|
+
role;
|
|
118
|
+
videoRange;
|
|
119
|
+
characteristics;
|
|
120
|
+
publishTime;
|
|
121
|
+
audioId;
|
|
122
|
+
videoId;
|
|
123
|
+
subtitleId;
|
|
124
|
+
periodId = null;
|
|
125
|
+
url = "";
|
|
126
|
+
originalUrl = "";
|
|
127
|
+
playlist;
|
|
128
|
+
get segmentsCount() {
|
|
129
|
+
return this.playlist?.mediaParts.reduce((sum, part) => sum + part.mediaSegments.length, 0) ?? 0;
|
|
130
|
+
}
|
|
131
|
+
toShortString() {
|
|
132
|
+
let prefixStr = "";
|
|
133
|
+
let returnStr = "";
|
|
134
|
+
const encStr = "";
|
|
135
|
+
const bandwidth = this.bandwidth ? `${this.bandwidth / 1e3} Kbps` : "";
|
|
136
|
+
const channels = this.channels ? `${this.channels}CH` : "";
|
|
137
|
+
if (this.mediaType === MEDIA_TYPES.AUDIO) {
|
|
138
|
+
prefixStr = `Aud ${encStr}`;
|
|
139
|
+
returnStr = [
|
|
140
|
+
this.groupId,
|
|
141
|
+
bandwidth,
|
|
142
|
+
this.name,
|
|
143
|
+
this.codecs,
|
|
144
|
+
this.language,
|
|
145
|
+
channels,
|
|
146
|
+
this.role
|
|
147
|
+
].filter(Boolean).join(" | ");
|
|
148
|
+
} else if (this.mediaType === MEDIA_TYPES.SUBTITLES) {
|
|
149
|
+
prefixStr = `Sub ${encStr}`;
|
|
150
|
+
returnStr = [
|
|
151
|
+
this.groupId,
|
|
152
|
+
this.language,
|
|
153
|
+
this.name,
|
|
154
|
+
this.codecs,
|
|
155
|
+
this.role
|
|
156
|
+
].filter(Boolean).join(" | ");
|
|
157
|
+
} else {
|
|
158
|
+
prefixStr = `Vid ${encStr}`;
|
|
159
|
+
returnStr = [
|
|
160
|
+
this.resolution,
|
|
161
|
+
bandwidth,
|
|
162
|
+
this.groupId,
|
|
163
|
+
this.frameRate,
|
|
164
|
+
this.codecs,
|
|
165
|
+
this.videoRange,
|
|
166
|
+
this.role
|
|
167
|
+
].filter(Boolean).join(" | ");
|
|
168
|
+
}
|
|
169
|
+
returnStr = `${prefixStr} | ${returnStr}`;
|
|
170
|
+
return returnStr.trim();
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
40
174
|
//#endregion
|
|
41
175
|
//#region lib/shared/role-type.ts
|
|
42
176
|
const ROLE_TYPE = {
|
|
@@ -153,23 +287,6 @@ var DefaultHlsContentProcessor = class DefaultHlsContentProcessor {
|
|
|
153
287
|
}
|
|
154
288
|
};
|
|
155
289
|
|
|
156
|
-
//#endregion
|
|
157
|
-
//#region lib/shared/encrypt-info.ts
|
|
158
|
-
var EncryptInfo = class {
|
|
159
|
-
method = ENCRYPT_METHODS.NONE;
|
|
160
|
-
key;
|
|
161
|
-
iv;
|
|
162
|
-
drm;
|
|
163
|
-
constructor(method) {
|
|
164
|
-
this.method = this.parseMethod(method);
|
|
165
|
-
this.drm = {};
|
|
166
|
-
}
|
|
167
|
-
parseMethod(method) {
|
|
168
|
-
if (method) return ENCRYPT_METHODS[method.replace("-", "_")];
|
|
169
|
-
else return ENCRYPT_METHODS.UNKNOWN;
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
-
|
|
173
290
|
//#endregion
|
|
174
291
|
//#region lib/hls/hls-key-processor.ts
|
|
175
292
|
var DefaultHlsKeyProcessor = class {
|
|
@@ -250,78 +367,6 @@ var ParserConfig = class {
|
|
|
250
367
|
keyRetryCount = 3;
|
|
251
368
|
};
|
|
252
369
|
|
|
253
|
-
//#endregion
|
|
254
|
-
//#region lib/shared/stream-spec.ts
|
|
255
|
-
var StreamSpec = class {
|
|
256
|
-
mediaType;
|
|
257
|
-
groupId = null;
|
|
258
|
-
language;
|
|
259
|
-
name;
|
|
260
|
-
default;
|
|
261
|
-
skippedDuration;
|
|
262
|
-
bandwidth;
|
|
263
|
-
codecs = null;
|
|
264
|
-
resolution;
|
|
265
|
-
frameRate;
|
|
266
|
-
channels = null;
|
|
267
|
-
extension = null;
|
|
268
|
-
role;
|
|
269
|
-
videoRange;
|
|
270
|
-
characteristics;
|
|
271
|
-
publishTime;
|
|
272
|
-
audioId;
|
|
273
|
-
videoId;
|
|
274
|
-
subtitleId;
|
|
275
|
-
periodId = null;
|
|
276
|
-
url = "";
|
|
277
|
-
originalUrl = "";
|
|
278
|
-
playlist;
|
|
279
|
-
get segmentsCount() {
|
|
280
|
-
return this.playlist?.mediaParts.reduce((sum, part) => sum + part.mediaSegments.length, 0) ?? 0;
|
|
281
|
-
}
|
|
282
|
-
toShortString() {
|
|
283
|
-
let prefixStr = "";
|
|
284
|
-
let returnStr = "";
|
|
285
|
-
const encStr = "";
|
|
286
|
-
const bandwidth = this.bandwidth ? `${this.bandwidth / 1e3} Kbps` : "";
|
|
287
|
-
const channels = this.channels ? `${this.channels}CH` : "";
|
|
288
|
-
if (this.mediaType === MEDIA_TYPES.AUDIO) {
|
|
289
|
-
prefixStr = `Aud ${encStr}`;
|
|
290
|
-
returnStr = [
|
|
291
|
-
this.groupId,
|
|
292
|
-
bandwidth,
|
|
293
|
-
this.name,
|
|
294
|
-
this.codecs,
|
|
295
|
-
this.language,
|
|
296
|
-
channels,
|
|
297
|
-
this.role
|
|
298
|
-
].filter(Boolean).join(" | ");
|
|
299
|
-
} else if (this.mediaType === MEDIA_TYPES.SUBTITLES) {
|
|
300
|
-
prefixStr = `Sub ${encStr}`;
|
|
301
|
-
returnStr = [
|
|
302
|
-
this.groupId,
|
|
303
|
-
this.language,
|
|
304
|
-
this.name,
|
|
305
|
-
this.codecs,
|
|
306
|
-
this.role
|
|
307
|
-
].filter(Boolean).join(" | ");
|
|
308
|
-
} else {
|
|
309
|
-
prefixStr = `Vid ${encStr}`;
|
|
310
|
-
returnStr = [
|
|
311
|
-
this.resolution,
|
|
312
|
-
bandwidth,
|
|
313
|
-
this.groupId,
|
|
314
|
-
this.frameRate,
|
|
315
|
-
this.codecs,
|
|
316
|
-
this.videoRange,
|
|
317
|
-
this.role
|
|
318
|
-
].filter(Boolean).join(" | ");
|
|
319
|
-
}
|
|
320
|
-
returnStr = `${prefixStr} | ${returnStr}`;
|
|
321
|
-
return returnStr.trim();
|
|
322
|
-
}
|
|
323
|
-
};
|
|
324
|
-
|
|
325
370
|
//#endregion
|
|
326
371
|
//#region lib/dash/dash-tags.ts
|
|
327
372
|
const DASH_TAGS = {
|
|
@@ -400,51 +445,6 @@ var Playlist = class {
|
|
|
400
445
|
mediaParts = [];
|
|
401
446
|
};
|
|
402
447
|
|
|
403
|
-
//#endregion
|
|
404
|
-
//#region lib/shared/media-part.ts
|
|
405
|
-
var MediaPart = class {
|
|
406
|
-
mediaSegments = [];
|
|
407
|
-
constructor(segments) {
|
|
408
|
-
this.mediaSegments = segments || [];
|
|
409
|
-
}
|
|
410
|
-
};
|
|
411
|
-
|
|
412
|
-
//#endregion
|
|
413
|
-
//#region lib/shared/media-segment.ts
|
|
414
|
-
var MediaSegment = class MediaSegment {
|
|
415
|
-
index = NaN;
|
|
416
|
-
duration = NaN;
|
|
417
|
-
title;
|
|
418
|
-
dateTime;
|
|
419
|
-
startRange;
|
|
420
|
-
get stopRange() {
|
|
421
|
-
return this.startRange !== void 0 && this.expectLength !== void 0 ? this.startRange + this.expectLength - 1 : void 0;
|
|
422
|
-
}
|
|
423
|
-
expectLength;
|
|
424
|
-
encryptInfo = new EncryptInfo();
|
|
425
|
-
get isEncrypted() {
|
|
426
|
-
return this.encryptInfo.method !== ENCRYPT_METHODS.NONE;
|
|
427
|
-
}
|
|
428
|
-
url = "";
|
|
429
|
-
nameFromVar;
|
|
430
|
-
equals(segment) {
|
|
431
|
-
if (segment instanceof MediaSegment) return this.index == segment.index && Math.abs(this.duration - segment.duration) < .001 && this.title == segment.title && this.startRange == segment.startRange && this.stopRange == segment.stopRange && this.expectLength == segment.expectLength && this.url == segment.url;
|
|
432
|
-
else return false;
|
|
433
|
-
}
|
|
434
|
-
getHashCode() {
|
|
435
|
-
const payload = [
|
|
436
|
-
this.index,
|
|
437
|
-
this.duration,
|
|
438
|
-
this.title,
|
|
439
|
-
this.startRange,
|
|
440
|
-
this.stopRange,
|
|
441
|
-
this.expectLength,
|
|
442
|
-
this.url
|
|
443
|
-
].join("-");
|
|
444
|
-
return crypto.createHash("md5").update(payload).digest("hex");
|
|
445
|
-
}
|
|
446
|
-
};
|
|
447
|
-
|
|
448
448
|
//#endregion
|
|
449
449
|
//#region lib/dash/dash-utils.ts
|
|
450
450
|
/**
|
|
@@ -1168,4 +1168,4 @@ var StreamExtractor = class {
|
|
|
1168
1168
|
};
|
|
1169
1169
|
|
|
1170
1170
|
//#endregion
|
|
1171
|
-
export { DefaultUrlProcessor, ENCRYPT_METHODS, EXTRACTOR_TYPES, MEDIA_TYPES, ParserConfig, ROLE_TYPE, StreamExtractor };
|
|
1171
|
+
export { DASH_TAGS, DashExtractor, DefaultDashContentProcessor, DefaultHlsContentProcessor, DefaultHlsKeyProcessor, DefaultUrlProcessor, ENCRYPT_METHODS, EXTRACTOR_TYPES, EncryptInfo, HLS_TAGS, HlsExtractor, MEDIA_TYPES, MediaPart, MediaSegment, ParserConfig, ROLE_TYPE, StreamExtractor, StreamSpec, getRange, parseRange };
|
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.4",
|
|
4
4
|
"description": "Streaming manifest parser",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist"
|
|
@@ -73,9 +73,9 @@
|
|
|
73
73
|
"eslint-plugin-prettier": "^5.5.4",
|
|
74
74
|
"globals": "^16.5.0",
|
|
75
75
|
"prettier": "^3.6.2",
|
|
76
|
-
"tsdown": "^0.
|
|
76
|
+
"tsdown": "^0.16.1",
|
|
77
77
|
"typescript": "^5.9.3",
|
|
78
78
|
"typescript-eslint": "^8.46.3",
|
|
79
|
-
"vitest": "^4.0.
|
|
79
|
+
"vitest": "^4.0.8"
|
|
80
80
|
}
|
|
81
81
|
}
|