dasha 2.3.5 → 3.0.0
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/LICENSE +661 -201
- package/README.md +15 -42
- package/dasha.js +11 -3
- package/lib/audio.js +128 -0
- package/lib/dash.js +417 -0
- package/lib/hls.js +142 -0
- package/lib/subtitle.js +119 -0
- package/lib/track.js +66 -0
- package/lib/util.js +96 -0
- package/lib/video.js +158 -0
- package/lib/xml.js +277 -109
- package/package.json +35 -21
- package/types/dasha.d.ts +98 -2
- package/lib/constants.js +0 -16
- package/lib/manifest.js +0 -260
- package/lib/processor.js +0 -99
- package/lib/utils.js +0 -63
- package/types/constants.d.ts +0 -5
- package/types/manifest.d.ts +0 -52
- package/types/processor.d.ts +0 -63
- package/types/utils.d.ts +0 -7
- package/types/xml.d.ts +0 -3
package/lib/manifest.js
DELETED
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { parseXml } = require('./xml');
|
|
4
|
-
const { processManifest } = require('./processor');
|
|
5
|
-
const { getQualityLabel, getHeightByWidth, getClosest, sanitizeBaseUrl } = require('./utils');
|
|
6
|
-
const { CONTENT_TYPE, KEY_SYSTEMS } = require('./constants');
|
|
7
|
-
|
|
8
|
-
const isUrl = (value) => {
|
|
9
|
-
try {
|
|
10
|
-
return !!new URL(value);
|
|
11
|
-
} catch (e) {
|
|
12
|
-
return false;
|
|
13
|
-
}
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
class Manifest {
|
|
17
|
-
constructor(manifest) {
|
|
18
|
-
for (const key of Object.keys(manifest)) this[key] = manifest[key];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
addBaseUrl(value) {
|
|
22
|
-
const sanitizedValue = sanitizeBaseUrl(value);
|
|
23
|
-
if (Array.isArray(this.baseUrls)) this.baseUrls.push(sanitizedValue);
|
|
24
|
-
else this.baseUrls = [sanitizedValue];
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
getVideoTrack(height) {
|
|
28
|
-
const isVideoAdaptationSet = (adaptationSet) =>
|
|
29
|
-
adaptationSet.contentType === CONTENT_TYPE.video ||
|
|
30
|
-
adaptationSet.mimeType?.includes('video') ||
|
|
31
|
-
adaptationSet.maxWidth ||
|
|
32
|
-
adaptationSet.maxHeight;
|
|
33
|
-
const adaptationSets = this.periods
|
|
34
|
-
.map((p) => p.adaptationSets)
|
|
35
|
-
.flat(1)
|
|
36
|
-
.filter(isVideoAdaptationSet);
|
|
37
|
-
const representations = adaptationSets.map((a) => a.representations).flat(2);
|
|
38
|
-
|
|
39
|
-
let suitableRepresentations = [];
|
|
40
|
-
if (height) {
|
|
41
|
-
const resolutions = representations.map((r) => ({
|
|
42
|
-
height: getHeightByWidth(r.width),
|
|
43
|
-
width: r.width,
|
|
44
|
-
}));
|
|
45
|
-
const heights = resolutions.map((r) => r.height);
|
|
46
|
-
const matchedHeight = height ? getClosest(height, heights) : Math.max(...heights);
|
|
47
|
-
const matchedResolution = resolutions.find((r) => r.height === matchedHeight);
|
|
48
|
-
const matchesInHeight = representations.filter((r) => matchedResolution.width === r.width);
|
|
49
|
-
const matchesInId = representations.filter((r) => r.id.includes(height.toString()));
|
|
50
|
-
suitableRepresentations = matchesInHeight?.length ? matchesInHeight : matchesInId;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const representation = this.findBestRepresentation(
|
|
54
|
-
suitableRepresentations.length ? suitableRepresentations : representations
|
|
55
|
-
);
|
|
56
|
-
const adaptationSet = adaptationSets.find((a) => a.representations.includes(representation));
|
|
57
|
-
let codec = 'x264';
|
|
58
|
-
if (representation.codecs.includes('hvc') || representation.codecs.includes('hev'))
|
|
59
|
-
codec = 'x265';
|
|
60
|
-
let bitDepth = '8bit';
|
|
61
|
-
if (representation.codecs.includes('.2.4.')) bitDepth = '10bit';
|
|
62
|
-
if (representation.codecs.includes('.4.16.')) bitDepth = '12bit';
|
|
63
|
-
return {
|
|
64
|
-
id: 0,
|
|
65
|
-
type: CONTENT_TYPE.video,
|
|
66
|
-
pssh: this.getWidevinePssh(adaptationSet),
|
|
67
|
-
licenseUrl: this.getWidevineLicenseUrl(adaptationSet),
|
|
68
|
-
segments: this.getSegments(adaptationSet, representation),
|
|
69
|
-
bitrate: Math.round(representation.bandwidth / 1000), // Kbps
|
|
70
|
-
size: Math.ceil((representation.bandwidth / 8e6) * this.mediaPresentationDuration), // MB
|
|
71
|
-
width: representation.width,
|
|
72
|
-
height: representation.height,
|
|
73
|
-
qualityLabel: getQualityLabel(representation.width),
|
|
74
|
-
hevc: representation.codecs.includes('hvc') || representation.codecs.includes('hev'),
|
|
75
|
-
codec,
|
|
76
|
-
codecs: representation.codecs,
|
|
77
|
-
bitDepth,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
getAudioTracks(languages) {
|
|
82
|
-
const filterAudio = (adaptationSet) =>
|
|
83
|
-
(adaptationSet.contentType === CONTENT_TYPE.audio ||
|
|
84
|
-
adaptationSet.mimeType?.includes('audio') ||
|
|
85
|
-
adaptationSet.representations.some((r) => r.mimeType?.includes('audio'))) &&
|
|
86
|
-
!adaptationSet.maxWidth;
|
|
87
|
-
const filterLanguages = (adaptationSet) =>
|
|
88
|
-
languages?.length ? languages.some((lang) => adaptationSet.lang?.includes(lang)) : true;
|
|
89
|
-
const adaptationSets = this.periods
|
|
90
|
-
.map((p) => p.adaptationSets)
|
|
91
|
-
.flat(1)
|
|
92
|
-
.filter(filterAudio)
|
|
93
|
-
.filter(filterLanguages);
|
|
94
|
-
|
|
95
|
-
const tracks = [];
|
|
96
|
-
for (const adaptationSet of adaptationSets) {
|
|
97
|
-
const representation = this.findBestRepresentation(adaptationSet.representations);
|
|
98
|
-
let codec = '';
|
|
99
|
-
const track = {
|
|
100
|
-
id: tracks.length,
|
|
101
|
-
type: CONTENT_TYPE.audio,
|
|
102
|
-
label: adaptationSet.label,
|
|
103
|
-
language: adaptationSet.lang,
|
|
104
|
-
pssh: this.getWidevinePssh(adaptationSet),
|
|
105
|
-
licenseUrl: this.getWidevineLicenseUrl(adaptationSet),
|
|
106
|
-
segments: this.getSegments(adaptationSet, representation),
|
|
107
|
-
bitrate: Math.round(representation.bandwidth / 1000), // Kbps
|
|
108
|
-
size: Math.ceil((representation.bandwidth / 8e6) * this.mediaPresentationDuration), // MB
|
|
109
|
-
audioSamplingRate: representation.audioSamplingRate / 1000,
|
|
110
|
-
codecs: representation.codecs,
|
|
111
|
-
};
|
|
112
|
-
tracks.push(track);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return tracks;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
getSubtitleTracks(languages) {
|
|
119
|
-
const isSubtitleAdaptationSet = (adaptationSet) =>
|
|
120
|
-
(adaptationSet.contentType === CONTENT_TYPE.text ||
|
|
121
|
-
adaptationSet.mimeType?.includes('text')) &&
|
|
122
|
-
!adaptationSet.maxWidth;
|
|
123
|
-
const adaptationSets = this.periods
|
|
124
|
-
.map((p) => p.adaptationSets)
|
|
125
|
-
.flat(1)
|
|
126
|
-
.filter(isSubtitleAdaptationSet);
|
|
127
|
-
|
|
128
|
-
const representations = adaptationSets.map((a) => a.representations).flat(2);
|
|
129
|
-
const matches = representations.filter((r) =>
|
|
130
|
-
languages?.length ? languages?.some((lang) => r.lang.includes(lang)) : true
|
|
131
|
-
);
|
|
132
|
-
const selectedRepresentations = matches?.length ? matches : representations;
|
|
133
|
-
|
|
134
|
-
const tracks = [];
|
|
135
|
-
for (const representation of selectedRepresentations) {
|
|
136
|
-
const adaptationSet = adaptationSets.find((a) => a.representations.includes(representation));
|
|
137
|
-
const baseUrl = this.baseUrls?.[0];
|
|
138
|
-
const representationBaseUrl = representation.baseUrls?.[0];
|
|
139
|
-
const url = isUrl(representationBaseUrl)
|
|
140
|
-
? representationBaseUrl
|
|
141
|
-
: `${baseUrl || ''}${representationBaseUrl || ''}`;
|
|
142
|
-
const track = {
|
|
143
|
-
id: tracks.length,
|
|
144
|
-
type: CONTENT_TYPE.text,
|
|
145
|
-
label: adaptationSet.label,
|
|
146
|
-
language: adaptationSet.lang,
|
|
147
|
-
format: representation.mimeType?.split('/')[1] || adaptationSet.mimeType?.split('/')[1],
|
|
148
|
-
segments: [{ url }],
|
|
149
|
-
bitrate: Math.round(representation.bandwidth / 1000), // Kbps
|
|
150
|
-
size: Math.ceil((representation.bandwidth / 8e6) * this.mediaPresentationDuration), // MB
|
|
151
|
-
};
|
|
152
|
-
tracks.push(track);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return tracks;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
getWidevinePssh(adaptationSet) {
|
|
159
|
-
const isWidevineProtection = (protection) =>
|
|
160
|
-
protection.schemeIdUri === KEY_SYSTEMS['com.widevine.alpha'];
|
|
161
|
-
return adaptationSet?.contentProtections?.find(isWidevineProtection)?.cencPssh || null;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
getWidevineLicenseUrl(adaptationSet) {
|
|
165
|
-
const isWidevineProtection = (protection) =>
|
|
166
|
-
protection.schemeIdUri === KEY_SYSTEMS['com.widevine.alpha'];
|
|
167
|
-
return adaptationSet?.contentProtections?.find(isWidevineProtection)?.licenseUrl || null;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
findBestRepresentation(representations) {
|
|
171
|
-
const bandwidths = representations.map((r) => r.bandwidth);
|
|
172
|
-
const maxBandwidth = Math.max(...bandwidths);
|
|
173
|
-
return representations.find((r) => r.bandwidth === maxBandwidth);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
getSegments(adaptationSet, representation) {
|
|
177
|
-
const segments = [];
|
|
178
|
-
const segmentTemplate = representation.segmentTemplate || adaptationSet.segmentTemplate;
|
|
179
|
-
const baseUrls = representation.baseUrls || this.baseUrls;
|
|
180
|
-
const isBaseUrlRequired = !segmentTemplate.media.includes(`https://`);
|
|
181
|
-
const baseUrl = baseUrls?.[0];
|
|
182
|
-
const initTemplate = segmentTemplate.initialization;
|
|
183
|
-
const initUrl = initTemplate
|
|
184
|
-
?.replace(/\$Bandwidth\$/i, representation.bandwidth)
|
|
185
|
-
?.replace(/\$RepresentationID\$/i, representation.id);
|
|
186
|
-
const initFullUrl = isBaseUrlRequired ? baseUrl + initUrl : initUrl;
|
|
187
|
-
if (initUrl) segments.push({ url: initFullUrl, init: true });
|
|
188
|
-
|
|
189
|
-
const mediaTemplate = segmentTemplate.media.replace(/\$Bandwidth\$/i, representation.bandwidth);
|
|
190
|
-
let time = 0;
|
|
191
|
-
let index = parseInt(segmentTemplate.startNumber || segmentTemplate.start) || 0;
|
|
192
|
-
|
|
193
|
-
let s = segmentTemplate['SegmentTimeline']?.['S'];
|
|
194
|
-
if (!!s && !Array.isArray(segmentTemplate['SegmentTimeline']?.['S']))
|
|
195
|
-
s = [segmentTemplate['SegmentTimeline']?.['S']];
|
|
196
|
-
|
|
197
|
-
for (let i = 0; i < (s?.length || segmentTemplate.duration); i++) {
|
|
198
|
-
const segment = s?.[i] || {};
|
|
199
|
-
const repeats = parseInt(segment.r || '0') + 1;
|
|
200
|
-
if (segment.t) time = parseInt(segment.t);
|
|
201
|
-
const duration = parseInt(segment.d || segmentTemplate.timescale || '0');
|
|
202
|
-
for (let i = 0; i < repeats; i++) {
|
|
203
|
-
const url = mediaTemplate
|
|
204
|
-
?.replace(/\$Number\$/i, index + '')
|
|
205
|
-
?.replace(/\$Time\$/i, time + '')
|
|
206
|
-
?.replace(/\$RepresentationID\$/i, representation.id);
|
|
207
|
-
const fullUrl = isBaseUrlRequired ? baseUrl + url : url;
|
|
208
|
-
segments.push({ url: fullUrl });
|
|
209
|
-
index++;
|
|
210
|
-
time += duration;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return segments;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
getPssh(schemeIdUri = KEY_SYSTEMS['com.widevine.alpha']) {
|
|
218
|
-
let pssh = null;
|
|
219
|
-
for (const period of this.periods)
|
|
220
|
-
for (const adaptationSet of period.adaptationSets) {
|
|
221
|
-
for (const representation of adaptationSet.representations) {
|
|
222
|
-
if (!representation.contentProtections) continue;
|
|
223
|
-
for (const contentProtection of representation.contentProtections)
|
|
224
|
-
if (contentProtection.cencPssh && contentProtection.schemeIdUri === schemeIdUri) {
|
|
225
|
-
pssh = contentProtection.cencPssh;
|
|
226
|
-
break;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
if (!adaptationSet.contentProtections) continue;
|
|
230
|
-
for (const contentProtection of adaptationSet.contentProtections)
|
|
231
|
-
if (contentProtection.cencPssh && contentProtection.schemeIdUri === schemeIdUri) {
|
|
232
|
-
pssh = contentProtection.cencPssh;
|
|
233
|
-
break;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
return pssh;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
getLicenseUrl(schemeIdUri = KEY_SYSTEMS['com.widevine.alpha']) {
|
|
240
|
-
let licenseUrl = null;
|
|
241
|
-
for (const period of this.periods)
|
|
242
|
-
for (const adaptationSet of period.adaptationSets) {
|
|
243
|
-
if (!adaptationSet.contentProtections) continue;
|
|
244
|
-
for (const contentProtection of adaptationSet.contentProtections)
|
|
245
|
-
if (contentProtection.licenseUrl && contentProtection.schemeIdUri === schemeIdUri) {
|
|
246
|
-
licenseUrl = contentProtection.licenseUrl;
|
|
247
|
-
break;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
return licenseUrl;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const parseManifest = (text) => {
|
|
255
|
-
const parsedXml = parseXml(text);
|
|
256
|
-
const manifest = processManifest(parsedXml);
|
|
257
|
-
return manifest ? new Manifest(manifest) : null;
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
module.exports = { parseManifest };
|
package/lib/processor.js
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
const { parseDuration, sanitizeBaseUrl } = require('./utils');
|
|
2
|
-
|
|
3
|
-
const processElement = (element, type) => {
|
|
4
|
-
if (!element) return;
|
|
5
|
-
switch (type) {
|
|
6
|
-
case 'array':
|
|
7
|
-
return Array.isArray(element) ? element : [element];
|
|
8
|
-
case 'number':
|
|
9
|
-
return parseInt(element);
|
|
10
|
-
case 'duration':
|
|
11
|
-
return parseDuration(element);
|
|
12
|
-
default:
|
|
13
|
-
return element;
|
|
14
|
-
}
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
const processRepresentation = (representation) => {
|
|
18
|
-
const contentProtectionsList = processElement(representation?.['ContentProtection'], 'array');
|
|
19
|
-
const contentProtections = contentProtectionsList?.map((c) => processContentProtection(c));
|
|
20
|
-
return {
|
|
21
|
-
id: representation['id'],
|
|
22
|
-
baseUrls: processElement(representation['BaseURL'], 'array')?.map((url) =>
|
|
23
|
-
sanitizeBaseUrl(url.value)
|
|
24
|
-
),
|
|
25
|
-
codecs: representation['codecs'],
|
|
26
|
-
bandwidth: processElement(representation['bandwidth'], 'number'),
|
|
27
|
-
frameRate: representation['frameRate'],
|
|
28
|
-
mimeType: representation['mimeType'],
|
|
29
|
-
audioSamplingRate: processElement(representation['audioSamplingRate'], 'number'),
|
|
30
|
-
width: processElement(representation['width'], 'number'),
|
|
31
|
-
height: processElement(representation['height'], 'number'),
|
|
32
|
-
sar: representation['sar'],
|
|
33
|
-
startWithSAP: representation['startWithSAP'],
|
|
34
|
-
segmentTemplate: representation['SegmentTemplate'],
|
|
35
|
-
contentProtections,
|
|
36
|
-
};
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const processContentProtection = (contentProtection) => {
|
|
40
|
-
return {
|
|
41
|
-
schemeIdUri: contentProtection['schemeIdUri'],
|
|
42
|
-
value: contentProtection['value'],
|
|
43
|
-
cencDefaultKid: contentProtection['cenc:default_KID'],
|
|
44
|
-
cencPssh: contentProtection['cenc:pssh']?.['value'],
|
|
45
|
-
msprPro: contentProtection['mspr:pro']?.['value'],
|
|
46
|
-
licenseUrl: contentProtection['ms:laurl']?.['licenseUrl'],
|
|
47
|
-
};
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const processAdaptationSet = (adaptationSet) => {
|
|
51
|
-
const contentProtectionsList = processElement(adaptationSet?.['ContentProtection'], 'array');
|
|
52
|
-
const contentProtections = contentProtectionsList?.map((c) => processContentProtection(c));
|
|
53
|
-
const representationsList = processElement(adaptationSet['Representation'], 'array');
|
|
54
|
-
const representations = representationsList.map((r) => processRepresentation(r));
|
|
55
|
-
return {
|
|
56
|
-
id: adaptationSet['id'],
|
|
57
|
-
group: processElement(adaptationSet['group'], 'number'),
|
|
58
|
-
baseUrls: processElement(adaptationSet['BaseURL'], 'array')?.map((url) =>
|
|
59
|
-
sanitizeBaseUrl(url.value)
|
|
60
|
-
),
|
|
61
|
-
segmentAlignment: adaptationSet['segmentAlignment'],
|
|
62
|
-
lang: adaptationSet['lang'],
|
|
63
|
-
maxWidth: processElement(adaptationSet['maxWidth'], 'number'),
|
|
64
|
-
maxHeight: processElement(adaptationSet['maxHeight'], 'number'),
|
|
65
|
-
maxFrameRate: adaptationSet['maxFrameRate'],
|
|
66
|
-
contentType: adaptationSet['contentType'],
|
|
67
|
-
segmentTemplate: adaptationSet['SegmentTemplate'],
|
|
68
|
-
mimeType: adaptationSet['mimeType'],
|
|
69
|
-
label: adaptationSet['label'],
|
|
70
|
-
contentProtections,
|
|
71
|
-
representations,
|
|
72
|
-
};
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
const processPeriod = (period) => {
|
|
76
|
-
const adaptationSetsList = processElement(period['AdaptationSet'], 'array');
|
|
77
|
-
const adaptationSets = adaptationSetsList.map((a) => processAdaptationSet(a));
|
|
78
|
-
return {
|
|
79
|
-
id: processElement(period['id'], 'number'),
|
|
80
|
-
start: processElement(period['start'], 'duration'),
|
|
81
|
-
duration: processElement(period['duration'], 'duration'),
|
|
82
|
-
adaptationSets,
|
|
83
|
-
};
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
const processManifest = (parsedXml) => {
|
|
87
|
-
const mpd = parsedXml.MPD;
|
|
88
|
-
const periodsList = processElement(mpd['Period'], 'array');
|
|
89
|
-
if (!periodsList.length) return null;
|
|
90
|
-
return {
|
|
91
|
-
periods: periodsList.map((p) => processPeriod(p)),
|
|
92
|
-
baseUrls: processElement(mpd['BaseURL'], 'array')?.map((url) => sanitizeBaseUrl(url.value)),
|
|
93
|
-
locations: processElement(mpd['Location'], 'array'),
|
|
94
|
-
profiles: mpd['profiles'],
|
|
95
|
-
mediaPresentationDuration: processElement(mpd['mediaPresentationDuration'], 'duration'),
|
|
96
|
-
};
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
module.exports = { processManifest };
|
package/lib/utils.js
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Parses an XML duration string.
|
|
3
|
-
* @param {string} durationString The duration string, e.g., "PT1H3M43.2S",
|
|
4
|
-
* which means 1 hour, 3 minutes, and 43.2 seconds.
|
|
5
|
-
* @return {?number} The parsed duration in seconds on success; otherwise,
|
|
6
|
-
* return null.
|
|
7
|
-
* @see {@link http://www.datypic.com/sc/xsd/t-xsd_duration.html}
|
|
8
|
-
*/
|
|
9
|
-
const parseDuration = (durationString) => {
|
|
10
|
-
if (!durationString) return null;
|
|
11
|
-
|
|
12
|
-
const re =
|
|
13
|
-
'^P(?:([0-9]*)Y)?(?:([0-9]*)M)?(?:([0-9]*)D)?' +
|
|
14
|
-
'(?:T(?:([0-9]*)H)?(?:([0-9]*)M)?(?:([0-9.]*)S)?)?$';
|
|
15
|
-
const matches = new RegExp(re).exec(durationString);
|
|
16
|
-
if (!matches) return null;
|
|
17
|
-
|
|
18
|
-
// Note: Number(null) == 0 but Number(undefined) == NaN.
|
|
19
|
-
const years = Number(matches[1] || null);
|
|
20
|
-
const months = Number(matches[2] || null);
|
|
21
|
-
const days = Number(matches[3] || null);
|
|
22
|
-
const hours = Number(matches[4] || null);
|
|
23
|
-
const minutes = Number(matches[5] || null);
|
|
24
|
-
const seconds = Number(matches[6] || null);
|
|
25
|
-
|
|
26
|
-
// Assume a year always has 365 days and a month always has 30 days.
|
|
27
|
-
const d =
|
|
28
|
-
60 * 60 * 24 * 365 * years +
|
|
29
|
-
60 * 60 * 24 * 30 * months +
|
|
30
|
-
60 * 60 * 24 * days +
|
|
31
|
-
60 * 60 * hours +
|
|
32
|
-
60 * minutes +
|
|
33
|
-
seconds;
|
|
34
|
-
return isFinite(d) ? d : null;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const getHeightByWidth = (videoWidth) => {
|
|
38
|
-
const width = typeof videoWidth === 'number' ? videoWidth : parseInt(videoWidth);
|
|
39
|
-
if (isNaN(width)) return;
|
|
40
|
-
let height = 144;
|
|
41
|
-
if (width >= 426) height = 240;
|
|
42
|
-
if (width >= 640) height = 360;
|
|
43
|
-
if (width >= 854) height = 480;
|
|
44
|
-
if (width >= 1024) height = 576;
|
|
45
|
-
if (width >= 1280) height = 720;
|
|
46
|
-
if (width >= 1920) height = 1080;
|
|
47
|
-
if (width >= 3840) height = 2160;
|
|
48
|
-
if (width >= 7680) height = 4320;
|
|
49
|
-
return height;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
const getClosest = (value, array) =>
|
|
53
|
-
array.reduce((prev, curr) => (Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev));
|
|
54
|
-
|
|
55
|
-
const getQualityLabel = (videoWidth) => getHeightByWidth(videoWidth) + 'p';
|
|
56
|
-
|
|
57
|
-
const sanitizeBaseUrl = (value) => {
|
|
58
|
-
if (!value.includes('.mpd')) return value;
|
|
59
|
-
const manifestFilename = value.split('.mpd')[0].split('/').at(-1) + '.mpd';
|
|
60
|
-
return value.replace(manifestFilename, '');
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
module.exports = { parseDuration, getHeightByWidth, getQualityLabel, getClosest, sanitizeBaseUrl };
|
package/types/constants.d.ts
DELETED
package/types/manifest.d.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { AdaptationSet, ProcessedManifest, Representation } from './processor';
|
|
2
|
-
import { QualityLabel } from './utils';
|
|
3
|
-
|
|
4
|
-
declare class Manifest extends ProcessedManifest {
|
|
5
|
-
constructor(manifest: ProcessedManifest);
|
|
6
|
-
|
|
7
|
-
getVideoTrack(height: number): VideoTrack;
|
|
8
|
-
getAudioTracks(languages: string[]): AudioTrack[];
|
|
9
|
-
getSubtitleTracks(languages: string[]): SubtitleTrack[];
|
|
10
|
-
getWidevinePssh(adaptationSet: AdaptationSet): string | null;
|
|
11
|
-
getWidevineLicenseUrl(adaptationSet: AdaptationSet): string | null;
|
|
12
|
-
findBestRepresentation(representations: Representation[]): Representation;
|
|
13
|
-
getSegments(adaptationSet: AdaptationSet, representation: Representation): Segment[];
|
|
14
|
-
getPssh(): string | null;
|
|
15
|
-
getLicenseUrl(): string | null;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface MediaTrack {
|
|
19
|
-
type: 'video' | 'audio' | 'text';
|
|
20
|
-
segments: Segment[];
|
|
21
|
-
bitrate: number; // Kbps
|
|
22
|
-
size: number; // MB
|
|
23
|
-
pssh: string | null;
|
|
24
|
-
licenseUrl: string | null;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
interface VideoTrack extends MediaTrack {
|
|
28
|
-
type: 'video';
|
|
29
|
-
width: number;
|
|
30
|
-
height: number;
|
|
31
|
-
quality: QualityLabel;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
interface AudioTrack extends MediaTrack {
|
|
35
|
-
type: 'audio';
|
|
36
|
-
audioSampleRate: number;
|
|
37
|
-
language: string;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
interface SubtitleTrack extends MediaTrack {
|
|
41
|
-
type: 'text';
|
|
42
|
-
language: string;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
interface Segment {
|
|
46
|
-
url: string;
|
|
47
|
-
init?: boolean;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
declare const parseManifest: (text: string) => Manifest | null;
|
|
51
|
-
|
|
52
|
-
export { parseManifest, Manifest, VideoTrack, AudioTrack, SubtitleTrack };
|
package/types/processor.d.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
interface ProcessedManifest {
|
|
2
|
-
periods: Period[];
|
|
3
|
-
baseUrls: string[];
|
|
4
|
-
locations: string[];
|
|
5
|
-
profiles: string;
|
|
6
|
-
mediaPresentationDuration: number;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
interface Period {
|
|
10
|
-
id: number;
|
|
11
|
-
start: number;
|
|
12
|
-
duration: number;
|
|
13
|
-
adaptationSets: AdaptationSet[];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
interface AdaptationSet {
|
|
17
|
-
id: string;
|
|
18
|
-
group: number;
|
|
19
|
-
baseUrls: string[];
|
|
20
|
-
segmentAlignment: string;
|
|
21
|
-
lang: string;
|
|
22
|
-
maxWidth: number;
|
|
23
|
-
maxHeight: number;
|
|
24
|
-
maxFrameRate: string;
|
|
25
|
-
contentType: string;
|
|
26
|
-
segmentTemplate: Record<string, any>;
|
|
27
|
-
contentProtections: ContentProtection[];
|
|
28
|
-
representations: Representation[];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
interface Representation {
|
|
32
|
-
id: string;
|
|
33
|
-
baseUrls: string[]; // TODO: Check type
|
|
34
|
-
codecs: string;
|
|
35
|
-
bandwidth: number;
|
|
36
|
-
frameRate: string;
|
|
37
|
-
mimeType: string;
|
|
38
|
-
audioSamplingRate: number;
|
|
39
|
-
width: number;
|
|
40
|
-
height: number;
|
|
41
|
-
sar: string;
|
|
42
|
-
startWithSAP: string;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
interface ContentProtection {
|
|
46
|
-
schemeIdUri: string;
|
|
47
|
-
value: string;
|
|
48
|
-
cencDefaultKid: string;
|
|
49
|
-
cencPssh: string;
|
|
50
|
-
msprPro: string;
|
|
51
|
-
licenseUrl: string;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
declare const processManifest: (parsedXml: Record<string, any>) => ProcessedManifest;
|
|
55
|
-
|
|
56
|
-
export {
|
|
57
|
-
processManifest,
|
|
58
|
-
ProcessedManifest,
|
|
59
|
-
Period,
|
|
60
|
-
AdaptationSet,
|
|
61
|
-
Representation,
|
|
62
|
-
ContentProtection,
|
|
63
|
-
};
|
package/types/utils.d.ts
DELETED
package/types/xml.d.ts
DELETED