segmentTemplate.js
6.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
import resolveUrl from '@videojs/vhs-utils/es/resolve-url';
import urlTypeToSegment from './urlType';
import { parseByTimeline } from './timelineTimeParser';
import { parseByDuration } from './durationTimeParser';
const identifierPattern = /\$([A-z]*)(?:(%0)([0-9]+)d)?\$/g;
/**
* Replaces template identifiers with corresponding values. To be used as the callback
* for String.prototype.replace
*
* @name replaceCallback
* @function
* @param {string} match
* Entire match of identifier
* @param {string} identifier
* Name of matched identifier
* @param {string} format
* Format tag string. Its presence indicates that padding is expected
* @param {string} width
* Desired length of the replaced value. Values less than this width shall be left
* zero padded
* @return {string}
* Replacement for the matched identifier
*/
/**
* Returns a function to be used as a callback for String.prototype.replace to replace
* template identifiers
*
* @param {Obect} values
* Object containing values that shall be used to replace known identifiers
* @param {number} values.RepresentationID
* Value of the Representation@id attribute
* @param {number} values.Number
* Number of the corresponding segment
* @param {number} values.Bandwidth
* Value of the Representation@bandwidth attribute.
* @param {number} values.Time
* Timestamp value of the corresponding segment
* @return {replaceCallback}
* Callback to be used with String.prototype.replace to replace identifiers
*/
export const identifierReplacement = (values) => (match, identifier, format, width) => {
if (match === '$$') {
// escape sequence
return '$';
}
if (typeof values[identifier] === 'undefined') {
return match;
}
const value = '' + values[identifier];
if (identifier === 'RepresentationID') {
// Format tag shall not be present with RepresentationID
return value;
}
if (!format) {
width = 1;
} else {
width = parseInt(width, 10);
}
if (value.length >= width) {
return value;
}
return `${(new Array(width - value.length + 1)).join('0')}${value}`;
};
/**
* Constructs a segment url from a template string
*
* @param {string} url
* Template string to construct url from
* @param {Obect} values
* Object containing values that shall be used to replace known identifiers
* @param {number} values.RepresentationID
* Value of the Representation@id attribute
* @param {number} values.Number
* Number of the corresponding segment
* @param {number} values.Bandwidth
* Value of the Representation@bandwidth attribute.
* @param {number} values.Time
* Timestamp value of the corresponding segment
* @return {string}
* Segment url with identifiers replaced
*/
export const constructTemplateUrl = (url, values) =>
url.replace(identifierPattern, identifierReplacement(values));
/**
* Generates a list of objects containing timing and duration information about each
* segment needed to generate segment uris and the complete segment object
*
* @param {Object} attributes
* Object containing all inherited attributes from parent elements with attribute
* names as keys
* @param {Object[]|undefined} segmentTimeline
* List of objects representing the attributes of each S element contained within
* the SegmentTimeline element
* @return {{number: number, duration: number, time: number, timeline: number}[]}
* List of Objects with segment timing and duration info
*/
export const parseTemplateInfo = (attributes, segmentTimeline) => {
if (!attributes.duration && !segmentTimeline) {
// if neither @duration or SegmentTimeline are present, then there shall be exactly
// one media segment
return [{
number: attributes.startNumber || 1,
duration: attributes.sourceDuration,
time: 0,
timeline: attributes.periodIndex
}];
}
if (attributes.duration) {
return parseByDuration(attributes);
}
return parseByTimeline(attributes, segmentTimeline);
};
/**
* Generates a list of segments using information provided by the SegmentTemplate element
*
* @param {Object} attributes
* Object containing all inherited attributes from parent elements with attribute
* names as keys
* @param {Object[]|undefined} segmentTimeline
* List of objects representing the attributes of each S element contained within
* the SegmentTimeline element
* @return {Object[]}
* List of segment objects
*/
export const segmentsFromTemplate = (attributes, segmentTimeline) => {
const templateValues = {
RepresentationID: attributes.id,
Bandwidth: attributes.bandwidth || 0
};
const { initialization = { sourceURL: '', range: '' } } = attributes;
const mapSegment = urlTypeToSegment({
baseUrl: attributes.baseUrl,
source: constructTemplateUrl(initialization.sourceURL, templateValues),
range: initialization.range
});
const segments = parseTemplateInfo(attributes, segmentTimeline);
return segments.map(segment => {
templateValues.Number = segment.number;
templateValues.Time = segment.time;
const uri = constructTemplateUrl(attributes.media || '', templateValues);
// See DASH spec section 5.3.9.2.2
// - if timescale isn't present on any level, default to 1.
const timescale = attributes.timescale || 1;
// - if presentationTimeOffset isn't present on any level, default to 0
const presentationTimeOffset = attributes.presentationTimeOffset || 0;
const presentationTime =
// Even if the @t attribute is not specified for the segment, segment.time is
// calculated in mpd-parser prior to this, so it's assumed to be available.
attributes.periodStart + ((segment.time - presentationTimeOffset) / timescale);
const map = {
uri,
timeline: segment.timeline,
duration: segment.duration,
resolvedUri: resolveUrl(attributes.baseUrl || '', uri),
map: mapSegment,
number: segment.number,
presentationTime
};
return map;
});
};