dasha 2.3.1 → 2.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/manifest.js +51 -14
- package/lib/processor.js +11 -4
- package/lib/utils.js +10 -1
- package/package.json +1 -1
- package/types/manifest.d.ts +6 -2
package/lib/manifest.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { parseXml } = require('./xml');
|
|
4
4
|
const { processManifest } = require('./processor');
|
|
5
|
-
const { getQualityLabel, getHeightByWidth } = require('./utils');
|
|
5
|
+
const { getQualityLabel, getHeightByWidth, getClosest, sanitizeBaseUrl } = require('./utils');
|
|
6
6
|
const { CONTENT_TYPE, KEY_SYSTEMS } = require('./constants');
|
|
7
7
|
|
|
8
8
|
const isUrl = (value) => {
|
|
@@ -19,8 +19,9 @@ class Manifest {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
addBaseUrl(value) {
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
const sanitizedValue = sanitizeBaseUrl(value);
|
|
23
|
+
if (Array.isArray(this.baseUrls)) this.baseUrls.push(sanitizedValue);
|
|
24
|
+
else this.baseUrls = [sanitizedValue];
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
getVideoTrack(height) {
|
|
@@ -34,11 +35,31 @@ class Manifest {
|
|
|
34
35
|
.flat(1)
|
|
35
36
|
.filter(isVideoAdaptationSet);
|
|
36
37
|
const representations = adaptationSets.map((a) => a.representations).flat(2);
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
+
);
|
|
41
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';
|
|
42
63
|
return {
|
|
43
64
|
id: 0,
|
|
44
65
|
type: CONTENT_TYPE.video,
|
|
@@ -50,6 +71,10 @@ class Manifest {
|
|
|
50
71
|
width: representation.width,
|
|
51
72
|
height: representation.height,
|
|
52
73
|
qualityLabel: getQualityLabel(representation.width),
|
|
74
|
+
hevc: representation.codecs.includes('hvc') || representation.codecs.includes('hev'),
|
|
75
|
+
codec,
|
|
76
|
+
codecs: representation.codecs,
|
|
77
|
+
bitDepth,
|
|
53
78
|
};
|
|
54
79
|
}
|
|
55
80
|
|
|
@@ -70,6 +95,7 @@ class Manifest {
|
|
|
70
95
|
const tracks = [];
|
|
71
96
|
for (const adaptationSet of adaptationSets) {
|
|
72
97
|
const representation = this.findBestRepresentation(adaptationSet.representations);
|
|
98
|
+
let codec = '';
|
|
73
99
|
const track = {
|
|
74
100
|
id: tracks.length,
|
|
75
101
|
type: CONTENT_TYPE.audio,
|
|
@@ -80,7 +106,8 @@ class Manifest {
|
|
|
80
106
|
segments: this.getSegments(adaptationSet, representation),
|
|
81
107
|
bitrate: Math.round(representation.bandwidth / 1000), // Kbps
|
|
82
108
|
size: Math.ceil((representation.bandwidth / 8e6) * this.mediaPresentationDuration), // MB
|
|
83
|
-
|
|
109
|
+
audioSamplingRate: representation.audioSamplingRate / 1000,
|
|
110
|
+
codecs: representation.codecs,
|
|
84
111
|
};
|
|
85
112
|
tracks.push(track);
|
|
86
113
|
}
|
|
@@ -107,8 +134,8 @@ class Manifest {
|
|
|
107
134
|
const tracks = [];
|
|
108
135
|
for (const representation of selectedRepresentations) {
|
|
109
136
|
const adaptationSet = adaptationSets.find((a) => a.representations.includes(representation));
|
|
110
|
-
const baseUrl = this.baseUrls?.[0]
|
|
111
|
-
const representationBaseUrl = representation.baseUrls?.[0]
|
|
137
|
+
const baseUrl = this.baseUrls?.[0];
|
|
138
|
+
const representationBaseUrl = representation.baseUrls?.[0];
|
|
112
139
|
const url = isUrl(representationBaseUrl)
|
|
113
140
|
? representationBaseUrl
|
|
114
141
|
: `${baseUrl || ''}${representationBaseUrl || ''}`;
|
|
@@ -151,7 +178,7 @@ class Manifest {
|
|
|
151
178
|
const segmentTemplate = representation.segmentTemplate || adaptationSet.segmentTemplate;
|
|
152
179
|
const baseUrls = representation.baseUrls || this.baseUrls;
|
|
153
180
|
const isBaseUrlRequired = !segmentTemplate.media.includes(`https://`);
|
|
154
|
-
const baseUrl = baseUrls?.
|
|
181
|
+
const baseUrl = baseUrls?.[0];
|
|
155
182
|
const initTemplate = segmentTemplate.initialization;
|
|
156
183
|
const initUrl = initTemplate
|
|
157
184
|
?.replace(/\$Bandwidth\$/i, representation.bandwidth)
|
|
@@ -163,10 +190,12 @@ class Manifest {
|
|
|
163
190
|
let time = 0;
|
|
164
191
|
let index = parseInt(segmentTemplate.startNumber) || 0;
|
|
165
192
|
|
|
166
|
-
|
|
167
|
-
|
|
193
|
+
let s = segmentTemplate['SegmentTimeline']?.['S'];
|
|
194
|
+
if (!!s && !Array.isArray(segmentTemplate['SegmentTimeline']?.['S']))
|
|
195
|
+
s = [segmentTemplate['SegmentTimeline']?.['S']];
|
|
168
196
|
|
|
169
|
-
for (
|
|
197
|
+
for (let i = segmentTemplate.start || 1; i < (s?.length || segmentTemplate.duration); i++) {
|
|
198
|
+
const segment = s?.[i] || {};
|
|
170
199
|
const repeats = parseInt(segment.r || '0') + 1;
|
|
171
200
|
if (segment.t) time = parseInt(segment.t);
|
|
172
201
|
const duration = parseInt(segment.d || segmentTemplate.timescale || '0');
|
|
@@ -189,6 +218,14 @@ class Manifest {
|
|
|
189
218
|
let pssh = null;
|
|
190
219
|
for (const period of this.periods)
|
|
191
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
|
+
}
|
|
192
229
|
if (!adaptationSet.contentProtections) continue;
|
|
193
230
|
for (const contentProtection of adaptationSet.contentProtections)
|
|
194
231
|
if (contentProtection.cencPssh && contentProtection.schemeIdUri === schemeIdUri) {
|
package/lib/processor.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { parseDuration } = require('./utils');
|
|
1
|
+
const { parseDuration, sanitizeBaseUrl } = require('./utils');
|
|
2
2
|
|
|
3
3
|
const processElement = (element, type) => {
|
|
4
4
|
if (!element) return;
|
|
@@ -15,9 +15,13 @@ const processElement = (element, type) => {
|
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
const processRepresentation = (representation) => {
|
|
18
|
+
const contentProtectionsList = processElement(representation?.['ContentProtection'], 'array');
|
|
19
|
+
const contentProtections = contentProtectionsList?.map((c) => processContentProtection(c));
|
|
18
20
|
return {
|
|
19
21
|
id: representation['id'],
|
|
20
|
-
baseUrls: processElement(representation['BaseURL'], 'array')
|
|
22
|
+
baseUrls: processElement(representation['BaseURL'], 'array')?.map((url) =>
|
|
23
|
+
sanitizeBaseUrl(url.value)
|
|
24
|
+
),
|
|
21
25
|
codecs: representation['codecs'],
|
|
22
26
|
bandwidth: processElement(representation['bandwidth'], 'number'),
|
|
23
27
|
frameRate: representation['frameRate'],
|
|
@@ -28,6 +32,7 @@ const processRepresentation = (representation) => {
|
|
|
28
32
|
sar: representation['sar'],
|
|
29
33
|
startWithSAP: representation['startWithSAP'],
|
|
30
34
|
segmentTemplate: representation['SegmentTemplate'],
|
|
35
|
+
contentProtections,
|
|
31
36
|
};
|
|
32
37
|
};
|
|
33
38
|
|
|
@@ -50,7 +55,9 @@ const processAdaptationSet = (adaptationSet) => {
|
|
|
50
55
|
return {
|
|
51
56
|
id: adaptationSet['id'],
|
|
52
57
|
group: processElement(adaptationSet['group'], 'number'),
|
|
53
|
-
baseUrls: processElement(adaptationSet['BaseURL'], 'array')
|
|
58
|
+
baseUrls: processElement(adaptationSet['BaseURL'], 'array')?.map((url) =>
|
|
59
|
+
sanitizeBaseUrl(url.value)
|
|
60
|
+
),
|
|
54
61
|
segmentAlignment: adaptationSet['segmentAlignment'],
|
|
55
62
|
lang: adaptationSet['lang'],
|
|
56
63
|
maxWidth: processElement(adaptationSet['maxWidth'], 'number'),
|
|
@@ -82,7 +89,7 @@ const processManifest = (parsedXml) => {
|
|
|
82
89
|
if (!periodsList.length) return null;
|
|
83
90
|
return {
|
|
84
91
|
periods: periodsList.map((p) => processPeriod(p)),
|
|
85
|
-
baseUrls: processElement(mpd['BaseURL'], 'array'),
|
|
92
|
+
baseUrls: processElement(mpd['BaseURL'], 'array')?.map((url) => sanitizeBaseUrl(url.value)),
|
|
86
93
|
locations: processElement(mpd['Location'], 'array'),
|
|
87
94
|
profiles: mpd['profiles'],
|
|
88
95
|
mediaPresentationDuration: processElement(mpd['mediaPresentationDuration'], 'duration'),
|
package/lib/utils.js
CHANGED
|
@@ -49,6 +49,15 @@ const getHeightByWidth = (videoWidth) => {
|
|
|
49
49
|
return height;
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
+
const getClosest = (value, array) =>
|
|
53
|
+
array.reduce((prev, curr) => (Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev));
|
|
54
|
+
|
|
52
55
|
const getQualityLabel = (videoWidth) => getHeightByWidth(videoWidth) + 'p';
|
|
53
56
|
|
|
54
|
-
|
|
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/package.json
CHANGED
package/types/manifest.d.ts
CHANGED
|
@@ -34,9 +34,13 @@ interface VideoTrack extends MediaTrack {
|
|
|
34
34
|
interface AudioTrack extends MediaTrack {
|
|
35
35
|
type: 'audio';
|
|
36
36
|
audioSampleRate: number;
|
|
37
|
+
language: string;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
interface SubtitleTrack {
|
|
40
|
+
interface SubtitleTrack extends MediaTrack {
|
|
41
|
+
type: 'text';
|
|
42
|
+
language: string;
|
|
43
|
+
}
|
|
40
44
|
|
|
41
45
|
interface Segment {
|
|
42
46
|
url: string;
|
|
@@ -45,4 +49,4 @@ interface Segment {
|
|
|
45
49
|
|
|
46
50
|
declare const parseManifest: (text: string) => Manifest | null;
|
|
47
51
|
|
|
48
|
-
export { parseManifest, Manifest, VideoTrack, AudioTrack };
|
|
52
|
+
export { parseManifest, Manifest, VideoTrack, AudioTrack, SubtitleTrack };
|