@transmitlive/m3u8-parser 4.7.2-beta.6 → 7.1.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/README.md +7 -2
- package/dist/m3u8-parser.cjs.js +449 -305
- package/dist/m3u8-parser.es.js +446 -300
- package/dist/m3u8-parser.js +465 -354
- package/dist/m3u8-parser.min.js +2 -2
- package/package.json +6 -11
- package/src/line-stream.js +2 -0
- package/src/parse-stream.js +93 -20
- package/src/parser.js +124 -12
- package/test/fixtures/integration/absoluteUris.js +1 -0
- package/test/fixtures/integration/allowCache.js +1 -0
- package/test/fixtures/integration/allowCacheInvalid.js +1 -0
- package/test/fixtures/integration/alternateAudio.js +1 -0
- package/test/fixtures/integration/alternateVideo.js +1 -0
- package/test/fixtures/integration/brightcove.js +1 -0
- package/test/fixtures/integration/byteRange.js +1 -0
- package/test/fixtures/integration/dateTime.js +3 -0
- package/test/fixtures/integration/diff-init-key.js +1 -0
- package/test/fixtures/integration/disallowCache.js +1 -0
- package/test/fixtures/integration/disc-sequence.js +9 -4
- package/test/fixtures/integration/discontinuity.js +19 -9
- package/test/fixtures/integration/domainUris.js +1 -0
- package/test/fixtures/integration/empty.js +1 -0
- package/test/fixtures/integration/emptyAllowCache.js +1 -0
- package/test/fixtures/integration/emptyMediaSequence.js +9 -4
- package/test/fixtures/integration/emptyPlaylistType.js +1 -0
- package/test/fixtures/integration/emptyTargetDuration.js +1 -0
- package/test/fixtures/integration/encrypted.js +1 -0
- package/test/fixtures/integration/event.js +1 -0
- package/test/fixtures/integration/extXPlaylistTypeInvalidPlaylist.js +3 -1
- package/test/fixtures/integration/extinf.js +3 -1
- package/test/fixtures/integration/fmp4.js +3 -1
- package/test/fixtures/integration/headerOnly.js +1 -0
- package/test/fixtures/integration/invalidAllowCache.js +1 -0
- package/test/fixtures/integration/invalidMediaSequence.js +9 -4
- package/test/fixtures/integration/invalidPlaylistType.js +1 -0
- package/test/fixtures/integration/invalidTargetDuration.js +1 -0
- package/test/fixtures/integration/liveMissingSegmentDuration.js +3 -1
- package/test/fixtures/integration/liveStart30sBefore.js +19 -9
- package/test/fixtures/integration/llhls-byte-range.js +1 -0
- package/test/fixtures/integration/llhls-delta-byte-range.js +1 -0
- package/test/fixtures/integration/llhls.js +8 -0
- package/test/fixtures/integration/llhlsDelta.js +5 -0
- package/test/fixtures/integration/manifestExtTTargetdurationNegative.js +1 -0
- package/test/fixtures/integration/manifestExtXEndlistEarly.js +1 -0
- package/test/fixtures/integration/manifestNoExtM3u.js +1 -0
- package/test/fixtures/integration/master-fmp4.js +27 -25
- package/test/fixtures/integration/master.js +1 -0
- package/test/fixtures/integration/media.js +1 -0
- package/test/fixtures/integration/mediaSequence.js +9 -4
- package/test/fixtures/integration/missingEndlist.js +1 -0
- package/test/fixtures/integration/missingExtinf.js +1 -0
- package/test/fixtures/integration/missingMediaSequence.js +9 -4
- package/test/fixtures/integration/missingSegmentDuration.js +3 -1
- package/test/fixtures/integration/multipleAudioGroups.js +1 -0
- package/test/fixtures/integration/multipleAudioGroupsCombinedMain.js +1 -0
- package/test/fixtures/integration/multipleTargetDurations.js +1 -0
- package/test/fixtures/integration/multipleVideo.js +1 -0
- package/test/fixtures/integration/negativeMediaSequence.js +9 -4
- package/test/fixtures/integration/playlist.js +1 -0
- package/test/fixtures/integration/playlistMediaSequenceHigher.js +3 -1
- package/test/fixtures/integration/start.js +1 -0
- package/test/fixtures/integration/streamInfInvalid.js +1 -0
- package/test/fixtures/integration/twoMediaSequences.js +9 -4
- package/test/fixtures/integration/versionInvalid.js +1 -0
- package/test/fixtures/integration/whiteSpace.js +1 -0
- package/test/fixtures/integration/zeroDuration.js +1 -0
- package/test/parse-stream.test.js +112 -16
- package/test/parser.test.js +345 -15
package/dist/m3u8-parser.cjs.js
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
|
-
/*! @name @transmitlive/m3u8-parser @version
|
|
1
|
+
/*! @name @transmitlive/m3u8-parser @version 7.1.0-0 @license Apache-2.0 */
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
5
5
|
|
|
6
|
-
var _inheritsLoose = require('@babel/runtime/helpers/inheritsLoose');
|
|
7
6
|
var Stream = require('@videojs/vhs-utils/cjs/stream.js');
|
|
8
7
|
var _extends = require('@babel/runtime/helpers/extends');
|
|
9
|
-
var _assertThisInitialized = require('@babel/runtime/helpers/assertThisInitialized');
|
|
10
8
|
var decodeB64ToUint8Array = require('@videojs/vhs-utils/cjs/decode-b64-to-uint8-array.js');
|
|
11
9
|
|
|
12
10
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
13
11
|
|
|
14
|
-
var _inheritsLoose__default = /*#__PURE__*/_interopDefaultLegacy(_inheritsLoose);
|
|
15
12
|
var Stream__default = /*#__PURE__*/_interopDefaultLegacy(Stream);
|
|
16
13
|
var _extends__default = /*#__PURE__*/_interopDefaultLegacy(_extends);
|
|
17
|
-
var _assertThisInitialized__default = /*#__PURE__*/_interopDefaultLegacy(_assertThisInitialized);
|
|
18
14
|
var decodeB64ToUint8Array__default = /*#__PURE__*/_interopDefaultLegacy(decodeB64ToUint8Array);
|
|
19
15
|
|
|
16
|
+
/**
|
|
17
|
+
* @file m3u8/line-stream.js
|
|
18
|
+
*/
|
|
20
19
|
/**
|
|
21
20
|
* A stream that buffers string input and generates a `data` event for each
|
|
22
21
|
* line.
|
|
@@ -25,15 +24,11 @@ var decodeB64ToUint8Array__default = /*#__PURE__*/_interopDefaultLegacy(decodeB6
|
|
|
25
24
|
* @extends Stream
|
|
26
25
|
*/
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
_this = _Stream.call(this) || this;
|
|
35
|
-
_this.buffer = '';
|
|
36
|
-
return _this;
|
|
27
|
+
class LineStream extends Stream__default["default"] {
|
|
28
|
+
constructor() {
|
|
29
|
+
super();
|
|
30
|
+
this.buffer = '';
|
|
31
|
+
this.lineNumber = 0;
|
|
37
32
|
}
|
|
38
33
|
/**
|
|
39
34
|
* Add new data to be parsed.
|
|
@@ -42,29 +37,27 @@ var LineStream = /*#__PURE__*/function (_Stream) {
|
|
|
42
37
|
*/
|
|
43
38
|
|
|
44
39
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
_proto.push = function push(data) {
|
|
48
|
-
var nextNewline;
|
|
40
|
+
push(data) {
|
|
41
|
+
let nextNewline;
|
|
49
42
|
this.buffer += data;
|
|
43
|
+
this.lineNumber += 1;
|
|
50
44
|
nextNewline = this.buffer.indexOf('\n');
|
|
51
45
|
|
|
52
46
|
for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) {
|
|
53
47
|
this.trigger('data', this.buffer.substring(0, nextNewline));
|
|
54
48
|
this.buffer = this.buffer.substring(nextNewline + 1);
|
|
55
49
|
}
|
|
56
|
-
}
|
|
50
|
+
}
|
|
57
51
|
|
|
58
|
-
|
|
59
|
-
}(Stream__default['default']);
|
|
52
|
+
}
|
|
60
53
|
|
|
61
|
-
|
|
54
|
+
const TAB = String.fromCharCode(0x09);
|
|
62
55
|
|
|
63
|
-
|
|
56
|
+
const parseByterange = function (byterangeString) {
|
|
64
57
|
// optionally match and capture 0+ digits before `@`
|
|
65
58
|
// optionally match and capture 0+ digits after `@`
|
|
66
|
-
|
|
67
|
-
|
|
59
|
+
const match = /([0-9.]*)?@?([0-9.]*)?/.exec(byterangeString || '');
|
|
60
|
+
const result = {};
|
|
68
61
|
|
|
69
62
|
if (match[1]) {
|
|
70
63
|
result.length = parseInt(match[1], 10);
|
|
@@ -85,10 +78,10 @@ var parseByterange = function parseByterange(byterangeString) {
|
|
|
85
78
|
*/
|
|
86
79
|
|
|
87
80
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
81
|
+
const attributeSeparator = function () {
|
|
82
|
+
const key = '[^=]*';
|
|
83
|
+
const value = '"[^"]*"|[^,]*';
|
|
84
|
+
const keyvalue = '(?:' + key + ')=(?:' + value + ')';
|
|
92
85
|
return new RegExp('(?:^|,)(' + keyvalue + ')');
|
|
93
86
|
};
|
|
94
87
|
/**
|
|
@@ -98,12 +91,17 @@ var attributeSeparator = function attributeSeparator() {
|
|
|
98
91
|
*/
|
|
99
92
|
|
|
100
93
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
94
|
+
const parseAttributes = function (attributes) {
|
|
95
|
+
const result = {};
|
|
96
|
+
|
|
97
|
+
if (!attributes) {
|
|
98
|
+
return result;
|
|
99
|
+
} // split the string using attributes as the separator
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
const attrs = attributes.split(attributeSeparator());
|
|
103
|
+
let i = attrs.length;
|
|
104
|
+
let attr;
|
|
107
105
|
|
|
108
106
|
while (i--) {
|
|
109
107
|
// filter out unmatched portions of the string
|
|
@@ -148,17 +146,11 @@ var parseAttributes = function parseAttributes(attributes) {
|
|
|
148
146
|
*/
|
|
149
147
|
|
|
150
148
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
_this = _Stream.call(this) || this;
|
|
158
|
-
_this.customParsers = [];
|
|
159
|
-
_this.tagMappers = [];
|
|
160
|
-
_this.lineNumber = 0;
|
|
161
|
-
return _this;
|
|
149
|
+
class ParseStream extends Stream__default["default"] {
|
|
150
|
+
constructor() {
|
|
151
|
+
super();
|
|
152
|
+
this.customParsers = [];
|
|
153
|
+
this.tagMappers = [];
|
|
162
154
|
}
|
|
163
155
|
/**
|
|
164
156
|
* Parses an additional line of input.
|
|
@@ -167,14 +159,9 @@ var ParseStream = /*#__PURE__*/function (_Stream) {
|
|
|
167
159
|
*/
|
|
168
160
|
|
|
169
161
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
var _this2 = this;
|
|
174
|
-
|
|
175
|
-
var match;
|
|
176
|
-
var event;
|
|
177
|
-
this.lineNumber = this.lineNumber + 1; // strip whitespace
|
|
162
|
+
push(line) {
|
|
163
|
+
let match;
|
|
164
|
+
let event; // strip whitespace
|
|
178
165
|
|
|
179
166
|
line = line.trim();
|
|
180
167
|
|
|
@@ -193,8 +180,8 @@ var ParseStream = /*#__PURE__*/function (_Stream) {
|
|
|
193
180
|
} // map tags
|
|
194
181
|
|
|
195
182
|
|
|
196
|
-
|
|
197
|
-
|
|
183
|
+
const newLines = this.tagMappers.reduce((acc, mapper) => {
|
|
184
|
+
const mappedLine = mapper(line); // skip if unchanged
|
|
198
185
|
|
|
199
186
|
if (mappedLine === line) {
|
|
200
187
|
return acc;
|
|
@@ -202,20 +189,19 @@ var ParseStream = /*#__PURE__*/function (_Stream) {
|
|
|
202
189
|
|
|
203
190
|
return acc.concat([mappedLine]);
|
|
204
191
|
}, [line]);
|
|
205
|
-
newLines.forEach(
|
|
206
|
-
for (
|
|
207
|
-
if (
|
|
192
|
+
newLines.forEach(newLine => {
|
|
193
|
+
for (let i = 0; i < this.customParsers.length; i++) {
|
|
194
|
+
if (this.customParsers[i].call(this, newLine)) {
|
|
208
195
|
return;
|
|
209
196
|
}
|
|
210
197
|
} // Comments
|
|
211
198
|
|
|
212
199
|
|
|
213
200
|
if (newLine.indexOf('#EXT') !== 0) {
|
|
214
|
-
|
|
201
|
+
this.trigger('data', {
|
|
215
202
|
type: 'comment',
|
|
216
203
|
text: newLine.slice(1)
|
|
217
204
|
});
|
|
218
|
-
|
|
219
205
|
return;
|
|
220
206
|
} // strip off any carriage returns here so the regex matching
|
|
221
207
|
// doesn't have to account for them.
|
|
@@ -226,15 +212,14 @@ var ParseStream = /*#__PURE__*/function (_Stream) {
|
|
|
226
212
|
match = /^#EXTM3U/.exec(newLine);
|
|
227
213
|
|
|
228
214
|
if (match) {
|
|
229
|
-
|
|
215
|
+
this.trigger('data', {
|
|
230
216
|
type: 'tag',
|
|
231
217
|
tagType: 'm3u'
|
|
232
218
|
});
|
|
233
|
-
|
|
234
219
|
return;
|
|
235
220
|
}
|
|
236
221
|
|
|
237
|
-
match = /^#EXTINF
|
|
222
|
+
match = /^#EXTINF:([0-9\.]*)?,?(.*)?$/.exec(newLine);
|
|
238
223
|
|
|
239
224
|
if (match) {
|
|
240
225
|
event = {
|
|
@@ -250,12 +235,11 @@ var ParseStream = /*#__PURE__*/function (_Stream) {
|
|
|
250
235
|
event.title = match[2];
|
|
251
236
|
}
|
|
252
237
|
|
|
253
|
-
|
|
254
|
-
|
|
238
|
+
this.trigger('data', event);
|
|
255
239
|
return;
|
|
256
240
|
}
|
|
257
241
|
|
|
258
|
-
match = /^#EXT-X-TARGETDURATION
|
|
242
|
+
match = /^#EXT-X-TARGETDURATION:([0-9.]*)?/.exec(newLine);
|
|
259
243
|
|
|
260
244
|
if (match) {
|
|
261
245
|
event = {
|
|
@@ -267,12 +251,11 @@ var ParseStream = /*#__PURE__*/function (_Stream) {
|
|
|
267
251
|
event.duration = parseInt(match[1], 10);
|
|
268
252
|
}
|
|
269
253
|
|
|
270
|
-
|
|
271
|
-
|
|
254
|
+
this.trigger('data', event);
|
|
272
255
|
return;
|
|
273
256
|
}
|
|
274
257
|
|
|
275
|
-
match = /^#EXT-X-VERSION
|
|
258
|
+
match = /^#EXT-X-VERSION:([0-9.]*)?/.exec(newLine);
|
|
276
259
|
|
|
277
260
|
if (match) {
|
|
278
261
|
event = {
|
|
@@ -284,12 +267,11 @@ var ParseStream = /*#__PURE__*/function (_Stream) {
|
|
|
284
267
|
event.version = parseInt(match[1], 10);
|
|
285
268
|
}
|
|
286
269
|
|
|
287
|
-
|
|
288
|
-
|
|
270
|
+
this.trigger('data', event);
|
|
289
271
|
return;
|
|
290
272
|
}
|
|
291
273
|
|
|
292
|
-
match = /^#EXT-X-MEDIA-SEQUENCE
|
|
274
|
+
match = /^#EXT-X-MEDIA-SEQUENCE:(\-?[0-9.]*)?/.exec(newLine);
|
|
293
275
|
|
|
294
276
|
if (match) {
|
|
295
277
|
event = {
|
|
@@ -301,12 +283,11 @@ var ParseStream = /*#__PURE__*/function (_Stream) {
|
|
|
301
283
|
event.number = parseInt(match[1], 10);
|
|
302
284
|
}
|
|
303
285
|
|
|
304
|
-
|
|
305
|
-
|
|
286
|
+
this.trigger('data', event);
|
|
306
287
|
return;
|
|
307
288
|
}
|
|
308
289
|
|
|
309
|
-
match = /^#EXT-X-DISCONTINUITY-SEQUENCE
|
|
290
|
+
match = /^#EXT-X-DISCONTINUITY-SEQUENCE:(\-?[0-9.]*)?/.exec(newLine);
|
|
310
291
|
|
|
311
292
|
if (match) {
|
|
312
293
|
event = {
|
|
@@ -318,12 +299,11 @@ var ParseStream = /*#__PURE__*/function (_Stream) {
|
|
|
318
299
|
event.number = parseInt(match[1], 10);
|
|
319
300
|
}
|
|
320
301
|
|
|
321
|
-
|
|
322
|
-
|
|
302
|
+
this.trigger('data', event);
|
|
323
303
|
return;
|
|
324
304
|
}
|
|
325
305
|
|
|
326
|
-
match = /^#EXT-X-PLAYLIST-TYPE
|
|
306
|
+
match = /^#EXT-X-PLAYLIST-TYPE:(.*)?$/.exec(newLine);
|
|
327
307
|
|
|
328
308
|
if (match) {
|
|
329
309
|
event = {
|
|
@@ -335,25 +315,22 @@ var ParseStream = /*#__PURE__*/function (_Stream) {
|
|
|
335
315
|
event.playlistType = match[1];
|
|
336
316
|
}
|
|
337
317
|
|
|
338
|
-
|
|
339
|
-
|
|
318
|
+
this.trigger('data', event);
|
|
340
319
|
return;
|
|
341
320
|
}
|
|
342
321
|
|
|
343
|
-
match = /^#EXT-X-BYTERANGE
|
|
322
|
+
match = /^#EXT-X-BYTERANGE:(.*)?$/.exec(newLine);
|
|
344
323
|
|
|
345
324
|
if (match) {
|
|
346
|
-
event = _extends__default[
|
|
325
|
+
event = _extends__default["default"](parseByterange(match[1]), {
|
|
347
326
|
type: 'tag',
|
|
348
327
|
tagType: 'byterange'
|
|
349
328
|
});
|
|
350
|
-
|
|
351
|
-
_this2.trigger('data', event);
|
|
352
|
-
|
|
329
|
+
this.trigger('data', event);
|
|
353
330
|
return;
|
|
354
331
|
}
|
|
355
332
|
|
|
356
|
-
match = /^#EXT-X-ALLOW-CACHE
|
|
333
|
+
match = /^#EXT-X-ALLOW-CACHE:(YES|NO)?/.exec(newLine);
|
|
357
334
|
|
|
358
335
|
if (match) {
|
|
359
336
|
event = {
|
|
@@ -365,12 +342,11 @@ var ParseStream = /*#__PURE__*/function (_Stream) {
|
|
|
365
342
|
event.allowed = !/NO/.test(match[1]);
|
|
366
343
|
}
|
|
367
344
|
|
|
368
|
-
|
|
369
|
-
|
|
345
|
+
this.trigger('data', event);
|
|
370
346
|
return;
|
|
371
347
|
}
|
|
372
348
|
|
|
373
|
-
match = /^#EXT-X-MAP
|
|
349
|
+
match = /^#EXT-X-MAP:(.*)$/.exec(newLine);
|
|
374
350
|
|
|
375
351
|
if (match) {
|
|
376
352
|
event = {
|
|
@@ -379,7 +355,7 @@ var ParseStream = /*#__PURE__*/function (_Stream) {
|
|
|
379
355
|
};
|
|
380
356
|
|
|
381
357
|
if (match[1]) {
|
|
382
|
-
|
|
358
|
+
const attributes = parseAttributes(match[1]);
|
|
383
359
|
|
|
384
360
|
if (attributes.URI) {
|
|
385
361
|
event.uri = attributes.URI;
|
|
@@ -390,12 +366,11 @@ var ParseStream = /*#__PURE__*/function (_Stream) {
|
|
|
390
366
|
}
|
|
391
367
|
}
|
|
392
368
|
|
|
393
|
-
|
|
394
|
-
|
|
369
|
+
this.trigger('data', event);
|
|
395
370
|
return;
|
|
396
371
|
}
|
|
397
372
|
|
|
398
|
-
match = /^#EXT-X-STREAM-INF
|
|
373
|
+
match = /^#EXT-X-STREAM-INF:(.*)$/.exec(newLine);
|
|
399
374
|
|
|
400
375
|
if (match) {
|
|
401
376
|
event = {
|
|
@@ -407,8 +382,8 @@ var ParseStream = /*#__PURE__*/function (_Stream) {
|
|
|
407
382
|
event.attributes = parseAttributes(match[1]);
|
|
408
383
|
|
|
409
384
|
if (event.attributes.RESOLUTION) {
|
|
410
|
-
|
|
411
|
-
|
|
385
|
+
const split = event.attributes.RESOLUTION.split('x');
|
|
386
|
+
const resolution = {};
|
|
412
387
|
|
|
413
388
|
if (split[0]) {
|
|
414
389
|
resolution.width = parseInt(split[0], 10);
|
|
@@ -425,17 +400,20 @@ var ParseStream = /*#__PURE__*/function (_Stream) {
|
|
|
425
400
|
event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
|
|
426
401
|
}
|
|
427
402
|
|
|
403
|
+
if (event.attributes['FRAME-RATE']) {
|
|
404
|
+
event.attributes['FRAME-RATE'] = parseFloat(event.attributes['FRAME-RATE']);
|
|
405
|
+
}
|
|
406
|
+
|
|
428
407
|
if (event.attributes['PROGRAM-ID']) {
|
|
429
408
|
event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10);
|
|
430
409
|
}
|
|
431
410
|
}
|
|
432
411
|
|
|
433
|
-
|
|
434
|
-
|
|
412
|
+
this.trigger('data', event);
|
|
435
413
|
return;
|
|
436
414
|
}
|
|
437
415
|
|
|
438
|
-
match = /^#EXT-X-MEDIA
|
|
416
|
+
match = /^#EXT-X-MEDIA:(.*)$/.exec(newLine);
|
|
439
417
|
|
|
440
418
|
if (match) {
|
|
441
419
|
event = {
|
|
@@ -447,34 +425,31 @@ var ParseStream = /*#__PURE__*/function (_Stream) {
|
|
|
447
425
|
event.attributes = parseAttributes(match[1]);
|
|
448
426
|
}
|
|
449
427
|
|
|
450
|
-
|
|
451
|
-
|
|
428
|
+
this.trigger('data', event);
|
|
452
429
|
return;
|
|
453
430
|
}
|
|
454
431
|
|
|
455
432
|
match = /^#EXT-X-ENDLIST/.exec(newLine);
|
|
456
433
|
|
|
457
434
|
if (match) {
|
|
458
|
-
|
|
435
|
+
this.trigger('data', {
|
|
459
436
|
type: 'tag',
|
|
460
437
|
tagType: 'endlist'
|
|
461
438
|
});
|
|
462
|
-
|
|
463
439
|
return;
|
|
464
440
|
}
|
|
465
441
|
|
|
466
442
|
match = /^#EXT-X-DISCONTINUITY/.exec(newLine);
|
|
467
443
|
|
|
468
444
|
if (match) {
|
|
469
|
-
|
|
445
|
+
this.trigger('data', {
|
|
470
446
|
type: 'tag',
|
|
471
447
|
tagType: 'discontinuity'
|
|
472
448
|
});
|
|
473
|
-
|
|
474
449
|
return;
|
|
475
450
|
}
|
|
476
451
|
|
|
477
|
-
match = /^#EXT-X-PROGRAM-DATE-TIME
|
|
452
|
+
match = /^#EXT-X-PROGRAM-DATE-TIME:(.*)$/.exec(newLine);
|
|
478
453
|
|
|
479
454
|
if (match) {
|
|
480
455
|
event = {
|
|
@@ -487,12 +462,11 @@ var ParseStream = /*#__PURE__*/function (_Stream) {
|
|
|
487
462
|
event.dateTimeObject = new Date(match[1]);
|
|
488
463
|
}
|
|
489
464
|
|
|
490
|
-
|
|
491
|
-
|
|
465
|
+
this.trigger('data', event);
|
|
492
466
|
return;
|
|
493
467
|
}
|
|
494
468
|
|
|
495
|
-
match = /^#EXT-X-KEY
|
|
469
|
+
match = /^#EXT-X-KEY:(.*)$/.exec(newLine);
|
|
496
470
|
|
|
497
471
|
if (match) {
|
|
498
472
|
event = {
|
|
@@ -517,12 +491,11 @@ var ParseStream = /*#__PURE__*/function (_Stream) {
|
|
|
517
491
|
}
|
|
518
492
|
}
|
|
519
493
|
|
|
520
|
-
|
|
521
|
-
|
|
494
|
+
this.trigger('data', event);
|
|
522
495
|
return;
|
|
523
496
|
}
|
|
524
497
|
|
|
525
|
-
match = /^#EXT-X-START
|
|
498
|
+
match = /^#EXT-X-START:(.*)$/.exec(newLine);
|
|
526
499
|
|
|
527
500
|
if (match) {
|
|
528
501
|
event = {
|
|
@@ -536,12 +509,11 @@ var ParseStream = /*#__PURE__*/function (_Stream) {
|
|
|
536
509
|
event.attributes.PRECISE = /YES/.test(event.attributes.PRECISE);
|
|
537
510
|
}
|
|
538
511
|
|
|
539
|
-
|
|
540
|
-
|
|
512
|
+
this.trigger('data', event);
|
|
541
513
|
return;
|
|
542
514
|
}
|
|
543
515
|
|
|
544
|
-
match = /^#EXT-X-CUE-OUT-CONT
|
|
516
|
+
match = /^#EXT-X-CUE-OUT-CONT:(.*)?$/.exec(newLine);
|
|
545
517
|
|
|
546
518
|
if (match) {
|
|
547
519
|
event = {
|
|
@@ -555,12 +527,11 @@ var ParseStream = /*#__PURE__*/function (_Stream) {
|
|
|
555
527
|
event.data = '';
|
|
556
528
|
}
|
|
557
529
|
|
|
558
|
-
|
|
559
|
-
|
|
530
|
+
this.trigger('data', event);
|
|
560
531
|
return;
|
|
561
532
|
}
|
|
562
533
|
|
|
563
|
-
match = /^#EXT-X-CUE-OUT
|
|
534
|
+
match = /^#EXT-X-CUE-OUT:(.*)?$/.exec(newLine);
|
|
564
535
|
|
|
565
536
|
if (match) {
|
|
566
537
|
event = {
|
|
@@ -574,12 +545,11 @@ var ParseStream = /*#__PURE__*/function (_Stream) {
|
|
|
574
545
|
event.data = '';
|
|
575
546
|
}
|
|
576
547
|
|
|
577
|
-
|
|
578
|
-
|
|
548
|
+
this.trigger('data', event);
|
|
579
549
|
return;
|
|
580
550
|
}
|
|
581
551
|
|
|
582
|
-
match = /^#EXT-X-CUE-IN
|
|
552
|
+
match = /^#EXT-X-CUE-IN:(.*)?$/.exec(newLine);
|
|
583
553
|
|
|
584
554
|
if (match) {
|
|
585
555
|
event = {
|
|
@@ -593,8 +563,7 @@ var ParseStream = /*#__PURE__*/function (_Stream) {
|
|
|
593
563
|
event.data = '';
|
|
594
564
|
}
|
|
595
565
|
|
|
596
|
-
|
|
597
|
-
|
|
566
|
+
this.trigger('data', event);
|
|
598
567
|
return;
|
|
599
568
|
}
|
|
600
569
|
|
|
@@ -615,8 +584,7 @@ var ParseStream = /*#__PURE__*/function (_Stream) {
|
|
|
615
584
|
event.attributes['RECENTLY-REMOVED-DATERANGES'] = event.attributes['RECENTLY-REMOVED-DATERANGES'].split(TAB);
|
|
616
585
|
}
|
|
617
586
|
|
|
618
|
-
|
|
619
|
-
|
|
587
|
+
this.trigger('data', event);
|
|
620
588
|
return;
|
|
621
589
|
}
|
|
622
590
|
|
|
@@ -643,8 +611,7 @@ var ParseStream = /*#__PURE__*/function (_Stream) {
|
|
|
643
611
|
event.attributes.byterange = parseByterange(event.attributes.BYTERANGE);
|
|
644
612
|
}
|
|
645
613
|
|
|
646
|
-
|
|
647
|
-
|
|
614
|
+
this.trigger('data', event);
|
|
648
615
|
return;
|
|
649
616
|
}
|
|
650
617
|
|
|
@@ -666,9 +633,7 @@ var ParseStream = /*#__PURE__*/function (_Stream) {
|
|
|
666
633
|
event.attributes[key] = /YES/.test(event.attributes[key]);
|
|
667
634
|
}
|
|
668
635
|
});
|
|
669
|
-
|
|
670
|
-
_this2.trigger('data', event);
|
|
671
|
-
|
|
636
|
+
this.trigger('data', event);
|
|
672
637
|
return;
|
|
673
638
|
}
|
|
674
639
|
|
|
@@ -685,9 +650,7 @@ var ParseStream = /*#__PURE__*/function (_Stream) {
|
|
|
685
650
|
event.attributes[key] = parseFloat(event.attributes[key]);
|
|
686
651
|
}
|
|
687
652
|
});
|
|
688
|
-
|
|
689
|
-
_this2.trigger('data', event);
|
|
690
|
-
|
|
653
|
+
this.trigger('data', event);
|
|
691
654
|
return;
|
|
692
655
|
}
|
|
693
656
|
|
|
@@ -702,16 +665,14 @@ var ParseStream = /*#__PURE__*/function (_Stream) {
|
|
|
702
665
|
['BYTERANGE-START', 'BYTERANGE-LENGTH'].forEach(function (key) {
|
|
703
666
|
if (event.attributes.hasOwnProperty(key)) {
|
|
704
667
|
event.attributes[key] = parseInt(event.attributes[key], 10);
|
|
705
|
-
|
|
668
|
+
const subkey = key === 'BYTERANGE-LENGTH' ? 'length' : 'offset';
|
|
706
669
|
event.attributes.byterange = event.attributes.byterange || {};
|
|
707
670
|
event.attributes.byterange[subkey] = event.attributes[key]; // only keep the parsed byterange object.
|
|
708
671
|
|
|
709
672
|
delete event.attributes[key];
|
|
710
673
|
}
|
|
711
674
|
});
|
|
712
|
-
|
|
713
|
-
_this2.trigger('data', event);
|
|
714
|
-
|
|
675
|
+
this.trigger('data', event);
|
|
715
676
|
return;
|
|
716
677
|
}
|
|
717
678
|
|
|
@@ -728,14 +689,83 @@ var ParseStream = /*#__PURE__*/function (_Stream) {
|
|
|
728
689
|
event.attributes[key] = parseInt(event.attributes[key], 10);
|
|
729
690
|
}
|
|
730
691
|
});
|
|
692
|
+
this.trigger('data', event);
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
731
695
|
|
|
732
|
-
|
|
696
|
+
match = /^#EXT-X-DATERANGE:(.*)$/.exec(newLine);
|
|
733
697
|
|
|
698
|
+
if (match && match[1]) {
|
|
699
|
+
event = {
|
|
700
|
+
type: 'tag',
|
|
701
|
+
tagType: 'daterange'
|
|
702
|
+
};
|
|
703
|
+
event.attributes = parseAttributes(match[1]);
|
|
704
|
+
['ID', 'CLASS'].forEach(function (key) {
|
|
705
|
+
if (event.attributes.hasOwnProperty(key)) {
|
|
706
|
+
event.attributes[key] = String(event.attributes[key]);
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
['START-DATE', 'END-DATE'].forEach(function (key) {
|
|
710
|
+
if (event.attributes.hasOwnProperty(key)) {
|
|
711
|
+
event.attributes[key] = new Date(event.attributes[key]);
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
['DURATION', 'PLANNED-DURATION'].forEach(function (key) {
|
|
715
|
+
if (event.attributes.hasOwnProperty(key)) {
|
|
716
|
+
event.attributes[key] = parseFloat(event.attributes[key]);
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
['END-ON-NEXT'].forEach(function (key) {
|
|
720
|
+
if (event.attributes.hasOwnProperty(key)) {
|
|
721
|
+
event.attributes[key] = /YES/i.test(event.attributes[key]);
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
['SCTE35-CMD', ' SCTE35-OUT', 'SCTE35-IN'].forEach(function (key) {
|
|
725
|
+
if (event.attributes.hasOwnProperty(key)) {
|
|
726
|
+
event.attributes[key] = event.attributes[key].toString(16);
|
|
727
|
+
}
|
|
728
|
+
});
|
|
729
|
+
const clientAttributePattern = /^X-([A-Z]+-)+[A-Z]+$/;
|
|
730
|
+
|
|
731
|
+
for (const key in event.attributes) {
|
|
732
|
+
if (!clientAttributePattern.test(key)) {
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
const isHexaDecimal = /[0-9A-Fa-f]{6}/g.test(event.attributes[key]);
|
|
737
|
+
const isDecimalFloating = /^\d+(\.\d+)?$/.test(event.attributes[key]);
|
|
738
|
+
event.attributes[key] = isHexaDecimal ? event.attributes[key].toString(16) : isDecimalFloating ? parseFloat(event.attributes[key]) : String(event.attributes[key]);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
this.trigger('data', event);
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
match = /^#EXT-X-INDEPENDENT-SEGMENTS/.exec(newLine);
|
|
746
|
+
|
|
747
|
+
if (match) {
|
|
748
|
+
this.trigger('data', {
|
|
749
|
+
type: 'tag',
|
|
750
|
+
tagType: 'independent-segments'
|
|
751
|
+
});
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
match = /^#EXT-X-CONTENT-STEERING:(.*)$/.exec(newLine);
|
|
756
|
+
|
|
757
|
+
if (match) {
|
|
758
|
+
event = {
|
|
759
|
+
type: 'tag',
|
|
760
|
+
tagType: 'content-steering'
|
|
761
|
+
};
|
|
762
|
+
event.attributes = parseAttributes(match[1]);
|
|
763
|
+
this.trigger('data', event);
|
|
734
764
|
return;
|
|
735
765
|
} // unknown tag type
|
|
736
766
|
|
|
737
767
|
|
|
738
|
-
|
|
768
|
+
this.trigger('data', {
|
|
739
769
|
type: 'tag',
|
|
740
770
|
data: newLine.slice(4)
|
|
741
771
|
});
|
|
@@ -750,33 +780,28 @@ var ParseStream = /*#__PURE__*/function (_Stream) {
|
|
|
750
780
|
* @param {Function} [options.dataParser] function to parse the line into an object
|
|
751
781
|
* @param {boolean} [options.segment] should tag data be attached to the segment object
|
|
752
782
|
*/
|
|
753
|
-
;
|
|
754
|
-
|
|
755
|
-
_proto.addParser = function addParser(_ref) {
|
|
756
|
-
var _this3 = this;
|
|
757
783
|
|
|
758
|
-
var expression = _ref.expression,
|
|
759
|
-
customType = _ref.customType,
|
|
760
|
-
dataParser = _ref.dataParser,
|
|
761
|
-
segment = _ref.segment;
|
|
762
784
|
|
|
785
|
+
addParser({
|
|
786
|
+
expression,
|
|
787
|
+
customType,
|
|
788
|
+
dataParser,
|
|
789
|
+
segment
|
|
790
|
+
}) {
|
|
763
791
|
if (typeof dataParser !== 'function') {
|
|
764
|
-
dataParser =
|
|
765
|
-
return line;
|
|
766
|
-
};
|
|
792
|
+
dataParser = line => line;
|
|
767
793
|
}
|
|
768
794
|
|
|
769
|
-
this.customParsers.push(
|
|
770
|
-
|
|
795
|
+
this.customParsers.push(line => {
|
|
796
|
+
const match = expression.exec(line);
|
|
771
797
|
|
|
772
798
|
if (match) {
|
|
773
|
-
|
|
799
|
+
this.trigger('data', {
|
|
774
800
|
type: 'custom',
|
|
775
801
|
data: dataParser(line),
|
|
776
|
-
customType
|
|
777
|
-
segment
|
|
802
|
+
customType,
|
|
803
|
+
segment
|
|
778
804
|
});
|
|
779
|
-
|
|
780
805
|
return true;
|
|
781
806
|
}
|
|
782
807
|
});
|
|
@@ -788,13 +813,13 @@ var ParseStream = /*#__PURE__*/function (_Stream) {
|
|
|
788
813
|
* @param {RegExp} options.expression a regular expression to match the custom header
|
|
789
814
|
* @param {Function} options.map function to translate tag into a different tag
|
|
790
815
|
*/
|
|
791
|
-
;
|
|
792
816
|
|
|
793
|
-
_proto.addTagMapper = function addTagMapper(_ref2) {
|
|
794
|
-
var expression = _ref2.expression,
|
|
795
|
-
map = _ref2.map;
|
|
796
817
|
|
|
797
|
-
|
|
818
|
+
addTagMapper({
|
|
819
|
+
expression,
|
|
820
|
+
map
|
|
821
|
+
}) {
|
|
822
|
+
const mapFn = line => {
|
|
798
823
|
if (expression.test(line)) {
|
|
799
824
|
return map(line);
|
|
800
825
|
}
|
|
@@ -803,19 +828,14 @@ var ParseStream = /*#__PURE__*/function (_Stream) {
|
|
|
803
828
|
};
|
|
804
829
|
|
|
805
830
|
this.tagMappers.push(mapFn);
|
|
806
|
-
}
|
|
831
|
+
}
|
|
807
832
|
|
|
808
|
-
|
|
809
|
-
}(Stream__default['default']);
|
|
833
|
+
}
|
|
810
834
|
|
|
811
|
-
|
|
812
|
-
return str.toLowerCase().replace(/-(\w)/g, function (a) {
|
|
813
|
-
return a[1].toUpperCase();
|
|
814
|
-
});
|
|
815
|
-
};
|
|
835
|
+
const camelCase = str => str.toLowerCase().replace(/-(\w)/g, a => a[1].toUpperCase());
|
|
816
836
|
|
|
817
|
-
|
|
818
|
-
|
|
837
|
+
const camelCaseKeys = function (attributes) {
|
|
838
|
+
const result = {};
|
|
819
839
|
Object.keys(attributes).forEach(function (key) {
|
|
820
840
|
result[camelCase(key)] = attributes[key];
|
|
821
841
|
});
|
|
@@ -826,31 +846,33 @@ var camelCaseKeys = function camelCaseKeys(attributes) {
|
|
|
826
846
|
// target durations are set.
|
|
827
847
|
|
|
828
848
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
849
|
+
const setHoldBack = function (manifest) {
|
|
850
|
+
const {
|
|
851
|
+
serverControl,
|
|
852
|
+
targetDuration,
|
|
853
|
+
partTargetDuration
|
|
854
|
+
} = manifest;
|
|
833
855
|
|
|
834
856
|
if (!serverControl) {
|
|
835
857
|
return;
|
|
836
858
|
}
|
|
837
859
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
860
|
+
const tag = '#EXT-X-SERVER-CONTROL';
|
|
861
|
+
const hb = 'holdBack';
|
|
862
|
+
const phb = 'partHoldBack';
|
|
863
|
+
const minTargetDuration = targetDuration && targetDuration * 3;
|
|
864
|
+
const minPartDuration = partTargetDuration && partTargetDuration * 2;
|
|
843
865
|
|
|
844
866
|
if (targetDuration && !serverControl.hasOwnProperty(hb)) {
|
|
845
867
|
serverControl[hb] = minTargetDuration;
|
|
846
868
|
this.trigger('info', {
|
|
847
|
-
message: tag
|
|
869
|
+
message: `${tag} defaulting HOLD-BACK to targetDuration * 3 (${minTargetDuration}).`
|
|
848
870
|
});
|
|
849
871
|
}
|
|
850
872
|
|
|
851
873
|
if (minTargetDuration && serverControl[hb] < minTargetDuration) {
|
|
852
874
|
this.trigger('warn', {
|
|
853
|
-
message: tag
|
|
875
|
+
message: `${tag} clamping HOLD-BACK (${serverControl[hb]}) to targetDuration * 3 (${minTargetDuration})`
|
|
854
876
|
});
|
|
855
877
|
serverControl[hb] = minTargetDuration;
|
|
856
878
|
} // default no part hold back to part target duration * 3
|
|
@@ -859,14 +881,14 @@ var setHoldBack = function setHoldBack(manifest) {
|
|
|
859
881
|
if (partTargetDuration && !serverControl.hasOwnProperty(phb)) {
|
|
860
882
|
serverControl[phb] = partTargetDuration * 3;
|
|
861
883
|
this.trigger('info', {
|
|
862
|
-
message: tag
|
|
884
|
+
message: `${tag} defaulting PART-HOLD-BACK to partTargetDuration * 3 (${serverControl[phb]}).`
|
|
863
885
|
});
|
|
864
886
|
} // if part hold back is too small default it to part target duration * 2
|
|
865
887
|
|
|
866
888
|
|
|
867
889
|
if (partTargetDuration && serverControl[phb] < minPartDuration) {
|
|
868
890
|
this.trigger('warn', {
|
|
869
|
-
message: tag
|
|
891
|
+
message: `${tag} clamping PART-HOLD-BACK (${serverControl[phb]}) to partTargetDuration * 2 (${minPartDuration}).`
|
|
870
892
|
});
|
|
871
893
|
serverControl[phb] = minPartDuration;
|
|
872
894
|
}
|
|
@@ -894,36 +916,29 @@ var setHoldBack = function setHoldBack(manifest) {
|
|
|
894
916
|
*/
|
|
895
917
|
|
|
896
918
|
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
_this.lineStream = new LineStream();
|
|
905
|
-
_this.parseStream = new ParseStream();
|
|
906
|
-
|
|
907
|
-
_this.lineStream.pipe(_this.parseStream);
|
|
919
|
+
class Parser extends Stream__default["default"] {
|
|
920
|
+
constructor() {
|
|
921
|
+
super();
|
|
922
|
+
this.lineStream = new LineStream();
|
|
923
|
+
this.parseStream = new ParseStream();
|
|
924
|
+
this.lineStream.pipe(this.parseStream);
|
|
925
|
+
this.lastProgramDateTime = null;
|
|
908
926
|
/* eslint-disable consistent-this */
|
|
909
927
|
|
|
910
|
-
|
|
911
|
-
var self = _assertThisInitialized__default['default'](_this);
|
|
928
|
+
const self = this;
|
|
912
929
|
/* eslint-enable consistent-this */
|
|
913
930
|
|
|
931
|
+
const uris = [];
|
|
932
|
+
let currentUri = {}; // if specified, the active EXT-X-MAP definition
|
|
914
933
|
|
|
915
|
-
|
|
916
|
-
var currentUri = {}; // if specified, the active EXT-X-MAP definition
|
|
934
|
+
let currentMap; // if specified, the active decryption key
|
|
917
935
|
|
|
918
|
-
|
|
936
|
+
let key;
|
|
937
|
+
let hasParts = false;
|
|
919
938
|
|
|
920
|
-
|
|
939
|
+
const noop = function () {};
|
|
921
940
|
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
var noop = function noop() {};
|
|
925
|
-
|
|
926
|
-
var defaultMediaGroups = {
|
|
941
|
+
const defaultMediaGroups = {
|
|
927
942
|
'AUDIO': {},
|
|
928
943
|
'VIDEO': {},
|
|
929
944
|
'CLOSED-CAPTIONS': {},
|
|
@@ -931,25 +946,26 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
931
946
|
}; // This is the Widevine UUID from DASH IF IOP. The same exact string is
|
|
932
947
|
// used in MPDs with Widevine encrypted streams.
|
|
933
948
|
|
|
934
|
-
|
|
949
|
+
const widevineUuid = 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'; // group segments into numbered timelines delineated by discontinuities
|
|
935
950
|
|
|
936
|
-
|
|
951
|
+
let currentTimeline = 0; // the manifest is empty until the parse stream begins delivering data
|
|
937
952
|
|
|
938
|
-
|
|
953
|
+
this.manifest = {
|
|
939
954
|
allowCache: true,
|
|
940
955
|
discontinuityStarts: [],
|
|
956
|
+
dateRanges: [],
|
|
941
957
|
segments: []
|
|
942
958
|
}; // keep track of the last seen segment's byte range end, as segments are not required
|
|
943
959
|
// to provide the offset, in which case it defaults to the next byte after the
|
|
944
960
|
// previous segment
|
|
945
961
|
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
var lastPartByterangeEnd = 0; // track where next segment starts
|
|
962
|
+
let lastByterangeEnd = 0; // keep track of the last seen part's byte range end.
|
|
949
963
|
|
|
950
|
-
|
|
964
|
+
let lastPartByterangeEnd = 0;
|
|
965
|
+
const dateRangeTags = {}; // track where next segment starts
|
|
951
966
|
|
|
952
|
-
|
|
967
|
+
let nextSegmentLineNumberStart = 0;
|
|
968
|
+
this.on('end', () => {
|
|
953
969
|
// only add preloadSegment if we don't yet have a uri for it.
|
|
954
970
|
// and we actually have parts/preloadHints
|
|
955
971
|
if (currentUri.uri || !currentUri.parts && !currentUri.preloadHints) {
|
|
@@ -960,36 +976,36 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
960
976
|
currentUri.map = currentMap;
|
|
961
977
|
}
|
|
962
978
|
|
|
963
|
-
if (!currentUri.key &&
|
|
964
|
-
currentUri.key =
|
|
979
|
+
if (!currentUri.key && key) {
|
|
980
|
+
currentUri.key = key;
|
|
965
981
|
}
|
|
966
982
|
|
|
967
983
|
if (!currentUri.timeline && typeof currentTimeline === 'number') {
|
|
968
984
|
currentUri.timeline = currentTimeline;
|
|
969
985
|
}
|
|
970
986
|
|
|
971
|
-
|
|
987
|
+
this.manifest.preloadSegment = currentUri;
|
|
972
988
|
}); // update the manifest with the m3u8 entry from the parse stream
|
|
973
989
|
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
var rendition; // starting a new segment
|
|
990
|
+
this.parseStream.on('data', function (entry) {
|
|
991
|
+
let mediaGroup;
|
|
992
|
+
let rendition; //starting a new segment
|
|
978
993
|
|
|
979
994
|
if (!Object.keys(currentUri).length) {
|
|
980
|
-
nextSegmentLineNumberStart = this.lineNumber;
|
|
995
|
+
nextSegmentLineNumberStart = this.lineStream.lineNumber;
|
|
981
996
|
}
|
|
982
997
|
|
|
983
998
|
({
|
|
984
|
-
tag
|
|
999
|
+
tag() {
|
|
985
1000
|
// switch based on the tag type
|
|
986
1001
|
(({
|
|
987
|
-
version
|
|
1002
|
+
version() {
|
|
988
1003
|
if (entry.version) {
|
|
989
1004
|
this.manifest.version = entry.version;
|
|
990
1005
|
}
|
|
991
1006
|
},
|
|
992
|
-
|
|
1007
|
+
|
|
1008
|
+
'allow-cache'() {
|
|
993
1009
|
this.manifest.allowCache = entry.allowed;
|
|
994
1010
|
|
|
995
1011
|
if (!('allowed' in entry)) {
|
|
@@ -999,8 +1015,9 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
999
1015
|
this.manifest.allowCache = true;
|
|
1000
1016
|
}
|
|
1001
1017
|
},
|
|
1002
|
-
|
|
1003
|
-
|
|
1018
|
+
|
|
1019
|
+
byterange() {
|
|
1020
|
+
const byterange = {};
|
|
1004
1021
|
|
|
1005
1022
|
if ('length' in entry) {
|
|
1006
1023
|
currentUri.byterange = byterange;
|
|
@@ -1028,10 +1045,12 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
1028
1045
|
|
|
1029
1046
|
lastByterangeEnd = byterange.offset + byterange.length;
|
|
1030
1047
|
},
|
|
1031
|
-
|
|
1048
|
+
|
|
1049
|
+
endlist() {
|
|
1032
1050
|
this.manifest.endList = true;
|
|
1033
1051
|
},
|
|
1034
|
-
|
|
1052
|
+
|
|
1053
|
+
inf() {
|
|
1035
1054
|
if (!('mediaSequence' in this.manifest)) {
|
|
1036
1055
|
this.manifest.mediaSequence = 0;
|
|
1037
1056
|
this.trigger('info', {
|
|
@@ -1046,6 +1065,10 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
1046
1065
|
});
|
|
1047
1066
|
}
|
|
1048
1067
|
|
|
1068
|
+
if (entry.title) {
|
|
1069
|
+
currentUri.title = entry.title;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1049
1072
|
if (entry.duration > 0) {
|
|
1050
1073
|
currentUri.duration = entry.duration;
|
|
1051
1074
|
}
|
|
@@ -1059,7 +1082,8 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
1059
1082
|
|
|
1060
1083
|
this.manifest.segments = uris;
|
|
1061
1084
|
},
|
|
1062
|
-
|
|
1085
|
+
|
|
1086
|
+
key() {
|
|
1063
1087
|
if (!entry.attributes) {
|
|
1064
1088
|
this.trigger('warn', {
|
|
1065
1089
|
message: 'ignoring key declaration without attribute list'
|
|
@@ -1069,7 +1093,7 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
1069
1093
|
|
|
1070
1094
|
|
|
1071
1095
|
if (entry.attributes.METHOD === 'NONE') {
|
|
1072
|
-
|
|
1096
|
+
key = null;
|
|
1073
1097
|
return;
|
|
1074
1098
|
}
|
|
1075
1099
|
|
|
@@ -1101,7 +1125,7 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
1101
1125
|
|
|
1102
1126
|
|
|
1103
1127
|
if (entry.attributes.KEYFORMAT === widevineUuid) {
|
|
1104
|
-
|
|
1128
|
+
const VALID_METHODS = ['SAMPLE-AES', 'SAMPLE-AES-CTR', 'SAMPLE-AES-CENC'];
|
|
1105
1129
|
|
|
1106
1130
|
if (VALID_METHODS.indexOf(entry.attributes.METHOD) === -1) {
|
|
1107
1131
|
this.trigger('warn', {
|
|
@@ -1140,7 +1164,7 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
1140
1164
|
keyId: entry.attributes.KEYID.substring(2)
|
|
1141
1165
|
},
|
|
1142
1166
|
// decode the base64-encoded PSSH box
|
|
1143
|
-
pssh: decodeB64ToUint8Array__default[
|
|
1167
|
+
pssh: decodeB64ToUint8Array__default["default"](entry.attributes.URI.split(',')[1])
|
|
1144
1168
|
};
|
|
1145
1169
|
return;
|
|
1146
1170
|
}
|
|
@@ -1152,16 +1176,17 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
1152
1176
|
} // setup an encryption key for upcoming segments
|
|
1153
1177
|
|
|
1154
1178
|
|
|
1155
|
-
|
|
1179
|
+
key = {
|
|
1156
1180
|
method: entry.attributes.METHOD || 'AES-128',
|
|
1157
1181
|
uri: entry.attributes.URI
|
|
1158
1182
|
};
|
|
1159
1183
|
|
|
1160
1184
|
if (typeof entry.attributes.IV !== 'undefined') {
|
|
1161
|
-
|
|
1185
|
+
key.iv = entry.attributes.IV;
|
|
1162
1186
|
}
|
|
1163
1187
|
},
|
|
1164
|
-
|
|
1188
|
+
|
|
1189
|
+
'media-sequence'() {
|
|
1165
1190
|
if (!isFinite(entry.number)) {
|
|
1166
1191
|
this.trigger('warn', {
|
|
1167
1192
|
message: 'ignoring invalid media sequence: ' + entry.number
|
|
@@ -1171,7 +1196,8 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
1171
1196
|
|
|
1172
1197
|
this.manifest.mediaSequence = entry.number;
|
|
1173
1198
|
},
|
|
1174
|
-
|
|
1199
|
+
|
|
1200
|
+
'discontinuity-sequence'() {
|
|
1175
1201
|
if (!isFinite(entry.number)) {
|
|
1176
1202
|
this.trigger('warn', {
|
|
1177
1203
|
message: 'ignoring invalid discontinuity sequence: ' + entry.number
|
|
@@ -1182,7 +1208,8 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
1182
1208
|
this.manifest.discontinuitySequence = entry.number;
|
|
1183
1209
|
currentTimeline = entry.number;
|
|
1184
1210
|
},
|
|
1185
|
-
|
|
1211
|
+
|
|
1212
|
+
'playlist-type'() {
|
|
1186
1213
|
if (!/VOD|EVENT/.test(entry.playlistType)) {
|
|
1187
1214
|
this.trigger('warn', {
|
|
1188
1215
|
message: 'ignoring unknown playlist type: ' + entry.playlist
|
|
@@ -1192,7 +1219,8 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
1192
1219
|
|
|
1193
1220
|
this.manifest.playlistType = entry.playlistType;
|
|
1194
1221
|
},
|
|
1195
|
-
|
|
1222
|
+
|
|
1223
|
+
map() {
|
|
1196
1224
|
currentMap = {};
|
|
1197
1225
|
|
|
1198
1226
|
if (entry.uri) {
|
|
@@ -1203,11 +1231,12 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
1203
1231
|
currentMap.byterange = entry.byterange;
|
|
1204
1232
|
}
|
|
1205
1233
|
|
|
1206
|
-
if (
|
|
1207
|
-
currentMap.key =
|
|
1234
|
+
if (key) {
|
|
1235
|
+
currentMap.key = key;
|
|
1208
1236
|
}
|
|
1209
1237
|
},
|
|
1210
|
-
|
|
1238
|
+
|
|
1239
|
+
'stream-inf'() {
|
|
1211
1240
|
this.manifest.playlists = uris;
|
|
1212
1241
|
this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
|
|
1213
1242
|
|
|
@@ -1222,9 +1251,10 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
1222
1251
|
currentUri.attributes = {};
|
|
1223
1252
|
}
|
|
1224
1253
|
|
|
1225
|
-
_extends__default[
|
|
1254
|
+
_extends__default["default"](currentUri.attributes, entry.attributes);
|
|
1226
1255
|
},
|
|
1227
|
-
|
|
1256
|
+
|
|
1257
|
+
media() {
|
|
1228
1258
|
this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
|
|
1229
1259
|
|
|
1230
1260
|
if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) {
|
|
@@ -1235,7 +1265,7 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
1235
1265
|
} // find the media group, creating defaults as necessary
|
|
1236
1266
|
|
|
1237
1267
|
|
|
1238
|
-
|
|
1268
|
+
const mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE];
|
|
1239
1269
|
mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {};
|
|
1240
1270
|
mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']]; // collect the rendition metadata
|
|
1241
1271
|
|
|
@@ -1272,12 +1302,14 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
1272
1302
|
|
|
1273
1303
|
mediaGroup[entry.attributes.NAME] = rendition;
|
|
1274
1304
|
},
|
|
1275
|
-
|
|
1305
|
+
|
|
1306
|
+
discontinuity() {
|
|
1276
1307
|
currentTimeline += 1;
|
|
1277
1308
|
currentUri.discontinuity = true;
|
|
1278
1309
|
this.manifest.discontinuityStarts.push(uris.length);
|
|
1279
1310
|
},
|
|
1280
|
-
|
|
1311
|
+
|
|
1312
|
+
'program-date-time'() {
|
|
1281
1313
|
if (typeof this.manifest.dateTimeString === 'undefined') {
|
|
1282
1314
|
// PROGRAM-DATE-TIME is a media-segment tag, but for backwards
|
|
1283
1315
|
// compatibility, we add the first occurence of the PROGRAM-DATE-TIME tag
|
|
@@ -1289,8 +1321,24 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
1289
1321
|
|
|
1290
1322
|
currentUri.dateTimeString = entry.dateTimeString;
|
|
1291
1323
|
currentUri.dateTimeObject = entry.dateTimeObject;
|
|
1324
|
+
const {
|
|
1325
|
+
lastProgramDateTime
|
|
1326
|
+
} = this;
|
|
1327
|
+
this.lastProgramDateTime = new Date(entry.dateTimeString).getTime(); // We should extrapolate Program Date Time backward only during first program date time occurrence.
|
|
1328
|
+
// Once we have at least one program date time point, we can always extrapolate it forward using lastProgramDateTime reference.
|
|
1329
|
+
|
|
1330
|
+
if (lastProgramDateTime === null) {
|
|
1331
|
+
// Extrapolate Program Date Time backward
|
|
1332
|
+
// Since it is first program date time occurrence we're assuming that
|
|
1333
|
+
// all this.manifest.segments have no program date time info
|
|
1334
|
+
this.manifest.segments.reduceRight((programDateTime, segment) => {
|
|
1335
|
+
segment.programDateTime = programDateTime - segment.duration * 1000;
|
|
1336
|
+
return segment.programDateTime;
|
|
1337
|
+
}, this.lastProgramDateTime);
|
|
1338
|
+
}
|
|
1292
1339
|
},
|
|
1293
|
-
|
|
1340
|
+
|
|
1341
|
+
targetduration() {
|
|
1294
1342
|
if (!isFinite(entry.duration) || entry.duration < 0) {
|
|
1295
1343
|
this.trigger('warn', {
|
|
1296
1344
|
message: 'ignoring invalid target duration: ' + entry.duration
|
|
@@ -1301,7 +1349,8 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
1301
1349
|
this.manifest.targetDuration = entry.duration;
|
|
1302
1350
|
setHoldBack.call(this, this.manifest);
|
|
1303
1351
|
},
|
|
1304
|
-
|
|
1352
|
+
|
|
1353
|
+
start() {
|
|
1305
1354
|
if (!entry.attributes || isNaN(entry.attributes['TIME-OFFSET'])) {
|
|
1306
1355
|
this.trigger('warn', {
|
|
1307
1356
|
message: 'ignoring start declaration without appropriate attribute list'
|
|
@@ -1314,26 +1363,29 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
1314
1363
|
precise: entry.attributes.PRECISE
|
|
1315
1364
|
};
|
|
1316
1365
|
},
|
|
1317
|
-
|
|
1366
|
+
|
|
1367
|
+
'cue-out'() {
|
|
1318
1368
|
currentUri.cueOut = entry.data;
|
|
1319
1369
|
},
|
|
1320
|
-
|
|
1370
|
+
|
|
1371
|
+
'cue-out-cont'() {
|
|
1321
1372
|
currentUri.cueOutCont = entry.data;
|
|
1322
1373
|
},
|
|
1323
|
-
|
|
1374
|
+
|
|
1375
|
+
'cue-in'() {
|
|
1324
1376
|
currentUri.cueIn = entry.data;
|
|
1325
1377
|
},
|
|
1326
|
-
|
|
1378
|
+
|
|
1379
|
+
'skip'() {
|
|
1327
1380
|
this.manifest.skip = camelCaseKeys(entry.attributes);
|
|
1328
1381
|
this.warnOnMissingAttributes_('#EXT-X-SKIP', entry.attributes, ['SKIPPED-SEGMENTS']);
|
|
1329
1382
|
},
|
|
1330
|
-
'part': function part() {
|
|
1331
|
-
var _this2 = this;
|
|
1332
1383
|
|
|
1384
|
+
'part'() {
|
|
1333
1385
|
hasParts = true; // parts are always specifed before a segment
|
|
1334
1386
|
|
|
1335
|
-
|
|
1336
|
-
|
|
1387
|
+
const segmentIndex = this.manifest.segments.length;
|
|
1388
|
+
const part = camelCaseKeys(entry.attributes);
|
|
1337
1389
|
currentUri.parts = currentUri.parts || [];
|
|
1338
1390
|
currentUri.parts.push(part);
|
|
1339
1391
|
|
|
@@ -1345,21 +1397,22 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
1345
1397
|
lastPartByterangeEnd = part.byterange.offset + part.byterange.length;
|
|
1346
1398
|
}
|
|
1347
1399
|
|
|
1348
|
-
|
|
1349
|
-
this.warnOnMissingAttributes_(
|
|
1400
|
+
const partIndex = currentUri.parts.length - 1;
|
|
1401
|
+
this.warnOnMissingAttributes_(`#EXT-X-PART #${partIndex} for segment #${segmentIndex}`, entry.attributes, ['URI', 'DURATION']);
|
|
1350
1402
|
|
|
1351
1403
|
if (this.manifest.renditionReports) {
|
|
1352
|
-
this.manifest.renditionReports.forEach(
|
|
1404
|
+
this.manifest.renditionReports.forEach((r, i) => {
|
|
1353
1405
|
if (!r.hasOwnProperty('lastPart')) {
|
|
1354
|
-
|
|
1355
|
-
message:
|
|
1406
|
+
this.trigger('warn', {
|
|
1407
|
+
message: `#EXT-X-RENDITION-REPORT #${i} lacks required attribute(s): LAST-PART`
|
|
1356
1408
|
});
|
|
1357
1409
|
}
|
|
1358
1410
|
});
|
|
1359
1411
|
}
|
|
1360
1412
|
},
|
|
1361
|
-
|
|
1362
|
-
|
|
1413
|
+
|
|
1414
|
+
'server-control'() {
|
|
1415
|
+
const attrs = this.manifest.serverControl = camelCaseKeys(entry.attributes);
|
|
1363
1416
|
|
|
1364
1417
|
if (!attrs.hasOwnProperty('canBlockReload')) {
|
|
1365
1418
|
attrs.canBlockReload = false;
|
|
@@ -1376,11 +1429,12 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
1376
1429
|
});
|
|
1377
1430
|
}
|
|
1378
1431
|
},
|
|
1379
|
-
|
|
1432
|
+
|
|
1433
|
+
'preload-hint'() {
|
|
1380
1434
|
// parts are always specifed before a segment
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1435
|
+
const segmentIndex = this.manifest.segments.length;
|
|
1436
|
+
const hint = camelCaseKeys(entry.attributes);
|
|
1437
|
+
const isPart = hint.type && hint.type === 'PART';
|
|
1384
1438
|
currentUri.preloadHints = currentUri.preloadHints || [];
|
|
1385
1439
|
currentUri.preloadHints.push(hint);
|
|
1386
1440
|
|
|
@@ -1395,8 +1449,8 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
1395
1449
|
}
|
|
1396
1450
|
}
|
|
1397
1451
|
|
|
1398
|
-
|
|
1399
|
-
this.warnOnMissingAttributes_(
|
|
1452
|
+
const index = currentUri.preloadHints.length - 1;
|
|
1453
|
+
this.warnOnMissingAttributes_(`#EXT-X-PRELOAD-HINT #${index} for segment #${segmentIndex}`, entry.attributes, ['TYPE', 'URI']);
|
|
1400
1454
|
|
|
1401
1455
|
if (!hint.type) {
|
|
1402
1456
|
return;
|
|
@@ -1404,8 +1458,8 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
1404
1458
|
// a duplicate type.
|
|
1405
1459
|
|
|
1406
1460
|
|
|
1407
|
-
for (
|
|
1408
|
-
|
|
1461
|
+
for (let i = 0; i < currentUri.preloadHints.length - 1; i++) {
|
|
1462
|
+
const otherHint = currentUri.preloadHints[i];
|
|
1409
1463
|
|
|
1410
1464
|
if (!otherHint.type) {
|
|
1411
1465
|
continue;
|
|
@@ -1413,25 +1467,27 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
1413
1467
|
|
|
1414
1468
|
if (otherHint.type === hint.type) {
|
|
1415
1469
|
this.trigger('warn', {
|
|
1416
|
-
message:
|
|
1470
|
+
message: `#EXT-X-PRELOAD-HINT #${index} for segment #${segmentIndex} has the same TYPE ${hint.type} as preload hint #${i}`
|
|
1417
1471
|
});
|
|
1418
1472
|
}
|
|
1419
1473
|
}
|
|
1420
1474
|
},
|
|
1421
|
-
|
|
1422
|
-
|
|
1475
|
+
|
|
1476
|
+
'rendition-report'() {
|
|
1477
|
+
const report = camelCaseKeys(entry.attributes);
|
|
1423
1478
|
this.manifest.renditionReports = this.manifest.renditionReports || [];
|
|
1424
1479
|
this.manifest.renditionReports.push(report);
|
|
1425
|
-
|
|
1426
|
-
|
|
1480
|
+
const index = this.manifest.renditionReports.length - 1;
|
|
1481
|
+
const required = ['LAST-MSN', 'URI'];
|
|
1427
1482
|
|
|
1428
1483
|
if (hasParts) {
|
|
1429
1484
|
required.push('LAST-PART');
|
|
1430
1485
|
}
|
|
1431
1486
|
|
|
1432
|
-
this.warnOnMissingAttributes_(
|
|
1487
|
+
this.warnOnMissingAttributes_(`#EXT-X-RENDITION-REPORT #${index}`, entry.attributes, required);
|
|
1433
1488
|
},
|
|
1434
|
-
|
|
1489
|
+
|
|
1490
|
+
'part-inf'() {
|
|
1435
1491
|
this.manifest.partInf = camelCaseKeys(entry.attributes);
|
|
1436
1492
|
this.warnOnMissingAttributes_('#EXT-X-PART-INF', entry.attributes, ['PART-TARGET']);
|
|
1437
1493
|
|
|
@@ -1440,13 +1496,89 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
1440
1496
|
}
|
|
1441
1497
|
|
|
1442
1498
|
setHoldBack.call(this, this.manifest);
|
|
1499
|
+
},
|
|
1500
|
+
|
|
1501
|
+
'daterange'() {
|
|
1502
|
+
this.manifest.dateRanges.push(camelCaseKeys(entry.attributes));
|
|
1503
|
+
const index = this.manifest.dateRanges.length - 1;
|
|
1504
|
+
this.warnOnMissingAttributes_(`#EXT-X-DATERANGE #${index}`, entry.attributes, ['ID', 'START-DATE']);
|
|
1505
|
+
const dateRange = this.manifest.dateRanges[index];
|
|
1506
|
+
|
|
1507
|
+
if (dateRange.endDate && dateRange.startDate && new Date(dateRange.endDate) < new Date(dateRange.startDate)) {
|
|
1508
|
+
this.trigger('warn', {
|
|
1509
|
+
message: 'EXT-X-DATERANGE END-DATE must be equal to or later than the value of the START-DATE'
|
|
1510
|
+
});
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
if (dateRange.duration && dateRange.duration < 0) {
|
|
1514
|
+
this.trigger('warn', {
|
|
1515
|
+
message: 'EXT-X-DATERANGE DURATION must not be negative'
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
if (dateRange.plannedDuration && dateRange.plannedDuration < 0) {
|
|
1520
|
+
this.trigger('warn', {
|
|
1521
|
+
message: 'EXT-X-DATERANGE PLANNED-DURATION must not be negative'
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
const endOnNextYes = !!dateRange.endOnNext;
|
|
1526
|
+
|
|
1527
|
+
if (endOnNextYes && !dateRange.class) {
|
|
1528
|
+
this.trigger('warn', {
|
|
1529
|
+
message: 'EXT-X-DATERANGE with an END-ON-NEXT=YES attribute must have a CLASS attribute'
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
if (endOnNextYes && (dateRange.duration || dateRange.endDate)) {
|
|
1534
|
+
this.trigger('warn', {
|
|
1535
|
+
message: 'EXT-X-DATERANGE with an END-ON-NEXT=YES attribute must not contain DURATION or END-DATE attributes'
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
if (dateRange.duration && dateRange.endDate) {
|
|
1540
|
+
const startDate = dateRange.startDate;
|
|
1541
|
+
const newDateInSeconds = startDate.getTime() + dateRange.duration * 1000;
|
|
1542
|
+
this.manifest.dateRanges[index].endDate = new Date(newDateInSeconds);
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
if (!dateRangeTags[dateRange.id]) {
|
|
1546
|
+
dateRangeTags[dateRange.id] = dateRange;
|
|
1547
|
+
} else {
|
|
1548
|
+
for (const attribute in dateRangeTags[dateRange.id]) {
|
|
1549
|
+
if (!!dateRange[attribute] && JSON.stringify(dateRangeTags[dateRange.id][attribute]) !== JSON.stringify(dateRange[attribute])) {
|
|
1550
|
+
this.trigger('warn', {
|
|
1551
|
+
message: 'EXT-X-DATERANGE tags with the same ID in a playlist must have the same attributes values'
|
|
1552
|
+
});
|
|
1553
|
+
break;
|
|
1554
|
+
}
|
|
1555
|
+
} // if tags with the same ID do not have conflicting attributes, merge them
|
|
1556
|
+
|
|
1557
|
+
|
|
1558
|
+
const dateRangeWithSameId = this.manifest.dateRanges.findIndex(dateRangeToFind => dateRangeToFind.id === dateRange.id);
|
|
1559
|
+
this.manifest.dateRanges[dateRangeWithSameId] = _extends__default["default"](this.manifest.dateRanges[dateRangeWithSameId], dateRange);
|
|
1560
|
+
dateRangeTags[dateRange.id] = _extends__default["default"](dateRangeTags[dateRange.id], dateRange); // after merging, delete the duplicate dateRange that was added last
|
|
1561
|
+
|
|
1562
|
+
this.manifest.dateRanges.pop();
|
|
1563
|
+
}
|
|
1564
|
+
},
|
|
1565
|
+
|
|
1566
|
+
'independent-segments'() {
|
|
1567
|
+
this.manifest.independentSegments = true;
|
|
1568
|
+
},
|
|
1569
|
+
|
|
1570
|
+
'content-steering'() {
|
|
1571
|
+
this.manifest.contentSteering = camelCaseKeys(entry.attributes);
|
|
1572
|
+
this.warnOnMissingAttributes_('#EXT-X-CONTENT-STEERING', entry.attributes, ['SERVER-URI']);
|
|
1443
1573
|
}
|
|
1574
|
+
|
|
1444
1575
|
})[entry.tagType] || noop).call(self);
|
|
1445
1576
|
},
|
|
1446
|
-
|
|
1577
|
+
|
|
1578
|
+
uri() {
|
|
1447
1579
|
currentUri.uri = entry.uri;
|
|
1448
1580
|
currentUri.lineNumberStart = nextSegmentLineNumberStart;
|
|
1449
|
-
currentUri.lineNumberEnd = this.
|
|
1581
|
+
currentUri.lineNumberEnd = this.lineStream.lineNumber;
|
|
1450
1582
|
uris.push(currentUri); // if no explicit duration was declared, use the target duration
|
|
1451
1583
|
|
|
1452
1584
|
if (this.manifest.targetDuration && !('duration' in currentUri)) {
|
|
@@ -1457,8 +1589,8 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
1457
1589
|
} // annotate with encryption information, if necessary
|
|
1458
1590
|
|
|
1459
1591
|
|
|
1460
|
-
if (
|
|
1461
|
-
currentUri.key =
|
|
1592
|
+
if (key) {
|
|
1593
|
+
currentUri.key = key;
|
|
1462
1594
|
}
|
|
1463
1595
|
|
|
1464
1596
|
currentUri.timeline = currentTimeline; // annotate with initialization segment information, if necessary
|
|
@@ -1468,13 +1600,21 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
1468
1600
|
} // reset the last byterange end as it needs to be 0 between parts
|
|
1469
1601
|
|
|
1470
1602
|
|
|
1471
|
-
lastPartByterangeEnd = 0; //
|
|
1603
|
+
lastPartByterangeEnd = 0; // Once we have at least one program date time we can always extrapolate it forward
|
|
1604
|
+
|
|
1605
|
+
if (this.lastProgramDateTime !== null) {
|
|
1606
|
+
currentUri.programDateTime = this.lastProgramDateTime;
|
|
1607
|
+
this.lastProgramDateTime += currentUri.duration * 1000;
|
|
1608
|
+
} // prepare for the next URI
|
|
1609
|
+
|
|
1472
1610
|
|
|
1473
1611
|
currentUri = {};
|
|
1474
1612
|
},
|
|
1475
|
-
|
|
1613
|
+
|
|
1614
|
+
comment() {// comments are not important for playback
|
|
1476
1615
|
},
|
|
1477
|
-
|
|
1616
|
+
|
|
1617
|
+
custom() {
|
|
1478
1618
|
// if this is segment-level data attach the output to the segment
|
|
1479
1619
|
if (entry.segment) {
|
|
1480
1620
|
currentUri.custom = currentUri.custom || {};
|
|
@@ -1484,16 +1624,13 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
1484
1624
|
this.manifest.custom[entry.customType] = entry.data;
|
|
1485
1625
|
}
|
|
1486
1626
|
}
|
|
1627
|
+
|
|
1487
1628
|
})[entry.type].call(self);
|
|
1488
1629
|
});
|
|
1489
|
-
|
|
1490
|
-
return _this;
|
|
1491
1630
|
}
|
|
1492
1631
|
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
_proto.warnOnMissingAttributes_ = function warnOnMissingAttributes_(identifier, attributes, required) {
|
|
1496
|
-
var missing = [];
|
|
1632
|
+
warnOnMissingAttributes_(identifier, attributes, required) {
|
|
1633
|
+
const missing = [];
|
|
1497
1634
|
required.forEach(function (key) {
|
|
1498
1635
|
if (!attributes.hasOwnProperty(key)) {
|
|
1499
1636
|
missing.push(key);
|
|
@@ -1502,7 +1639,7 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
1502
1639
|
|
|
1503
1640
|
if (missing.length) {
|
|
1504
1641
|
this.trigger('warn', {
|
|
1505
|
-
message: identifier
|
|
1642
|
+
message: `${identifier} lacks required attribute(s): ${missing.join(', ')}`
|
|
1506
1643
|
});
|
|
1507
1644
|
}
|
|
1508
1645
|
}
|
|
@@ -1511,9 +1648,9 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
1511
1648
|
*
|
|
1512
1649
|
* @param {string} chunk a potentially incomplete portion of the manifest
|
|
1513
1650
|
*/
|
|
1514
|
-
;
|
|
1515
1651
|
|
|
1516
|
-
|
|
1652
|
+
|
|
1653
|
+
push(chunk) {
|
|
1517
1654
|
this.lineStream.push(chunk);
|
|
1518
1655
|
}
|
|
1519
1656
|
/**
|
|
@@ -1521,11 +1658,19 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
1521
1658
|
* manifest did not contain a trailing newline but the file has been
|
|
1522
1659
|
* completely received.
|
|
1523
1660
|
*/
|
|
1524
|
-
;
|
|
1525
1661
|
|
|
1526
|
-
|
|
1662
|
+
|
|
1663
|
+
end() {
|
|
1527
1664
|
// flush any buffered input
|
|
1528
1665
|
this.lineStream.push('\n');
|
|
1666
|
+
|
|
1667
|
+
if (this.manifest.dateRanges.length && this.lastProgramDateTime === null) {
|
|
1668
|
+
this.trigger('warn', {
|
|
1669
|
+
message: 'A playlist with EXT-X-DATERANGE tag must contain atleast one EXT-X-PROGRAM-DATE-TIME tag'
|
|
1670
|
+
});
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
this.lastProgramDateTime = null;
|
|
1529
1674
|
this.trigger('end');
|
|
1530
1675
|
}
|
|
1531
1676
|
/**
|
|
@@ -1533,13 +1678,13 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
1533
1678
|
*
|
|
1534
1679
|
* @param {Object} options a map of options for the added parser
|
|
1535
1680
|
* @param {RegExp} options.expression a regular expression to match the custom header
|
|
1536
|
-
* @param {string} options.
|
|
1681
|
+
* @param {string} options.customType the custom type to register to the output
|
|
1537
1682
|
* @param {Function} [options.dataParser] function to parse the line into an object
|
|
1538
1683
|
* @param {boolean} [options.segment] should tag data be attached to the segment object
|
|
1539
1684
|
*/
|
|
1540
|
-
;
|
|
1541
1685
|
|
|
1542
|
-
|
|
1686
|
+
|
|
1687
|
+
addParser(options) {
|
|
1543
1688
|
this.parseStream.addParser(options);
|
|
1544
1689
|
}
|
|
1545
1690
|
/**
|
|
@@ -1549,14 +1694,13 @@ var Parser = /*#__PURE__*/function (_Stream) {
|
|
|
1549
1694
|
* @param {RegExp} options.expression a regular expression to match the custom header
|
|
1550
1695
|
* @param {Function} options.map function to translate tag into a different tag
|
|
1551
1696
|
*/
|
|
1552
|
-
;
|
|
1553
1697
|
|
|
1554
|
-
|
|
1698
|
+
|
|
1699
|
+
addTagMapper(options) {
|
|
1555
1700
|
this.parseStream.addTagMapper(options);
|
|
1556
|
-
}
|
|
1701
|
+
}
|
|
1557
1702
|
|
|
1558
|
-
|
|
1559
|
-
}(Stream__default['default']);
|
|
1703
|
+
}
|
|
1560
1704
|
|
|
1561
1705
|
exports.LineStream = LineStream;
|
|
1562
1706
|
exports.ParseStream = ParseStream;
|