@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.js
CHANGED
|
@@ -1,21 +1,9 @@
|
|
|
1
|
-
/*! @name @transmitlive/m3u8-parser @version
|
|
1
|
+
/*! @name @transmitlive/m3u8-parser @version 7.1.0-0 @license Apache-2.0 */
|
|
2
2
|
(function (global, factory) {
|
|
3
|
-
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports
|
|
4
|
-
typeof define === 'function' && define.amd ? define(['exports'
|
|
5
|
-
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.m3u8Parser = {}
|
|
6
|
-
}(this, (function (exports
|
|
7
|
-
|
|
8
|
-
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
9
|
-
|
|
10
|
-
var window__default = /*#__PURE__*/_interopDefaultLegacy(window);
|
|
11
|
-
|
|
12
|
-
function _inheritsLoose(subClass, superClass) {
|
|
13
|
-
subClass.prototype = Object.create(superClass.prototype);
|
|
14
|
-
subClass.prototype.constructor = subClass;
|
|
15
|
-
subClass.__proto__ = superClass;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
var inheritsLoose = _inheritsLoose;
|
|
3
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
|
4
|
+
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
|
5
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.m3u8Parser = {}));
|
|
6
|
+
})(this, (function (exports) { 'use strict';
|
|
19
7
|
|
|
20
8
|
/**
|
|
21
9
|
* @file stream.js
|
|
@@ -137,6 +125,9 @@
|
|
|
137
125
|
return Stream;
|
|
138
126
|
}();
|
|
139
127
|
|
|
128
|
+
/**
|
|
129
|
+
* @file m3u8/line-stream.js
|
|
130
|
+
*/
|
|
140
131
|
/**
|
|
141
132
|
* A stream that buffers string input and generates a `data` event for each
|
|
142
133
|
* line.
|
|
@@ -145,15 +136,11 @@
|
|
|
145
136
|
* @extends Stream
|
|
146
137
|
*/
|
|
147
138
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
_this = _Stream.call(this) || this;
|
|
155
|
-
_this.buffer = '';
|
|
156
|
-
return _this;
|
|
139
|
+
class LineStream extends Stream {
|
|
140
|
+
constructor() {
|
|
141
|
+
super();
|
|
142
|
+
this.buffer = '';
|
|
143
|
+
this.lineNumber = 0;
|
|
157
144
|
}
|
|
158
145
|
/**
|
|
159
146
|
* Add new data to be parsed.
|
|
@@ -162,65 +149,48 @@
|
|
|
162
149
|
*/
|
|
163
150
|
|
|
164
151
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
_proto.push = function push(data) {
|
|
168
|
-
var nextNewline;
|
|
152
|
+
push(data) {
|
|
153
|
+
let nextNewline;
|
|
169
154
|
this.buffer += data;
|
|
155
|
+
this.lineNumber += 1;
|
|
170
156
|
nextNewline = this.buffer.indexOf('\n');
|
|
171
157
|
|
|
172
158
|
for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) {
|
|
173
159
|
this.trigger('data', this.buffer.substring(0, nextNewline));
|
|
174
160
|
this.buffer = this.buffer.substring(nextNewline + 1);
|
|
175
161
|
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return LineStream;
|
|
179
|
-
}(Stream);
|
|
180
|
-
|
|
181
|
-
function createCommonjsModule(fn, basedir, module) {
|
|
182
|
-
return module = {
|
|
183
|
-
path: basedir,
|
|
184
|
-
exports: {},
|
|
185
|
-
require: function (path, base) {
|
|
186
|
-
return commonjsRequire(path, (base === undefined || base === null) ? module.path : base);
|
|
187
|
-
}
|
|
188
|
-
}, fn(module, module.exports), module.exports;
|
|
189
|
-
}
|
|
162
|
+
}
|
|
190
163
|
|
|
191
|
-
function commonjsRequire () {
|
|
192
|
-
throw new Error('Dynamic requires are not currently supported by @rollup/plugin-commonjs');
|
|
193
164
|
}
|
|
194
165
|
|
|
195
|
-
|
|
196
|
-
function
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
var source = arguments[i];
|
|
166
|
+
function _extends() {
|
|
167
|
+
_extends_1 = _extends = Object.assign || function (target) {
|
|
168
|
+
for (var i = 1; i < arguments.length; i++) {
|
|
169
|
+
var source = arguments[i];
|
|
200
170
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
171
|
+
for (var key in source) {
|
|
172
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
173
|
+
target[key] = source[key];
|
|
205
174
|
}
|
|
206
175
|
}
|
|
176
|
+
}
|
|
207
177
|
|
|
208
|
-
|
|
209
|
-
|
|
178
|
+
return target;
|
|
179
|
+
};
|
|
210
180
|
|
|
211
|
-
|
|
212
|
-
|
|
181
|
+
return _extends.apply(this, arguments);
|
|
182
|
+
}
|
|
213
183
|
|
|
214
|
-
|
|
215
|
-
|
|
184
|
+
var _extends_1 = _extends;
|
|
185
|
+
var _extends$1 = _extends_1;
|
|
216
186
|
|
|
217
|
-
|
|
187
|
+
const TAB = String.fromCharCode(0x09);
|
|
218
188
|
|
|
219
|
-
|
|
189
|
+
const parseByterange = function (byterangeString) {
|
|
220
190
|
// optionally match and capture 0+ digits before `@`
|
|
221
191
|
// optionally match and capture 0+ digits after `@`
|
|
222
|
-
|
|
223
|
-
|
|
192
|
+
const match = /([0-9.]*)?@?([0-9.]*)?/.exec(byterangeString || '');
|
|
193
|
+
const result = {};
|
|
224
194
|
|
|
225
195
|
if (match[1]) {
|
|
226
196
|
result.length = parseInt(match[1], 10);
|
|
@@ -241,10 +211,10 @@
|
|
|
241
211
|
*/
|
|
242
212
|
|
|
243
213
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
214
|
+
const attributeSeparator = function () {
|
|
215
|
+
const key = '[^=]*';
|
|
216
|
+
const value = '"[^"]*"|[^,]*';
|
|
217
|
+
const keyvalue = '(?:' + key + ')=(?:' + value + ')';
|
|
248
218
|
return new RegExp('(?:^|,)(' + keyvalue + ')');
|
|
249
219
|
};
|
|
250
220
|
/**
|
|
@@ -254,12 +224,17 @@
|
|
|
254
224
|
*/
|
|
255
225
|
|
|
256
226
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
227
|
+
const parseAttributes = function (attributes) {
|
|
228
|
+
const result = {};
|
|
229
|
+
|
|
230
|
+
if (!attributes) {
|
|
231
|
+
return result;
|
|
232
|
+
} // split the string using attributes as the separator
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
const attrs = attributes.split(attributeSeparator());
|
|
236
|
+
let i = attrs.length;
|
|
237
|
+
let attr;
|
|
263
238
|
|
|
264
239
|
while (i--) {
|
|
265
240
|
// filter out unmatched portions of the string
|
|
@@ -304,17 +279,11 @@
|
|
|
304
279
|
*/
|
|
305
280
|
|
|
306
281
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
_this = _Stream.call(this) || this;
|
|
314
|
-
_this.customParsers = [];
|
|
315
|
-
_this.tagMappers = [];
|
|
316
|
-
_this.lineNumber = 0;
|
|
317
|
-
return _this;
|
|
282
|
+
class ParseStream extends Stream {
|
|
283
|
+
constructor() {
|
|
284
|
+
super();
|
|
285
|
+
this.customParsers = [];
|
|
286
|
+
this.tagMappers = [];
|
|
318
287
|
}
|
|
319
288
|
/**
|
|
320
289
|
* Parses an additional line of input.
|
|
@@ -323,14 +292,9 @@
|
|
|
323
292
|
*/
|
|
324
293
|
|
|
325
294
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
var _this2 = this;
|
|
330
|
-
|
|
331
|
-
var match;
|
|
332
|
-
var event;
|
|
333
|
-
this.lineNumber = this.lineNumber + 1; // strip whitespace
|
|
295
|
+
push(line) {
|
|
296
|
+
let match;
|
|
297
|
+
let event; // strip whitespace
|
|
334
298
|
|
|
335
299
|
line = line.trim();
|
|
336
300
|
|
|
@@ -349,8 +313,8 @@
|
|
|
349
313
|
} // map tags
|
|
350
314
|
|
|
351
315
|
|
|
352
|
-
|
|
353
|
-
|
|
316
|
+
const newLines = this.tagMappers.reduce((acc, mapper) => {
|
|
317
|
+
const mappedLine = mapper(line); // skip if unchanged
|
|
354
318
|
|
|
355
319
|
if (mappedLine === line) {
|
|
356
320
|
return acc;
|
|
@@ -358,20 +322,19 @@
|
|
|
358
322
|
|
|
359
323
|
return acc.concat([mappedLine]);
|
|
360
324
|
}, [line]);
|
|
361
|
-
newLines.forEach(
|
|
362
|
-
for (
|
|
363
|
-
if (
|
|
325
|
+
newLines.forEach(newLine => {
|
|
326
|
+
for (let i = 0; i < this.customParsers.length; i++) {
|
|
327
|
+
if (this.customParsers[i].call(this, newLine)) {
|
|
364
328
|
return;
|
|
365
329
|
}
|
|
366
330
|
} // Comments
|
|
367
331
|
|
|
368
332
|
|
|
369
333
|
if (newLine.indexOf('#EXT') !== 0) {
|
|
370
|
-
|
|
334
|
+
this.trigger('data', {
|
|
371
335
|
type: 'comment',
|
|
372
336
|
text: newLine.slice(1)
|
|
373
337
|
});
|
|
374
|
-
|
|
375
338
|
return;
|
|
376
339
|
} // strip off any carriage returns here so the regex matching
|
|
377
340
|
// doesn't have to account for them.
|
|
@@ -382,15 +345,14 @@
|
|
|
382
345
|
match = /^#EXTM3U/.exec(newLine);
|
|
383
346
|
|
|
384
347
|
if (match) {
|
|
385
|
-
|
|
348
|
+
this.trigger('data', {
|
|
386
349
|
type: 'tag',
|
|
387
350
|
tagType: 'm3u'
|
|
388
351
|
});
|
|
389
|
-
|
|
390
352
|
return;
|
|
391
353
|
}
|
|
392
354
|
|
|
393
|
-
match = /^#EXTINF
|
|
355
|
+
match = /^#EXTINF:([0-9\.]*)?,?(.*)?$/.exec(newLine);
|
|
394
356
|
|
|
395
357
|
if (match) {
|
|
396
358
|
event = {
|
|
@@ -406,12 +368,11 @@
|
|
|
406
368
|
event.title = match[2];
|
|
407
369
|
}
|
|
408
370
|
|
|
409
|
-
|
|
410
|
-
|
|
371
|
+
this.trigger('data', event);
|
|
411
372
|
return;
|
|
412
373
|
}
|
|
413
374
|
|
|
414
|
-
match = /^#EXT-X-TARGETDURATION
|
|
375
|
+
match = /^#EXT-X-TARGETDURATION:([0-9.]*)?/.exec(newLine);
|
|
415
376
|
|
|
416
377
|
if (match) {
|
|
417
378
|
event = {
|
|
@@ -423,12 +384,11 @@
|
|
|
423
384
|
event.duration = parseInt(match[1], 10);
|
|
424
385
|
}
|
|
425
386
|
|
|
426
|
-
|
|
427
|
-
|
|
387
|
+
this.trigger('data', event);
|
|
428
388
|
return;
|
|
429
389
|
}
|
|
430
390
|
|
|
431
|
-
match = /^#EXT-X-VERSION
|
|
391
|
+
match = /^#EXT-X-VERSION:([0-9.]*)?/.exec(newLine);
|
|
432
392
|
|
|
433
393
|
if (match) {
|
|
434
394
|
event = {
|
|
@@ -440,12 +400,11 @@
|
|
|
440
400
|
event.version = parseInt(match[1], 10);
|
|
441
401
|
}
|
|
442
402
|
|
|
443
|
-
|
|
444
|
-
|
|
403
|
+
this.trigger('data', event);
|
|
445
404
|
return;
|
|
446
405
|
}
|
|
447
406
|
|
|
448
|
-
match = /^#EXT-X-MEDIA-SEQUENCE
|
|
407
|
+
match = /^#EXT-X-MEDIA-SEQUENCE:(\-?[0-9.]*)?/.exec(newLine);
|
|
449
408
|
|
|
450
409
|
if (match) {
|
|
451
410
|
event = {
|
|
@@ -457,12 +416,11 @@
|
|
|
457
416
|
event.number = parseInt(match[1], 10);
|
|
458
417
|
}
|
|
459
418
|
|
|
460
|
-
|
|
461
|
-
|
|
419
|
+
this.trigger('data', event);
|
|
462
420
|
return;
|
|
463
421
|
}
|
|
464
422
|
|
|
465
|
-
match = /^#EXT-X-DISCONTINUITY-SEQUENCE
|
|
423
|
+
match = /^#EXT-X-DISCONTINUITY-SEQUENCE:(\-?[0-9.]*)?/.exec(newLine);
|
|
466
424
|
|
|
467
425
|
if (match) {
|
|
468
426
|
event = {
|
|
@@ -474,12 +432,11 @@
|
|
|
474
432
|
event.number = parseInt(match[1], 10);
|
|
475
433
|
}
|
|
476
434
|
|
|
477
|
-
|
|
478
|
-
|
|
435
|
+
this.trigger('data', event);
|
|
479
436
|
return;
|
|
480
437
|
}
|
|
481
438
|
|
|
482
|
-
match = /^#EXT-X-PLAYLIST-TYPE
|
|
439
|
+
match = /^#EXT-X-PLAYLIST-TYPE:(.*)?$/.exec(newLine);
|
|
483
440
|
|
|
484
441
|
if (match) {
|
|
485
442
|
event = {
|
|
@@ -491,25 +448,22 @@
|
|
|
491
448
|
event.playlistType = match[1];
|
|
492
449
|
}
|
|
493
450
|
|
|
494
|
-
|
|
495
|
-
|
|
451
|
+
this.trigger('data', event);
|
|
496
452
|
return;
|
|
497
453
|
}
|
|
498
454
|
|
|
499
|
-
match = /^#EXT-X-BYTERANGE
|
|
455
|
+
match = /^#EXT-X-BYTERANGE:(.*)?$/.exec(newLine);
|
|
500
456
|
|
|
501
457
|
if (match) {
|
|
502
|
-
event =
|
|
458
|
+
event = _extends$1(parseByterange(match[1]), {
|
|
503
459
|
type: 'tag',
|
|
504
460
|
tagType: 'byterange'
|
|
505
461
|
});
|
|
506
|
-
|
|
507
|
-
_this2.trigger('data', event);
|
|
508
|
-
|
|
462
|
+
this.trigger('data', event);
|
|
509
463
|
return;
|
|
510
464
|
}
|
|
511
465
|
|
|
512
|
-
match = /^#EXT-X-ALLOW-CACHE
|
|
466
|
+
match = /^#EXT-X-ALLOW-CACHE:(YES|NO)?/.exec(newLine);
|
|
513
467
|
|
|
514
468
|
if (match) {
|
|
515
469
|
event = {
|
|
@@ -521,12 +475,11 @@
|
|
|
521
475
|
event.allowed = !/NO/.test(match[1]);
|
|
522
476
|
}
|
|
523
477
|
|
|
524
|
-
|
|
525
|
-
|
|
478
|
+
this.trigger('data', event);
|
|
526
479
|
return;
|
|
527
480
|
}
|
|
528
481
|
|
|
529
|
-
match = /^#EXT-X-MAP
|
|
482
|
+
match = /^#EXT-X-MAP:(.*)$/.exec(newLine);
|
|
530
483
|
|
|
531
484
|
if (match) {
|
|
532
485
|
event = {
|
|
@@ -535,7 +488,7 @@
|
|
|
535
488
|
};
|
|
536
489
|
|
|
537
490
|
if (match[1]) {
|
|
538
|
-
|
|
491
|
+
const attributes = parseAttributes(match[1]);
|
|
539
492
|
|
|
540
493
|
if (attributes.URI) {
|
|
541
494
|
event.uri = attributes.URI;
|
|
@@ -546,12 +499,11 @@
|
|
|
546
499
|
}
|
|
547
500
|
}
|
|
548
501
|
|
|
549
|
-
|
|
550
|
-
|
|
502
|
+
this.trigger('data', event);
|
|
551
503
|
return;
|
|
552
504
|
}
|
|
553
505
|
|
|
554
|
-
match = /^#EXT-X-STREAM-INF
|
|
506
|
+
match = /^#EXT-X-STREAM-INF:(.*)$/.exec(newLine);
|
|
555
507
|
|
|
556
508
|
if (match) {
|
|
557
509
|
event = {
|
|
@@ -563,8 +515,8 @@
|
|
|
563
515
|
event.attributes = parseAttributes(match[1]);
|
|
564
516
|
|
|
565
517
|
if (event.attributes.RESOLUTION) {
|
|
566
|
-
|
|
567
|
-
|
|
518
|
+
const split = event.attributes.RESOLUTION.split('x');
|
|
519
|
+
const resolution = {};
|
|
568
520
|
|
|
569
521
|
if (split[0]) {
|
|
570
522
|
resolution.width = parseInt(split[0], 10);
|
|
@@ -581,17 +533,20 @@
|
|
|
581
533
|
event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
|
|
582
534
|
}
|
|
583
535
|
|
|
536
|
+
if (event.attributes['FRAME-RATE']) {
|
|
537
|
+
event.attributes['FRAME-RATE'] = parseFloat(event.attributes['FRAME-RATE']);
|
|
538
|
+
}
|
|
539
|
+
|
|
584
540
|
if (event.attributes['PROGRAM-ID']) {
|
|
585
541
|
event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10);
|
|
586
542
|
}
|
|
587
543
|
}
|
|
588
544
|
|
|
589
|
-
|
|
590
|
-
|
|
545
|
+
this.trigger('data', event);
|
|
591
546
|
return;
|
|
592
547
|
}
|
|
593
548
|
|
|
594
|
-
match = /^#EXT-X-MEDIA
|
|
549
|
+
match = /^#EXT-X-MEDIA:(.*)$/.exec(newLine);
|
|
595
550
|
|
|
596
551
|
if (match) {
|
|
597
552
|
event = {
|
|
@@ -603,34 +558,31 @@
|
|
|
603
558
|
event.attributes = parseAttributes(match[1]);
|
|
604
559
|
}
|
|
605
560
|
|
|
606
|
-
|
|
607
|
-
|
|
561
|
+
this.trigger('data', event);
|
|
608
562
|
return;
|
|
609
563
|
}
|
|
610
564
|
|
|
611
565
|
match = /^#EXT-X-ENDLIST/.exec(newLine);
|
|
612
566
|
|
|
613
567
|
if (match) {
|
|
614
|
-
|
|
568
|
+
this.trigger('data', {
|
|
615
569
|
type: 'tag',
|
|
616
570
|
tagType: 'endlist'
|
|
617
571
|
});
|
|
618
|
-
|
|
619
572
|
return;
|
|
620
573
|
}
|
|
621
574
|
|
|
622
575
|
match = /^#EXT-X-DISCONTINUITY/.exec(newLine);
|
|
623
576
|
|
|
624
577
|
if (match) {
|
|
625
|
-
|
|
578
|
+
this.trigger('data', {
|
|
626
579
|
type: 'tag',
|
|
627
580
|
tagType: 'discontinuity'
|
|
628
581
|
});
|
|
629
|
-
|
|
630
582
|
return;
|
|
631
583
|
}
|
|
632
584
|
|
|
633
|
-
match = /^#EXT-X-PROGRAM-DATE-TIME
|
|
585
|
+
match = /^#EXT-X-PROGRAM-DATE-TIME:(.*)$/.exec(newLine);
|
|
634
586
|
|
|
635
587
|
if (match) {
|
|
636
588
|
event = {
|
|
@@ -643,12 +595,11 @@
|
|
|
643
595
|
event.dateTimeObject = new Date(match[1]);
|
|
644
596
|
}
|
|
645
597
|
|
|
646
|
-
|
|
647
|
-
|
|
598
|
+
this.trigger('data', event);
|
|
648
599
|
return;
|
|
649
600
|
}
|
|
650
601
|
|
|
651
|
-
match = /^#EXT-X-KEY
|
|
602
|
+
match = /^#EXT-X-KEY:(.*)$/.exec(newLine);
|
|
652
603
|
|
|
653
604
|
if (match) {
|
|
654
605
|
event = {
|
|
@@ -673,12 +624,11 @@
|
|
|
673
624
|
}
|
|
674
625
|
}
|
|
675
626
|
|
|
676
|
-
|
|
677
|
-
|
|
627
|
+
this.trigger('data', event);
|
|
678
628
|
return;
|
|
679
629
|
}
|
|
680
630
|
|
|
681
|
-
match = /^#EXT-X-START
|
|
631
|
+
match = /^#EXT-X-START:(.*)$/.exec(newLine);
|
|
682
632
|
|
|
683
633
|
if (match) {
|
|
684
634
|
event = {
|
|
@@ -692,12 +642,11 @@
|
|
|
692
642
|
event.attributes.PRECISE = /YES/.test(event.attributes.PRECISE);
|
|
693
643
|
}
|
|
694
644
|
|
|
695
|
-
|
|
696
|
-
|
|
645
|
+
this.trigger('data', event);
|
|
697
646
|
return;
|
|
698
647
|
}
|
|
699
648
|
|
|
700
|
-
match = /^#EXT-X-CUE-OUT-CONT
|
|
649
|
+
match = /^#EXT-X-CUE-OUT-CONT:(.*)?$/.exec(newLine);
|
|
701
650
|
|
|
702
651
|
if (match) {
|
|
703
652
|
event = {
|
|
@@ -711,12 +660,11 @@
|
|
|
711
660
|
event.data = '';
|
|
712
661
|
}
|
|
713
662
|
|
|
714
|
-
|
|
715
|
-
|
|
663
|
+
this.trigger('data', event);
|
|
716
664
|
return;
|
|
717
665
|
}
|
|
718
666
|
|
|
719
|
-
match = /^#EXT-X-CUE-OUT
|
|
667
|
+
match = /^#EXT-X-CUE-OUT:(.*)?$/.exec(newLine);
|
|
720
668
|
|
|
721
669
|
if (match) {
|
|
722
670
|
event = {
|
|
@@ -730,12 +678,11 @@
|
|
|
730
678
|
event.data = '';
|
|
731
679
|
}
|
|
732
680
|
|
|
733
|
-
|
|
734
|
-
|
|
681
|
+
this.trigger('data', event);
|
|
735
682
|
return;
|
|
736
683
|
}
|
|
737
684
|
|
|
738
|
-
match = /^#EXT-X-CUE-IN
|
|
685
|
+
match = /^#EXT-X-CUE-IN:(.*)?$/.exec(newLine);
|
|
739
686
|
|
|
740
687
|
if (match) {
|
|
741
688
|
event = {
|
|
@@ -749,8 +696,7 @@
|
|
|
749
696
|
event.data = '';
|
|
750
697
|
}
|
|
751
698
|
|
|
752
|
-
|
|
753
|
-
|
|
699
|
+
this.trigger('data', event);
|
|
754
700
|
return;
|
|
755
701
|
}
|
|
756
702
|
|
|
@@ -771,8 +717,7 @@
|
|
|
771
717
|
event.attributes['RECENTLY-REMOVED-DATERANGES'] = event.attributes['RECENTLY-REMOVED-DATERANGES'].split(TAB);
|
|
772
718
|
}
|
|
773
719
|
|
|
774
|
-
|
|
775
|
-
|
|
720
|
+
this.trigger('data', event);
|
|
776
721
|
return;
|
|
777
722
|
}
|
|
778
723
|
|
|
@@ -799,8 +744,7 @@
|
|
|
799
744
|
event.attributes.byterange = parseByterange(event.attributes.BYTERANGE);
|
|
800
745
|
}
|
|
801
746
|
|
|
802
|
-
|
|
803
|
-
|
|
747
|
+
this.trigger('data', event);
|
|
804
748
|
return;
|
|
805
749
|
}
|
|
806
750
|
|
|
@@ -822,9 +766,7 @@
|
|
|
822
766
|
event.attributes[key] = /YES/.test(event.attributes[key]);
|
|
823
767
|
}
|
|
824
768
|
});
|
|
825
|
-
|
|
826
|
-
_this2.trigger('data', event);
|
|
827
|
-
|
|
769
|
+
this.trigger('data', event);
|
|
828
770
|
return;
|
|
829
771
|
}
|
|
830
772
|
|
|
@@ -841,9 +783,7 @@
|
|
|
841
783
|
event.attributes[key] = parseFloat(event.attributes[key]);
|
|
842
784
|
}
|
|
843
785
|
});
|
|
844
|
-
|
|
845
|
-
_this2.trigger('data', event);
|
|
846
|
-
|
|
786
|
+
this.trigger('data', event);
|
|
847
787
|
return;
|
|
848
788
|
}
|
|
849
789
|
|
|
@@ -858,16 +798,14 @@
|
|
|
858
798
|
['BYTERANGE-START', 'BYTERANGE-LENGTH'].forEach(function (key) {
|
|
859
799
|
if (event.attributes.hasOwnProperty(key)) {
|
|
860
800
|
event.attributes[key] = parseInt(event.attributes[key], 10);
|
|
861
|
-
|
|
801
|
+
const subkey = key === 'BYTERANGE-LENGTH' ? 'length' : 'offset';
|
|
862
802
|
event.attributes.byterange = event.attributes.byterange || {};
|
|
863
803
|
event.attributes.byterange[subkey] = event.attributes[key]; // only keep the parsed byterange object.
|
|
864
804
|
|
|
865
805
|
delete event.attributes[key];
|
|
866
806
|
}
|
|
867
807
|
});
|
|
868
|
-
|
|
869
|
-
_this2.trigger('data', event);
|
|
870
|
-
|
|
808
|
+
this.trigger('data', event);
|
|
871
809
|
return;
|
|
872
810
|
}
|
|
873
811
|
|
|
@@ -884,14 +822,83 @@
|
|
|
884
822
|
event.attributes[key] = parseInt(event.attributes[key], 10);
|
|
885
823
|
}
|
|
886
824
|
});
|
|
825
|
+
this.trigger('data', event);
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
887
828
|
|
|
888
|
-
|
|
829
|
+
match = /^#EXT-X-DATERANGE:(.*)$/.exec(newLine);
|
|
889
830
|
|
|
831
|
+
if (match && match[1]) {
|
|
832
|
+
event = {
|
|
833
|
+
type: 'tag',
|
|
834
|
+
tagType: 'daterange'
|
|
835
|
+
};
|
|
836
|
+
event.attributes = parseAttributes(match[1]);
|
|
837
|
+
['ID', 'CLASS'].forEach(function (key) {
|
|
838
|
+
if (event.attributes.hasOwnProperty(key)) {
|
|
839
|
+
event.attributes[key] = String(event.attributes[key]);
|
|
840
|
+
}
|
|
841
|
+
});
|
|
842
|
+
['START-DATE', 'END-DATE'].forEach(function (key) {
|
|
843
|
+
if (event.attributes.hasOwnProperty(key)) {
|
|
844
|
+
event.attributes[key] = new Date(event.attributes[key]);
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
['DURATION', 'PLANNED-DURATION'].forEach(function (key) {
|
|
848
|
+
if (event.attributes.hasOwnProperty(key)) {
|
|
849
|
+
event.attributes[key] = parseFloat(event.attributes[key]);
|
|
850
|
+
}
|
|
851
|
+
});
|
|
852
|
+
['END-ON-NEXT'].forEach(function (key) {
|
|
853
|
+
if (event.attributes.hasOwnProperty(key)) {
|
|
854
|
+
event.attributes[key] = /YES/i.test(event.attributes[key]);
|
|
855
|
+
}
|
|
856
|
+
});
|
|
857
|
+
['SCTE35-CMD', ' SCTE35-OUT', 'SCTE35-IN'].forEach(function (key) {
|
|
858
|
+
if (event.attributes.hasOwnProperty(key)) {
|
|
859
|
+
event.attributes[key] = event.attributes[key].toString(16);
|
|
860
|
+
}
|
|
861
|
+
});
|
|
862
|
+
const clientAttributePattern = /^X-([A-Z]+-)+[A-Z]+$/;
|
|
863
|
+
|
|
864
|
+
for (const key in event.attributes) {
|
|
865
|
+
if (!clientAttributePattern.test(key)) {
|
|
866
|
+
continue;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
const isHexaDecimal = /[0-9A-Fa-f]{6}/g.test(event.attributes[key]);
|
|
870
|
+
const isDecimalFloating = /^\d+(\.\d+)?$/.test(event.attributes[key]);
|
|
871
|
+
event.attributes[key] = isHexaDecimal ? event.attributes[key].toString(16) : isDecimalFloating ? parseFloat(event.attributes[key]) : String(event.attributes[key]);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
this.trigger('data', event);
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
match = /^#EXT-X-INDEPENDENT-SEGMENTS/.exec(newLine);
|
|
879
|
+
|
|
880
|
+
if (match) {
|
|
881
|
+
this.trigger('data', {
|
|
882
|
+
type: 'tag',
|
|
883
|
+
tagType: 'independent-segments'
|
|
884
|
+
});
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
match = /^#EXT-X-CONTENT-STEERING:(.*)$/.exec(newLine);
|
|
889
|
+
|
|
890
|
+
if (match) {
|
|
891
|
+
event = {
|
|
892
|
+
type: 'tag',
|
|
893
|
+
tagType: 'content-steering'
|
|
894
|
+
};
|
|
895
|
+
event.attributes = parseAttributes(match[1]);
|
|
896
|
+
this.trigger('data', event);
|
|
890
897
|
return;
|
|
891
898
|
} // unknown tag type
|
|
892
899
|
|
|
893
900
|
|
|
894
|
-
|
|
901
|
+
this.trigger('data', {
|
|
895
902
|
type: 'tag',
|
|
896
903
|
data: newLine.slice(4)
|
|
897
904
|
});
|
|
@@ -906,33 +913,28 @@
|
|
|
906
913
|
* @param {Function} [options.dataParser] function to parse the line into an object
|
|
907
914
|
* @param {boolean} [options.segment] should tag data be attached to the segment object
|
|
908
915
|
*/
|
|
909
|
-
;
|
|
910
916
|
|
|
911
|
-
_proto.addParser = function addParser(_ref) {
|
|
912
|
-
var _this3 = this;
|
|
913
|
-
|
|
914
|
-
var expression = _ref.expression,
|
|
915
|
-
customType = _ref.customType,
|
|
916
|
-
dataParser = _ref.dataParser,
|
|
917
|
-
segment = _ref.segment;
|
|
918
917
|
|
|
918
|
+
addParser({
|
|
919
|
+
expression,
|
|
920
|
+
customType,
|
|
921
|
+
dataParser,
|
|
922
|
+
segment
|
|
923
|
+
}) {
|
|
919
924
|
if (typeof dataParser !== 'function') {
|
|
920
|
-
dataParser =
|
|
921
|
-
return line;
|
|
922
|
-
};
|
|
925
|
+
dataParser = line => line;
|
|
923
926
|
}
|
|
924
927
|
|
|
925
|
-
this.customParsers.push(
|
|
926
|
-
|
|
928
|
+
this.customParsers.push(line => {
|
|
929
|
+
const match = expression.exec(line);
|
|
927
930
|
|
|
928
931
|
if (match) {
|
|
929
|
-
|
|
932
|
+
this.trigger('data', {
|
|
930
933
|
type: 'custom',
|
|
931
934
|
data: dataParser(line),
|
|
932
|
-
customType
|
|
933
|
-
segment
|
|
935
|
+
customType,
|
|
936
|
+
segment
|
|
934
937
|
});
|
|
935
|
-
|
|
936
938
|
return true;
|
|
937
939
|
}
|
|
938
940
|
});
|
|
@@ -944,13 +946,13 @@
|
|
|
944
946
|
* @param {RegExp} options.expression a regular expression to match the custom header
|
|
945
947
|
* @param {Function} options.map function to translate tag into a different tag
|
|
946
948
|
*/
|
|
947
|
-
;
|
|
948
949
|
|
|
949
|
-
_proto.addTagMapper = function addTagMapper(_ref2) {
|
|
950
|
-
var expression = _ref2.expression,
|
|
951
|
-
map = _ref2.map;
|
|
952
950
|
|
|
953
|
-
|
|
951
|
+
addTagMapper({
|
|
952
|
+
expression,
|
|
953
|
+
map
|
|
954
|
+
}) {
|
|
955
|
+
const mapFn = line => {
|
|
954
956
|
if (expression.test(line)) {
|
|
955
957
|
return map(line);
|
|
956
958
|
}
|
|
@@ -959,23 +961,12 @@
|
|
|
959
961
|
};
|
|
960
962
|
|
|
961
963
|
this.tagMappers.push(mapFn);
|
|
962
|
-
};
|
|
963
|
-
|
|
964
|
-
return ParseStream;
|
|
965
|
-
}(Stream);
|
|
966
|
-
|
|
967
|
-
function _assertThisInitialized(self) {
|
|
968
|
-
if (self === void 0) {
|
|
969
|
-
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
|
|
970
964
|
}
|
|
971
965
|
|
|
972
|
-
return self;
|
|
973
966
|
}
|
|
974
967
|
|
|
975
|
-
var assertThisInitialized = _assertThisInitialized;
|
|
976
|
-
|
|
977
968
|
var atob = function atob(s) {
|
|
978
|
-
return
|
|
969
|
+
return window.atob ? window.atob(s) : Buffer.from(s, 'base64').toString('binary');
|
|
979
970
|
};
|
|
980
971
|
|
|
981
972
|
function decodeB64ToUint8Array(b64Text) {
|
|
@@ -989,14 +980,10 @@
|
|
|
989
980
|
return array;
|
|
990
981
|
}
|
|
991
982
|
|
|
992
|
-
|
|
993
|
-
return str.toLowerCase().replace(/-(\w)/g, function (a) {
|
|
994
|
-
return a[1].toUpperCase();
|
|
995
|
-
});
|
|
996
|
-
};
|
|
983
|
+
const camelCase = str => str.toLowerCase().replace(/-(\w)/g, a => a[1].toUpperCase());
|
|
997
984
|
|
|
998
|
-
|
|
999
|
-
|
|
985
|
+
const camelCaseKeys = function (attributes) {
|
|
986
|
+
const result = {};
|
|
1000
987
|
Object.keys(attributes).forEach(function (key) {
|
|
1001
988
|
result[camelCase(key)] = attributes[key];
|
|
1002
989
|
});
|
|
@@ -1007,31 +994,33 @@
|
|
|
1007
994
|
// target durations are set.
|
|
1008
995
|
|
|
1009
996
|
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
997
|
+
const setHoldBack = function (manifest) {
|
|
998
|
+
const {
|
|
999
|
+
serverControl,
|
|
1000
|
+
targetDuration,
|
|
1001
|
+
partTargetDuration
|
|
1002
|
+
} = manifest;
|
|
1014
1003
|
|
|
1015
1004
|
if (!serverControl) {
|
|
1016
1005
|
return;
|
|
1017
1006
|
}
|
|
1018
1007
|
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1008
|
+
const tag = '#EXT-X-SERVER-CONTROL';
|
|
1009
|
+
const hb = 'holdBack';
|
|
1010
|
+
const phb = 'partHoldBack';
|
|
1011
|
+
const minTargetDuration = targetDuration && targetDuration * 3;
|
|
1012
|
+
const minPartDuration = partTargetDuration && partTargetDuration * 2;
|
|
1024
1013
|
|
|
1025
1014
|
if (targetDuration && !serverControl.hasOwnProperty(hb)) {
|
|
1026
1015
|
serverControl[hb] = minTargetDuration;
|
|
1027
1016
|
this.trigger('info', {
|
|
1028
|
-
message: tag
|
|
1017
|
+
message: `${tag} defaulting HOLD-BACK to targetDuration * 3 (${minTargetDuration}).`
|
|
1029
1018
|
});
|
|
1030
1019
|
}
|
|
1031
1020
|
|
|
1032
1021
|
if (minTargetDuration && serverControl[hb] < minTargetDuration) {
|
|
1033
1022
|
this.trigger('warn', {
|
|
1034
|
-
message: tag
|
|
1023
|
+
message: `${tag} clamping HOLD-BACK (${serverControl[hb]}) to targetDuration * 3 (${minTargetDuration})`
|
|
1035
1024
|
});
|
|
1036
1025
|
serverControl[hb] = minTargetDuration;
|
|
1037
1026
|
} // default no part hold back to part target duration * 3
|
|
@@ -1040,14 +1029,14 @@
|
|
|
1040
1029
|
if (partTargetDuration && !serverControl.hasOwnProperty(phb)) {
|
|
1041
1030
|
serverControl[phb] = partTargetDuration * 3;
|
|
1042
1031
|
this.trigger('info', {
|
|
1043
|
-
message: tag
|
|
1032
|
+
message: `${tag} defaulting PART-HOLD-BACK to partTargetDuration * 3 (${serverControl[phb]}).`
|
|
1044
1033
|
});
|
|
1045
1034
|
} // if part hold back is too small default it to part target duration * 2
|
|
1046
1035
|
|
|
1047
1036
|
|
|
1048
1037
|
if (partTargetDuration && serverControl[phb] < minPartDuration) {
|
|
1049
1038
|
this.trigger('warn', {
|
|
1050
|
-
message: tag
|
|
1039
|
+
message: `${tag} clamping PART-HOLD-BACK (${serverControl[phb]}) to partTargetDuration * 2 (${minPartDuration}).`
|
|
1051
1040
|
});
|
|
1052
1041
|
serverControl[phb] = minPartDuration;
|
|
1053
1042
|
}
|
|
@@ -1075,36 +1064,29 @@
|
|
|
1075
1064
|
*/
|
|
1076
1065
|
|
|
1077
1066
|
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
_this.lineStream = new LineStream();
|
|
1086
|
-
_this.parseStream = new ParseStream();
|
|
1087
|
-
|
|
1088
|
-
_this.lineStream.pipe(_this.parseStream);
|
|
1067
|
+
class Parser extends Stream {
|
|
1068
|
+
constructor() {
|
|
1069
|
+
super();
|
|
1070
|
+
this.lineStream = new LineStream();
|
|
1071
|
+
this.parseStream = new ParseStream();
|
|
1072
|
+
this.lineStream.pipe(this.parseStream);
|
|
1073
|
+
this.lastProgramDateTime = null;
|
|
1089
1074
|
/* eslint-disable consistent-this */
|
|
1090
1075
|
|
|
1091
|
-
|
|
1092
|
-
var self = assertThisInitialized(_this);
|
|
1076
|
+
const self = this;
|
|
1093
1077
|
/* eslint-enable consistent-this */
|
|
1094
1078
|
|
|
1079
|
+
const uris = [];
|
|
1080
|
+
let currentUri = {}; // if specified, the active EXT-X-MAP definition
|
|
1095
1081
|
|
|
1096
|
-
|
|
1097
|
-
var currentUri = {}; // if specified, the active EXT-X-MAP definition
|
|
1098
|
-
|
|
1099
|
-
var currentMap; // if specified, the active decryption key
|
|
1082
|
+
let currentMap; // if specified, the active decryption key
|
|
1100
1083
|
|
|
1101
|
-
|
|
1084
|
+
let key;
|
|
1085
|
+
let hasParts = false;
|
|
1102
1086
|
|
|
1103
|
-
|
|
1087
|
+
const noop = function () {};
|
|
1104
1088
|
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
var defaultMediaGroups = {
|
|
1089
|
+
const defaultMediaGroups = {
|
|
1108
1090
|
'AUDIO': {},
|
|
1109
1091
|
'VIDEO': {},
|
|
1110
1092
|
'CLOSED-CAPTIONS': {},
|
|
@@ -1112,25 +1094,26 @@
|
|
|
1112
1094
|
}; // This is the Widevine UUID from DASH IF IOP. The same exact string is
|
|
1113
1095
|
// used in MPDs with Widevine encrypted streams.
|
|
1114
1096
|
|
|
1115
|
-
|
|
1097
|
+
const widevineUuid = 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'; // group segments into numbered timelines delineated by discontinuities
|
|
1116
1098
|
|
|
1117
|
-
|
|
1099
|
+
let currentTimeline = 0; // the manifest is empty until the parse stream begins delivering data
|
|
1118
1100
|
|
|
1119
|
-
|
|
1101
|
+
this.manifest = {
|
|
1120
1102
|
allowCache: true,
|
|
1121
1103
|
discontinuityStarts: [],
|
|
1104
|
+
dateRanges: [],
|
|
1122
1105
|
segments: []
|
|
1123
1106
|
}; // keep track of the last seen segment's byte range end, as segments are not required
|
|
1124
1107
|
// to provide the offset, in which case it defaults to the next byte after the
|
|
1125
1108
|
// previous segment
|
|
1126
1109
|
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
var lastPartByterangeEnd = 0; // track where next segment starts
|
|
1110
|
+
let lastByterangeEnd = 0; // keep track of the last seen part's byte range end.
|
|
1130
1111
|
|
|
1131
|
-
|
|
1112
|
+
let lastPartByterangeEnd = 0;
|
|
1113
|
+
const dateRangeTags = {}; // track where next segment starts
|
|
1132
1114
|
|
|
1133
|
-
|
|
1115
|
+
let nextSegmentLineNumberStart = 0;
|
|
1116
|
+
this.on('end', () => {
|
|
1134
1117
|
// only add preloadSegment if we don't yet have a uri for it.
|
|
1135
1118
|
// and we actually have parts/preloadHints
|
|
1136
1119
|
if (currentUri.uri || !currentUri.parts && !currentUri.preloadHints) {
|
|
@@ -1141,36 +1124,36 @@
|
|
|
1141
1124
|
currentUri.map = currentMap;
|
|
1142
1125
|
}
|
|
1143
1126
|
|
|
1144
|
-
if (!currentUri.key &&
|
|
1145
|
-
currentUri.key =
|
|
1127
|
+
if (!currentUri.key && key) {
|
|
1128
|
+
currentUri.key = key;
|
|
1146
1129
|
}
|
|
1147
1130
|
|
|
1148
1131
|
if (!currentUri.timeline && typeof currentTimeline === 'number') {
|
|
1149
1132
|
currentUri.timeline = currentTimeline;
|
|
1150
1133
|
}
|
|
1151
1134
|
|
|
1152
|
-
|
|
1135
|
+
this.manifest.preloadSegment = currentUri;
|
|
1153
1136
|
}); // update the manifest with the m3u8 entry from the parse stream
|
|
1154
1137
|
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
var rendition; // starting a new segment
|
|
1138
|
+
this.parseStream.on('data', function (entry) {
|
|
1139
|
+
let mediaGroup;
|
|
1140
|
+
let rendition; //starting a new segment
|
|
1159
1141
|
|
|
1160
1142
|
if (!Object.keys(currentUri).length) {
|
|
1161
|
-
nextSegmentLineNumberStart = this.lineNumber;
|
|
1143
|
+
nextSegmentLineNumberStart = this.lineStream.lineNumber;
|
|
1162
1144
|
}
|
|
1163
1145
|
|
|
1164
1146
|
({
|
|
1165
|
-
tag
|
|
1147
|
+
tag() {
|
|
1166
1148
|
// switch based on the tag type
|
|
1167
1149
|
(({
|
|
1168
|
-
version
|
|
1150
|
+
version() {
|
|
1169
1151
|
if (entry.version) {
|
|
1170
1152
|
this.manifest.version = entry.version;
|
|
1171
1153
|
}
|
|
1172
1154
|
},
|
|
1173
|
-
|
|
1155
|
+
|
|
1156
|
+
'allow-cache'() {
|
|
1174
1157
|
this.manifest.allowCache = entry.allowed;
|
|
1175
1158
|
|
|
1176
1159
|
if (!('allowed' in entry)) {
|
|
@@ -1180,8 +1163,9 @@
|
|
|
1180
1163
|
this.manifest.allowCache = true;
|
|
1181
1164
|
}
|
|
1182
1165
|
},
|
|
1183
|
-
|
|
1184
|
-
|
|
1166
|
+
|
|
1167
|
+
byterange() {
|
|
1168
|
+
const byterange = {};
|
|
1185
1169
|
|
|
1186
1170
|
if ('length' in entry) {
|
|
1187
1171
|
currentUri.byterange = byterange;
|
|
@@ -1209,10 +1193,12 @@
|
|
|
1209
1193
|
|
|
1210
1194
|
lastByterangeEnd = byterange.offset + byterange.length;
|
|
1211
1195
|
},
|
|
1212
|
-
|
|
1196
|
+
|
|
1197
|
+
endlist() {
|
|
1213
1198
|
this.manifest.endList = true;
|
|
1214
1199
|
},
|
|
1215
|
-
|
|
1200
|
+
|
|
1201
|
+
inf() {
|
|
1216
1202
|
if (!('mediaSequence' in this.manifest)) {
|
|
1217
1203
|
this.manifest.mediaSequence = 0;
|
|
1218
1204
|
this.trigger('info', {
|
|
@@ -1227,6 +1213,10 @@
|
|
|
1227
1213
|
});
|
|
1228
1214
|
}
|
|
1229
1215
|
|
|
1216
|
+
if (entry.title) {
|
|
1217
|
+
currentUri.title = entry.title;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1230
1220
|
if (entry.duration > 0) {
|
|
1231
1221
|
currentUri.duration = entry.duration;
|
|
1232
1222
|
}
|
|
@@ -1240,7 +1230,8 @@
|
|
|
1240
1230
|
|
|
1241
1231
|
this.manifest.segments = uris;
|
|
1242
1232
|
},
|
|
1243
|
-
|
|
1233
|
+
|
|
1234
|
+
key() {
|
|
1244
1235
|
if (!entry.attributes) {
|
|
1245
1236
|
this.trigger('warn', {
|
|
1246
1237
|
message: 'ignoring key declaration without attribute list'
|
|
@@ -1250,7 +1241,7 @@
|
|
|
1250
1241
|
|
|
1251
1242
|
|
|
1252
1243
|
if (entry.attributes.METHOD === 'NONE') {
|
|
1253
|
-
|
|
1244
|
+
key = null;
|
|
1254
1245
|
return;
|
|
1255
1246
|
}
|
|
1256
1247
|
|
|
@@ -1282,7 +1273,7 @@
|
|
|
1282
1273
|
|
|
1283
1274
|
|
|
1284
1275
|
if (entry.attributes.KEYFORMAT === widevineUuid) {
|
|
1285
|
-
|
|
1276
|
+
const VALID_METHODS = ['SAMPLE-AES', 'SAMPLE-AES-CTR', 'SAMPLE-AES-CENC'];
|
|
1286
1277
|
|
|
1287
1278
|
if (VALID_METHODS.indexOf(entry.attributes.METHOD) === -1) {
|
|
1288
1279
|
this.trigger('warn', {
|
|
@@ -1333,16 +1324,17 @@
|
|
|
1333
1324
|
} // setup an encryption key for upcoming segments
|
|
1334
1325
|
|
|
1335
1326
|
|
|
1336
|
-
|
|
1327
|
+
key = {
|
|
1337
1328
|
method: entry.attributes.METHOD || 'AES-128',
|
|
1338
1329
|
uri: entry.attributes.URI
|
|
1339
1330
|
};
|
|
1340
1331
|
|
|
1341
1332
|
if (typeof entry.attributes.IV !== 'undefined') {
|
|
1342
|
-
|
|
1333
|
+
key.iv = entry.attributes.IV;
|
|
1343
1334
|
}
|
|
1344
1335
|
},
|
|
1345
|
-
|
|
1336
|
+
|
|
1337
|
+
'media-sequence'() {
|
|
1346
1338
|
if (!isFinite(entry.number)) {
|
|
1347
1339
|
this.trigger('warn', {
|
|
1348
1340
|
message: 'ignoring invalid media sequence: ' + entry.number
|
|
@@ -1352,7 +1344,8 @@
|
|
|
1352
1344
|
|
|
1353
1345
|
this.manifest.mediaSequence = entry.number;
|
|
1354
1346
|
},
|
|
1355
|
-
|
|
1347
|
+
|
|
1348
|
+
'discontinuity-sequence'() {
|
|
1356
1349
|
if (!isFinite(entry.number)) {
|
|
1357
1350
|
this.trigger('warn', {
|
|
1358
1351
|
message: 'ignoring invalid discontinuity sequence: ' + entry.number
|
|
@@ -1363,7 +1356,8 @@
|
|
|
1363
1356
|
this.manifest.discontinuitySequence = entry.number;
|
|
1364
1357
|
currentTimeline = entry.number;
|
|
1365
1358
|
},
|
|
1366
|
-
|
|
1359
|
+
|
|
1360
|
+
'playlist-type'() {
|
|
1367
1361
|
if (!/VOD|EVENT/.test(entry.playlistType)) {
|
|
1368
1362
|
this.trigger('warn', {
|
|
1369
1363
|
message: 'ignoring unknown playlist type: ' + entry.playlist
|
|
@@ -1373,7 +1367,8 @@
|
|
|
1373
1367
|
|
|
1374
1368
|
this.manifest.playlistType = entry.playlistType;
|
|
1375
1369
|
},
|
|
1376
|
-
|
|
1370
|
+
|
|
1371
|
+
map() {
|
|
1377
1372
|
currentMap = {};
|
|
1378
1373
|
|
|
1379
1374
|
if (entry.uri) {
|
|
@@ -1384,11 +1379,12 @@
|
|
|
1384
1379
|
currentMap.byterange = entry.byterange;
|
|
1385
1380
|
}
|
|
1386
1381
|
|
|
1387
|
-
if (
|
|
1388
|
-
currentMap.key =
|
|
1382
|
+
if (key) {
|
|
1383
|
+
currentMap.key = key;
|
|
1389
1384
|
}
|
|
1390
1385
|
},
|
|
1391
|
-
|
|
1386
|
+
|
|
1387
|
+
'stream-inf'() {
|
|
1392
1388
|
this.manifest.playlists = uris;
|
|
1393
1389
|
this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
|
|
1394
1390
|
|
|
@@ -1403,9 +1399,10 @@
|
|
|
1403
1399
|
currentUri.attributes = {};
|
|
1404
1400
|
}
|
|
1405
1401
|
|
|
1406
|
-
|
|
1402
|
+
_extends$1(currentUri.attributes, entry.attributes);
|
|
1407
1403
|
},
|
|
1408
|
-
|
|
1404
|
+
|
|
1405
|
+
media() {
|
|
1409
1406
|
this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
|
|
1410
1407
|
|
|
1411
1408
|
if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) {
|
|
@@ -1416,7 +1413,7 @@
|
|
|
1416
1413
|
} // find the media group, creating defaults as necessary
|
|
1417
1414
|
|
|
1418
1415
|
|
|
1419
|
-
|
|
1416
|
+
const mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE];
|
|
1420
1417
|
mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {};
|
|
1421
1418
|
mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']]; // collect the rendition metadata
|
|
1422
1419
|
|
|
@@ -1453,12 +1450,14 @@
|
|
|
1453
1450
|
|
|
1454
1451
|
mediaGroup[entry.attributes.NAME] = rendition;
|
|
1455
1452
|
},
|
|
1456
|
-
|
|
1453
|
+
|
|
1454
|
+
discontinuity() {
|
|
1457
1455
|
currentTimeline += 1;
|
|
1458
1456
|
currentUri.discontinuity = true;
|
|
1459
1457
|
this.manifest.discontinuityStarts.push(uris.length);
|
|
1460
1458
|
},
|
|
1461
|
-
|
|
1459
|
+
|
|
1460
|
+
'program-date-time'() {
|
|
1462
1461
|
if (typeof this.manifest.dateTimeString === 'undefined') {
|
|
1463
1462
|
// PROGRAM-DATE-TIME is a media-segment tag, but for backwards
|
|
1464
1463
|
// compatibility, we add the first occurence of the PROGRAM-DATE-TIME tag
|
|
@@ -1470,8 +1469,24 @@
|
|
|
1470
1469
|
|
|
1471
1470
|
currentUri.dateTimeString = entry.dateTimeString;
|
|
1472
1471
|
currentUri.dateTimeObject = entry.dateTimeObject;
|
|
1472
|
+
const {
|
|
1473
|
+
lastProgramDateTime
|
|
1474
|
+
} = this;
|
|
1475
|
+
this.lastProgramDateTime = new Date(entry.dateTimeString).getTime(); // We should extrapolate Program Date Time backward only during first program date time occurrence.
|
|
1476
|
+
// Once we have at least one program date time point, we can always extrapolate it forward using lastProgramDateTime reference.
|
|
1477
|
+
|
|
1478
|
+
if (lastProgramDateTime === null) {
|
|
1479
|
+
// Extrapolate Program Date Time backward
|
|
1480
|
+
// Since it is first program date time occurrence we're assuming that
|
|
1481
|
+
// all this.manifest.segments have no program date time info
|
|
1482
|
+
this.manifest.segments.reduceRight((programDateTime, segment) => {
|
|
1483
|
+
segment.programDateTime = programDateTime - segment.duration * 1000;
|
|
1484
|
+
return segment.programDateTime;
|
|
1485
|
+
}, this.lastProgramDateTime);
|
|
1486
|
+
}
|
|
1473
1487
|
},
|
|
1474
|
-
|
|
1488
|
+
|
|
1489
|
+
targetduration() {
|
|
1475
1490
|
if (!isFinite(entry.duration) || entry.duration < 0) {
|
|
1476
1491
|
this.trigger('warn', {
|
|
1477
1492
|
message: 'ignoring invalid target duration: ' + entry.duration
|
|
@@ -1482,7 +1497,8 @@
|
|
|
1482
1497
|
this.manifest.targetDuration = entry.duration;
|
|
1483
1498
|
setHoldBack.call(this, this.manifest);
|
|
1484
1499
|
},
|
|
1485
|
-
|
|
1500
|
+
|
|
1501
|
+
start() {
|
|
1486
1502
|
if (!entry.attributes || isNaN(entry.attributes['TIME-OFFSET'])) {
|
|
1487
1503
|
this.trigger('warn', {
|
|
1488
1504
|
message: 'ignoring start declaration without appropriate attribute list'
|
|
@@ -1495,26 +1511,29 @@
|
|
|
1495
1511
|
precise: entry.attributes.PRECISE
|
|
1496
1512
|
};
|
|
1497
1513
|
},
|
|
1498
|
-
|
|
1514
|
+
|
|
1515
|
+
'cue-out'() {
|
|
1499
1516
|
currentUri.cueOut = entry.data;
|
|
1500
1517
|
},
|
|
1501
|
-
|
|
1518
|
+
|
|
1519
|
+
'cue-out-cont'() {
|
|
1502
1520
|
currentUri.cueOutCont = entry.data;
|
|
1503
1521
|
},
|
|
1504
|
-
|
|
1522
|
+
|
|
1523
|
+
'cue-in'() {
|
|
1505
1524
|
currentUri.cueIn = entry.data;
|
|
1506
1525
|
},
|
|
1507
|
-
|
|
1526
|
+
|
|
1527
|
+
'skip'() {
|
|
1508
1528
|
this.manifest.skip = camelCaseKeys(entry.attributes);
|
|
1509
1529
|
this.warnOnMissingAttributes_('#EXT-X-SKIP', entry.attributes, ['SKIPPED-SEGMENTS']);
|
|
1510
1530
|
},
|
|
1511
|
-
'part': function part() {
|
|
1512
|
-
var _this2 = this;
|
|
1513
1531
|
|
|
1532
|
+
'part'() {
|
|
1514
1533
|
hasParts = true; // parts are always specifed before a segment
|
|
1515
1534
|
|
|
1516
|
-
|
|
1517
|
-
|
|
1535
|
+
const segmentIndex = this.manifest.segments.length;
|
|
1536
|
+
const part = camelCaseKeys(entry.attributes);
|
|
1518
1537
|
currentUri.parts = currentUri.parts || [];
|
|
1519
1538
|
currentUri.parts.push(part);
|
|
1520
1539
|
|
|
@@ -1526,21 +1545,22 @@
|
|
|
1526
1545
|
lastPartByterangeEnd = part.byterange.offset + part.byterange.length;
|
|
1527
1546
|
}
|
|
1528
1547
|
|
|
1529
|
-
|
|
1530
|
-
this.warnOnMissingAttributes_(
|
|
1548
|
+
const partIndex = currentUri.parts.length - 1;
|
|
1549
|
+
this.warnOnMissingAttributes_(`#EXT-X-PART #${partIndex} for segment #${segmentIndex}`, entry.attributes, ['URI', 'DURATION']);
|
|
1531
1550
|
|
|
1532
1551
|
if (this.manifest.renditionReports) {
|
|
1533
|
-
this.manifest.renditionReports.forEach(
|
|
1552
|
+
this.manifest.renditionReports.forEach((r, i) => {
|
|
1534
1553
|
if (!r.hasOwnProperty('lastPart')) {
|
|
1535
|
-
|
|
1536
|
-
message:
|
|
1554
|
+
this.trigger('warn', {
|
|
1555
|
+
message: `#EXT-X-RENDITION-REPORT #${i} lacks required attribute(s): LAST-PART`
|
|
1537
1556
|
});
|
|
1538
1557
|
}
|
|
1539
1558
|
});
|
|
1540
1559
|
}
|
|
1541
1560
|
},
|
|
1542
|
-
|
|
1543
|
-
|
|
1561
|
+
|
|
1562
|
+
'server-control'() {
|
|
1563
|
+
const attrs = this.manifest.serverControl = camelCaseKeys(entry.attributes);
|
|
1544
1564
|
|
|
1545
1565
|
if (!attrs.hasOwnProperty('canBlockReload')) {
|
|
1546
1566
|
attrs.canBlockReload = false;
|
|
@@ -1557,11 +1577,12 @@
|
|
|
1557
1577
|
});
|
|
1558
1578
|
}
|
|
1559
1579
|
},
|
|
1560
|
-
|
|
1580
|
+
|
|
1581
|
+
'preload-hint'() {
|
|
1561
1582
|
// parts are always specifed before a segment
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1583
|
+
const segmentIndex = this.manifest.segments.length;
|
|
1584
|
+
const hint = camelCaseKeys(entry.attributes);
|
|
1585
|
+
const isPart = hint.type && hint.type === 'PART';
|
|
1565
1586
|
currentUri.preloadHints = currentUri.preloadHints || [];
|
|
1566
1587
|
currentUri.preloadHints.push(hint);
|
|
1567
1588
|
|
|
@@ -1576,8 +1597,8 @@
|
|
|
1576
1597
|
}
|
|
1577
1598
|
}
|
|
1578
1599
|
|
|
1579
|
-
|
|
1580
|
-
this.warnOnMissingAttributes_(
|
|
1600
|
+
const index = currentUri.preloadHints.length - 1;
|
|
1601
|
+
this.warnOnMissingAttributes_(`#EXT-X-PRELOAD-HINT #${index} for segment #${segmentIndex}`, entry.attributes, ['TYPE', 'URI']);
|
|
1581
1602
|
|
|
1582
1603
|
if (!hint.type) {
|
|
1583
1604
|
return;
|
|
@@ -1585,8 +1606,8 @@
|
|
|
1585
1606
|
// a duplicate type.
|
|
1586
1607
|
|
|
1587
1608
|
|
|
1588
|
-
for (
|
|
1589
|
-
|
|
1609
|
+
for (let i = 0; i < currentUri.preloadHints.length - 1; i++) {
|
|
1610
|
+
const otherHint = currentUri.preloadHints[i];
|
|
1590
1611
|
|
|
1591
1612
|
if (!otherHint.type) {
|
|
1592
1613
|
continue;
|
|
@@ -1594,25 +1615,27 @@
|
|
|
1594
1615
|
|
|
1595
1616
|
if (otherHint.type === hint.type) {
|
|
1596
1617
|
this.trigger('warn', {
|
|
1597
|
-
message:
|
|
1618
|
+
message: `#EXT-X-PRELOAD-HINT #${index} for segment #${segmentIndex} has the same TYPE ${hint.type} as preload hint #${i}`
|
|
1598
1619
|
});
|
|
1599
1620
|
}
|
|
1600
1621
|
}
|
|
1601
1622
|
},
|
|
1602
|
-
|
|
1603
|
-
|
|
1623
|
+
|
|
1624
|
+
'rendition-report'() {
|
|
1625
|
+
const report = camelCaseKeys(entry.attributes);
|
|
1604
1626
|
this.manifest.renditionReports = this.manifest.renditionReports || [];
|
|
1605
1627
|
this.manifest.renditionReports.push(report);
|
|
1606
|
-
|
|
1607
|
-
|
|
1628
|
+
const index = this.manifest.renditionReports.length - 1;
|
|
1629
|
+
const required = ['LAST-MSN', 'URI'];
|
|
1608
1630
|
|
|
1609
1631
|
if (hasParts) {
|
|
1610
1632
|
required.push('LAST-PART');
|
|
1611
1633
|
}
|
|
1612
1634
|
|
|
1613
|
-
this.warnOnMissingAttributes_(
|
|
1635
|
+
this.warnOnMissingAttributes_(`#EXT-X-RENDITION-REPORT #${index}`, entry.attributes, required);
|
|
1614
1636
|
},
|
|
1615
|
-
|
|
1637
|
+
|
|
1638
|
+
'part-inf'() {
|
|
1616
1639
|
this.manifest.partInf = camelCaseKeys(entry.attributes);
|
|
1617
1640
|
this.warnOnMissingAttributes_('#EXT-X-PART-INF', entry.attributes, ['PART-TARGET']);
|
|
1618
1641
|
|
|
@@ -1621,13 +1644,89 @@
|
|
|
1621
1644
|
}
|
|
1622
1645
|
|
|
1623
1646
|
setHoldBack.call(this, this.manifest);
|
|
1647
|
+
},
|
|
1648
|
+
|
|
1649
|
+
'daterange'() {
|
|
1650
|
+
this.manifest.dateRanges.push(camelCaseKeys(entry.attributes));
|
|
1651
|
+
const index = this.manifest.dateRanges.length - 1;
|
|
1652
|
+
this.warnOnMissingAttributes_(`#EXT-X-DATERANGE #${index}`, entry.attributes, ['ID', 'START-DATE']);
|
|
1653
|
+
const dateRange = this.manifest.dateRanges[index];
|
|
1654
|
+
|
|
1655
|
+
if (dateRange.endDate && dateRange.startDate && new Date(dateRange.endDate) < new Date(dateRange.startDate)) {
|
|
1656
|
+
this.trigger('warn', {
|
|
1657
|
+
message: 'EXT-X-DATERANGE END-DATE must be equal to or later than the value of the START-DATE'
|
|
1658
|
+
});
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
if (dateRange.duration && dateRange.duration < 0) {
|
|
1662
|
+
this.trigger('warn', {
|
|
1663
|
+
message: 'EXT-X-DATERANGE DURATION must not be negative'
|
|
1664
|
+
});
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
if (dateRange.plannedDuration && dateRange.plannedDuration < 0) {
|
|
1668
|
+
this.trigger('warn', {
|
|
1669
|
+
message: 'EXT-X-DATERANGE PLANNED-DURATION must not be negative'
|
|
1670
|
+
});
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
const endOnNextYes = !!dateRange.endOnNext;
|
|
1674
|
+
|
|
1675
|
+
if (endOnNextYes && !dateRange.class) {
|
|
1676
|
+
this.trigger('warn', {
|
|
1677
|
+
message: 'EXT-X-DATERANGE with an END-ON-NEXT=YES attribute must have a CLASS attribute'
|
|
1678
|
+
});
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
if (endOnNextYes && (dateRange.duration || dateRange.endDate)) {
|
|
1682
|
+
this.trigger('warn', {
|
|
1683
|
+
message: 'EXT-X-DATERANGE with an END-ON-NEXT=YES attribute must not contain DURATION or END-DATE attributes'
|
|
1684
|
+
});
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
if (dateRange.duration && dateRange.endDate) {
|
|
1688
|
+
const startDate = dateRange.startDate;
|
|
1689
|
+
const newDateInSeconds = startDate.getTime() + dateRange.duration * 1000;
|
|
1690
|
+
this.manifest.dateRanges[index].endDate = new Date(newDateInSeconds);
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
if (!dateRangeTags[dateRange.id]) {
|
|
1694
|
+
dateRangeTags[dateRange.id] = dateRange;
|
|
1695
|
+
} else {
|
|
1696
|
+
for (const attribute in dateRangeTags[dateRange.id]) {
|
|
1697
|
+
if (!!dateRange[attribute] && JSON.stringify(dateRangeTags[dateRange.id][attribute]) !== JSON.stringify(dateRange[attribute])) {
|
|
1698
|
+
this.trigger('warn', {
|
|
1699
|
+
message: 'EXT-X-DATERANGE tags with the same ID in a playlist must have the same attributes values'
|
|
1700
|
+
});
|
|
1701
|
+
break;
|
|
1702
|
+
}
|
|
1703
|
+
} // if tags with the same ID do not have conflicting attributes, merge them
|
|
1704
|
+
|
|
1705
|
+
|
|
1706
|
+
const dateRangeWithSameId = this.manifest.dateRanges.findIndex(dateRangeToFind => dateRangeToFind.id === dateRange.id);
|
|
1707
|
+
this.manifest.dateRanges[dateRangeWithSameId] = _extends$1(this.manifest.dateRanges[dateRangeWithSameId], dateRange);
|
|
1708
|
+
dateRangeTags[dateRange.id] = _extends$1(dateRangeTags[dateRange.id], dateRange); // after merging, delete the duplicate dateRange that was added last
|
|
1709
|
+
|
|
1710
|
+
this.manifest.dateRanges.pop();
|
|
1711
|
+
}
|
|
1712
|
+
},
|
|
1713
|
+
|
|
1714
|
+
'independent-segments'() {
|
|
1715
|
+
this.manifest.independentSegments = true;
|
|
1716
|
+
},
|
|
1717
|
+
|
|
1718
|
+
'content-steering'() {
|
|
1719
|
+
this.manifest.contentSteering = camelCaseKeys(entry.attributes);
|
|
1720
|
+
this.warnOnMissingAttributes_('#EXT-X-CONTENT-STEERING', entry.attributes, ['SERVER-URI']);
|
|
1624
1721
|
}
|
|
1722
|
+
|
|
1625
1723
|
})[entry.tagType] || noop).call(self);
|
|
1626
1724
|
},
|
|
1627
|
-
|
|
1725
|
+
|
|
1726
|
+
uri() {
|
|
1628
1727
|
currentUri.uri = entry.uri;
|
|
1629
1728
|
currentUri.lineNumberStart = nextSegmentLineNumberStart;
|
|
1630
|
-
currentUri.lineNumberEnd = this.
|
|
1729
|
+
currentUri.lineNumberEnd = this.lineStream.lineNumber;
|
|
1631
1730
|
uris.push(currentUri); // if no explicit duration was declared, use the target duration
|
|
1632
1731
|
|
|
1633
1732
|
if (this.manifest.targetDuration && !('duration' in currentUri)) {
|
|
@@ -1638,8 +1737,8 @@
|
|
|
1638
1737
|
} // annotate with encryption information, if necessary
|
|
1639
1738
|
|
|
1640
1739
|
|
|
1641
|
-
if (
|
|
1642
|
-
currentUri.key =
|
|
1740
|
+
if (key) {
|
|
1741
|
+
currentUri.key = key;
|
|
1643
1742
|
}
|
|
1644
1743
|
|
|
1645
1744
|
currentUri.timeline = currentTimeline; // annotate with initialization segment information, if necessary
|
|
@@ -1649,13 +1748,21 @@
|
|
|
1649
1748
|
} // reset the last byterange end as it needs to be 0 between parts
|
|
1650
1749
|
|
|
1651
1750
|
|
|
1652
|
-
lastPartByterangeEnd = 0; //
|
|
1751
|
+
lastPartByterangeEnd = 0; // Once we have at least one program date time we can always extrapolate it forward
|
|
1752
|
+
|
|
1753
|
+
if (this.lastProgramDateTime !== null) {
|
|
1754
|
+
currentUri.programDateTime = this.lastProgramDateTime;
|
|
1755
|
+
this.lastProgramDateTime += currentUri.duration * 1000;
|
|
1756
|
+
} // prepare for the next URI
|
|
1757
|
+
|
|
1653
1758
|
|
|
1654
1759
|
currentUri = {};
|
|
1655
1760
|
},
|
|
1656
|
-
|
|
1761
|
+
|
|
1762
|
+
comment() {// comments are not important for playback
|
|
1657
1763
|
},
|
|
1658
|
-
|
|
1764
|
+
|
|
1765
|
+
custom() {
|
|
1659
1766
|
// if this is segment-level data attach the output to the segment
|
|
1660
1767
|
if (entry.segment) {
|
|
1661
1768
|
currentUri.custom = currentUri.custom || {};
|
|
@@ -1665,16 +1772,13 @@
|
|
|
1665
1772
|
this.manifest.custom[entry.customType] = entry.data;
|
|
1666
1773
|
}
|
|
1667
1774
|
}
|
|
1775
|
+
|
|
1668
1776
|
})[entry.type].call(self);
|
|
1669
1777
|
});
|
|
1670
|
-
|
|
1671
|
-
return _this;
|
|
1672
1778
|
}
|
|
1673
1779
|
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
_proto.warnOnMissingAttributes_ = function warnOnMissingAttributes_(identifier, attributes, required) {
|
|
1677
|
-
var missing = [];
|
|
1780
|
+
warnOnMissingAttributes_(identifier, attributes, required) {
|
|
1781
|
+
const missing = [];
|
|
1678
1782
|
required.forEach(function (key) {
|
|
1679
1783
|
if (!attributes.hasOwnProperty(key)) {
|
|
1680
1784
|
missing.push(key);
|
|
@@ -1683,7 +1787,7 @@
|
|
|
1683
1787
|
|
|
1684
1788
|
if (missing.length) {
|
|
1685
1789
|
this.trigger('warn', {
|
|
1686
|
-
message: identifier
|
|
1790
|
+
message: `${identifier} lacks required attribute(s): ${missing.join(', ')}`
|
|
1687
1791
|
});
|
|
1688
1792
|
}
|
|
1689
1793
|
}
|
|
@@ -1692,9 +1796,9 @@
|
|
|
1692
1796
|
*
|
|
1693
1797
|
* @param {string} chunk a potentially incomplete portion of the manifest
|
|
1694
1798
|
*/
|
|
1695
|
-
;
|
|
1696
1799
|
|
|
1697
|
-
|
|
1800
|
+
|
|
1801
|
+
push(chunk) {
|
|
1698
1802
|
this.lineStream.push(chunk);
|
|
1699
1803
|
}
|
|
1700
1804
|
/**
|
|
@@ -1702,11 +1806,19 @@
|
|
|
1702
1806
|
* manifest did not contain a trailing newline but the file has been
|
|
1703
1807
|
* completely received.
|
|
1704
1808
|
*/
|
|
1705
|
-
;
|
|
1706
1809
|
|
|
1707
|
-
|
|
1810
|
+
|
|
1811
|
+
end() {
|
|
1708
1812
|
// flush any buffered input
|
|
1709
1813
|
this.lineStream.push('\n');
|
|
1814
|
+
|
|
1815
|
+
if (this.manifest.dateRanges.length && this.lastProgramDateTime === null) {
|
|
1816
|
+
this.trigger('warn', {
|
|
1817
|
+
message: 'A playlist with EXT-X-DATERANGE tag must contain atleast one EXT-X-PROGRAM-DATE-TIME tag'
|
|
1818
|
+
});
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
this.lastProgramDateTime = null;
|
|
1710
1822
|
this.trigger('end');
|
|
1711
1823
|
}
|
|
1712
1824
|
/**
|
|
@@ -1714,13 +1826,13 @@
|
|
|
1714
1826
|
*
|
|
1715
1827
|
* @param {Object} options a map of options for the added parser
|
|
1716
1828
|
* @param {RegExp} options.expression a regular expression to match the custom header
|
|
1717
|
-
* @param {string} options.
|
|
1829
|
+
* @param {string} options.customType the custom type to register to the output
|
|
1718
1830
|
* @param {Function} [options.dataParser] function to parse the line into an object
|
|
1719
1831
|
* @param {boolean} [options.segment] should tag data be attached to the segment object
|
|
1720
1832
|
*/
|
|
1721
|
-
;
|
|
1722
1833
|
|
|
1723
|
-
|
|
1834
|
+
|
|
1835
|
+
addParser(options) {
|
|
1724
1836
|
this.parseStream.addParser(options);
|
|
1725
1837
|
}
|
|
1726
1838
|
/**
|
|
@@ -1730,14 +1842,13 @@
|
|
|
1730
1842
|
* @param {RegExp} options.expression a regular expression to match the custom header
|
|
1731
1843
|
* @param {Function} options.map function to translate tag into a different tag
|
|
1732
1844
|
*/
|
|
1733
|
-
;
|
|
1734
1845
|
|
|
1735
|
-
|
|
1846
|
+
|
|
1847
|
+
addTagMapper(options) {
|
|
1736
1848
|
this.parseStream.addTagMapper(options);
|
|
1737
|
-
}
|
|
1849
|
+
}
|
|
1738
1850
|
|
|
1739
|
-
|
|
1740
|
-
}(Stream);
|
|
1851
|
+
}
|
|
1741
1852
|
|
|
1742
1853
|
exports.LineStream = LineStream;
|
|
1743
1854
|
exports.ParseStream = ParseStream;
|
|
@@ -1745,4 +1856,4 @@
|
|
|
1745
1856
|
|
|
1746
1857
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
1747
1858
|
|
|
1748
|
-
}))
|
|
1859
|
+
}));
|