@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.
Files changed (69) hide show
  1. package/README.md +7 -2
  2. package/dist/m3u8-parser.cjs.js +449 -305
  3. package/dist/m3u8-parser.es.js +446 -300
  4. package/dist/m3u8-parser.js +465 -354
  5. package/dist/m3u8-parser.min.js +2 -2
  6. package/package.json +6 -11
  7. package/src/line-stream.js +2 -0
  8. package/src/parse-stream.js +93 -20
  9. package/src/parser.js +124 -12
  10. package/test/fixtures/integration/absoluteUris.js +1 -0
  11. package/test/fixtures/integration/allowCache.js +1 -0
  12. package/test/fixtures/integration/allowCacheInvalid.js +1 -0
  13. package/test/fixtures/integration/alternateAudio.js +1 -0
  14. package/test/fixtures/integration/alternateVideo.js +1 -0
  15. package/test/fixtures/integration/brightcove.js +1 -0
  16. package/test/fixtures/integration/byteRange.js +1 -0
  17. package/test/fixtures/integration/dateTime.js +3 -0
  18. package/test/fixtures/integration/diff-init-key.js +1 -0
  19. package/test/fixtures/integration/disallowCache.js +1 -0
  20. package/test/fixtures/integration/disc-sequence.js +9 -4
  21. package/test/fixtures/integration/discontinuity.js +19 -9
  22. package/test/fixtures/integration/domainUris.js +1 -0
  23. package/test/fixtures/integration/empty.js +1 -0
  24. package/test/fixtures/integration/emptyAllowCache.js +1 -0
  25. package/test/fixtures/integration/emptyMediaSequence.js +9 -4
  26. package/test/fixtures/integration/emptyPlaylistType.js +1 -0
  27. package/test/fixtures/integration/emptyTargetDuration.js +1 -0
  28. package/test/fixtures/integration/encrypted.js +1 -0
  29. package/test/fixtures/integration/event.js +1 -0
  30. package/test/fixtures/integration/extXPlaylistTypeInvalidPlaylist.js +3 -1
  31. package/test/fixtures/integration/extinf.js +3 -1
  32. package/test/fixtures/integration/fmp4.js +3 -1
  33. package/test/fixtures/integration/headerOnly.js +1 -0
  34. package/test/fixtures/integration/invalidAllowCache.js +1 -0
  35. package/test/fixtures/integration/invalidMediaSequence.js +9 -4
  36. package/test/fixtures/integration/invalidPlaylistType.js +1 -0
  37. package/test/fixtures/integration/invalidTargetDuration.js +1 -0
  38. package/test/fixtures/integration/liveMissingSegmentDuration.js +3 -1
  39. package/test/fixtures/integration/liveStart30sBefore.js +19 -9
  40. package/test/fixtures/integration/llhls-byte-range.js +1 -0
  41. package/test/fixtures/integration/llhls-delta-byte-range.js +1 -0
  42. package/test/fixtures/integration/llhls.js +8 -0
  43. package/test/fixtures/integration/llhlsDelta.js +5 -0
  44. package/test/fixtures/integration/manifestExtTTargetdurationNegative.js +1 -0
  45. package/test/fixtures/integration/manifestExtXEndlistEarly.js +1 -0
  46. package/test/fixtures/integration/manifestNoExtM3u.js +1 -0
  47. package/test/fixtures/integration/master-fmp4.js +27 -25
  48. package/test/fixtures/integration/master.js +1 -0
  49. package/test/fixtures/integration/media.js +1 -0
  50. package/test/fixtures/integration/mediaSequence.js +9 -4
  51. package/test/fixtures/integration/missingEndlist.js +1 -0
  52. package/test/fixtures/integration/missingExtinf.js +1 -0
  53. package/test/fixtures/integration/missingMediaSequence.js +9 -4
  54. package/test/fixtures/integration/missingSegmentDuration.js +3 -1
  55. package/test/fixtures/integration/multipleAudioGroups.js +1 -0
  56. package/test/fixtures/integration/multipleAudioGroupsCombinedMain.js +1 -0
  57. package/test/fixtures/integration/multipleTargetDurations.js +1 -0
  58. package/test/fixtures/integration/multipleVideo.js +1 -0
  59. package/test/fixtures/integration/negativeMediaSequence.js +9 -4
  60. package/test/fixtures/integration/playlist.js +1 -0
  61. package/test/fixtures/integration/playlistMediaSequenceHigher.js +3 -1
  62. package/test/fixtures/integration/start.js +1 -0
  63. package/test/fixtures/integration/streamInfInvalid.js +1 -0
  64. package/test/fixtures/integration/twoMediaSequences.js +9 -4
  65. package/test/fixtures/integration/versionInvalid.js +1 -0
  66. package/test/fixtures/integration/whiteSpace.js +1 -0
  67. package/test/fixtures/integration/zeroDuration.js +1 -0
  68. package/test/parse-stream.test.js +112 -16
  69. package/test/parser.test.js +345 -15
@@ -1,21 +1,9 @@
1
- /*! @name @transmitlive/m3u8-parser @version 4.7.2-beta.6 @license Apache-2.0 */
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, require('global/window')) :
4
- typeof define === 'function' && define.amd ? define(['exports', 'global/window'], factory) :
5
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.m3u8Parser = {}, global.window));
6
- }(this, (function (exports, window) { 'use strict';
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
- var LineStream = /*#__PURE__*/function (_Stream) {
149
- inheritsLoose(LineStream, _Stream);
150
-
151
- function LineStream() {
152
- var _this;
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
- var _proto = LineStream.prototype;
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
- var _extends_1 = createCommonjsModule(function (module) {
196
- function _extends() {
197
- module.exports = _extends = Object.assign || function (target) {
198
- for (var i = 1; i < arguments.length; i++) {
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
- for (var key in source) {
202
- if (Object.prototype.hasOwnProperty.call(source, key)) {
203
- target[key] = source[key];
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
- return target;
209
- };
178
+ return target;
179
+ };
210
180
 
211
- return _extends.apply(this, arguments);
212
- }
181
+ return _extends.apply(this, arguments);
182
+ }
213
183
 
214
- module.exports = _extends;
215
- });
184
+ var _extends_1 = _extends;
185
+ var _extends$1 = _extends_1;
216
186
 
217
- var TAB = String.fromCharCode(0x09);
187
+ const TAB = String.fromCharCode(0x09);
218
188
 
219
- var parseByterange = function parseByterange(byterangeString) {
189
+ const parseByterange = function (byterangeString) {
220
190
  // optionally match and capture 0+ digits before `@`
221
191
  // optionally match and capture 0+ digits after `@`
222
- var match = /([0-9.]*)?@?([0-9.]*)?/.exec(byterangeString || '');
223
- var result = {};
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
- var attributeSeparator = function attributeSeparator() {
245
- var key = '[^=]*';
246
- var value = '"[^"]*"|[^,]*';
247
- var keyvalue = '(?:' + key + ')=(?:' + value + ')';
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
- var parseAttributes = function parseAttributes(attributes) {
258
- // split the string using attributes as the separator
259
- var attrs = attributes.split(attributeSeparator());
260
- var result = {};
261
- var i = attrs.length;
262
- var attr;
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
- var ParseStream = /*#__PURE__*/function (_Stream) {
308
- inheritsLoose(ParseStream, _Stream);
309
-
310
- function ParseStream() {
311
- var _this;
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
- var _proto = ParseStream.prototype;
327
-
328
- _proto.push = function push(line) {
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
- var newLines = this.tagMappers.reduce(function (acc, mapper) {
353
- var mappedLine = mapper(line); // skip if unchanged
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(function (newLine) {
362
- for (var i = 0; i < _this2.customParsers.length; i++) {
363
- if (_this2.customParsers[i].call(_this2, newLine)) {
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
- _this2.trigger('data', {
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
- _this2.trigger('data', {
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:?([0-9\.]*)?,?(.*)?$/.exec(newLine);
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
- _this2.trigger('data', event);
410
-
371
+ this.trigger('data', event);
411
372
  return;
412
373
  }
413
374
 
414
- match = /^#EXT-X-TARGETDURATION:?([0-9.]*)?/.exec(newLine);
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
- _this2.trigger('data', event);
427
-
387
+ this.trigger('data', event);
428
388
  return;
429
389
  }
430
390
 
431
- match = /^#EXT-X-VERSION:?([0-9.]*)?/.exec(newLine);
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
- _this2.trigger('data', event);
444
-
403
+ this.trigger('data', event);
445
404
  return;
446
405
  }
447
406
 
448
- match = /^#EXT-X-MEDIA-SEQUENCE:?(\-?[0-9.]*)?/.exec(newLine);
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
- _this2.trigger('data', event);
461
-
419
+ this.trigger('data', event);
462
420
  return;
463
421
  }
464
422
 
465
- match = /^#EXT-X-DISCONTINUITY-SEQUENCE:?(\-?[0-9.]*)?/.exec(newLine);
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
- _this2.trigger('data', event);
478
-
435
+ this.trigger('data', event);
479
436
  return;
480
437
  }
481
438
 
482
- match = /^#EXT-X-PLAYLIST-TYPE:?(.*)?$/.exec(newLine);
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
- _this2.trigger('data', event);
495
-
451
+ this.trigger('data', event);
496
452
  return;
497
453
  }
498
454
 
499
- match = /^#EXT-X-BYTERANGE:?(.*)?$/.exec(newLine);
455
+ match = /^#EXT-X-BYTERANGE:(.*)?$/.exec(newLine);
500
456
 
501
457
  if (match) {
502
- event = _extends_1(parseByterange(match[1]), {
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:?(YES|NO)?/.exec(newLine);
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
- _this2.trigger('data', event);
525
-
478
+ this.trigger('data', event);
526
479
  return;
527
480
  }
528
481
 
529
- match = /^#EXT-X-MAP:?(.*)$/.exec(newLine);
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
- var attributes = parseAttributes(match[1]);
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
- _this2.trigger('data', event);
550
-
502
+ this.trigger('data', event);
551
503
  return;
552
504
  }
553
505
 
554
- match = /^#EXT-X-STREAM-INF:?(.*)$/.exec(newLine);
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
- var split = event.attributes.RESOLUTION.split('x');
567
- var resolution = {};
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
- _this2.trigger('data', event);
590
-
545
+ this.trigger('data', event);
591
546
  return;
592
547
  }
593
548
 
594
- match = /^#EXT-X-MEDIA:?(.*)$/.exec(newLine);
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
- _this2.trigger('data', event);
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
- _this2.trigger('data', {
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
- _this2.trigger('data', {
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:?(.*)$/.exec(newLine);
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
- _this2.trigger('data', event);
647
-
598
+ this.trigger('data', event);
648
599
  return;
649
600
  }
650
601
 
651
- match = /^#EXT-X-KEY:?(.*)$/.exec(newLine);
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
- _this2.trigger('data', event);
677
-
627
+ this.trigger('data', event);
678
628
  return;
679
629
  }
680
630
 
681
- match = /^#EXT-X-START:?(.*)$/.exec(newLine);
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
- _this2.trigger('data', event);
696
-
645
+ this.trigger('data', event);
697
646
  return;
698
647
  }
699
648
 
700
- match = /^#EXT-X-CUE-OUT-CONT:?(.*)?$/.exec(newLine);
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
- _this2.trigger('data', event);
715
-
663
+ this.trigger('data', event);
716
664
  return;
717
665
  }
718
666
 
719
- match = /^#EXT-X-CUE-OUT:?(.*)?$/.exec(newLine);
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
- _this2.trigger('data', event);
734
-
681
+ this.trigger('data', event);
735
682
  return;
736
683
  }
737
684
 
738
- match = /^#EXT-X-CUE-IN:?(.*)?$/.exec(newLine);
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
- _this2.trigger('data', event);
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
- _this2.trigger('data', event);
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
- _this2.trigger('data', event);
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
- var subkey = key === 'BYTERANGE-LENGTH' ? 'length' : 'offset';
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
- _this2.trigger('data', event);
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
- _this2.trigger('data', {
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 = function dataParser(line) {
921
- return line;
922
- };
925
+ dataParser = line => line;
923
926
  }
924
927
 
925
- this.customParsers.push(function (line) {
926
- var match = expression.exec(line);
928
+ this.customParsers.push(line => {
929
+ const match = expression.exec(line);
927
930
 
928
931
  if (match) {
929
- _this3.trigger('data', {
932
+ this.trigger('data', {
930
933
  type: 'custom',
931
934
  data: dataParser(line),
932
- customType: customType,
933
- segment: 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
- var mapFn = function mapFn(line) {
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 window__default['default'].atob ? window__default['default'].atob(s) : Buffer.from(s, 'base64').toString('binary');
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
- var camelCase = function camelCase(str) {
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
- var camelCaseKeys = function camelCaseKeys(attributes) {
999
- var result = {};
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
- var setHoldBack = function setHoldBack(manifest) {
1011
- var serverControl = manifest.serverControl,
1012
- targetDuration = manifest.targetDuration,
1013
- partTargetDuration = manifest.partTargetDuration;
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
- var tag = '#EXT-X-SERVER-CONTROL';
1020
- var hb = 'holdBack';
1021
- var phb = 'partHoldBack';
1022
- var minTargetDuration = targetDuration && targetDuration * 3;
1023
- var minPartDuration = partTargetDuration && partTargetDuration * 2;
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 + " defaulting HOLD-BACK to targetDuration * 3 (" + minTargetDuration + ")."
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 + " clamping HOLD-BACK (" + serverControl[hb] + ") to targetDuration * 3 (" + minTargetDuration + ")"
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 + " defaulting PART-HOLD-BACK to partTargetDuration * 3 (" + serverControl[phb] + ")."
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 + " clamping PART-HOLD-BACK (" + serverControl[phb] + ") to partTargetDuration * 2 (" + minPartDuration + ")."
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
- var Parser = /*#__PURE__*/function (_Stream) {
1079
- inheritsLoose(Parser, _Stream);
1080
-
1081
- function Parser() {
1082
- var _this;
1083
-
1084
- _this = _Stream.call(this) || this;
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
- var uris = [];
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
- var _key;
1084
+ let key;
1085
+ let hasParts = false;
1102
1086
 
1103
- var hasParts = false;
1087
+ const noop = function () {};
1104
1088
 
1105
- var noop = function noop() {};
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
- var widevineUuid = 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'; // group segments into numbered timelines delineated by discontinuities
1097
+ const widevineUuid = 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'; // group segments into numbered timelines delineated by discontinuities
1116
1098
 
1117
- var currentTimeline = 0; // the manifest is empty until the parse stream begins delivering data
1099
+ let currentTimeline = 0; // the manifest is empty until the parse stream begins delivering data
1118
1100
 
1119
- _this.manifest = {
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
- var lastByterangeEnd = 0; // keep track of the last seen part's byte range end.
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
- var nextSegmentLineNumberStart = 0;
1112
+ let lastPartByterangeEnd = 0;
1113
+ const dateRangeTags = {}; // track where next segment starts
1132
1114
 
1133
- _this.on('end', function () {
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 && _key) {
1145
- currentUri.key = _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
- _this.manifest.preloadSegment = currentUri;
1135
+ this.manifest.preloadSegment = currentUri;
1153
1136
  }); // update the manifest with the m3u8 entry from the parse stream
1154
1137
 
1155
-
1156
- _this.parseStream.on('data', function (entry) {
1157
- var mediaGroup;
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: function tag() {
1147
+ tag() {
1166
1148
  // switch based on the tag type
1167
1149
  (({
1168
- version: function version() {
1150
+ version() {
1169
1151
  if (entry.version) {
1170
1152
  this.manifest.version = entry.version;
1171
1153
  }
1172
1154
  },
1173
- 'allow-cache': function allowCache() {
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
- byterange: function byterange() {
1184
- var byterange = {};
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
- endlist: function endlist() {
1196
+
1197
+ endlist() {
1213
1198
  this.manifest.endList = true;
1214
1199
  },
1215
- inf: function inf() {
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
- key: function key() {
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
- _key = null;
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
- var VALID_METHODS = ['SAMPLE-AES', 'SAMPLE-AES-CTR', 'SAMPLE-AES-CENC'];
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
- _key = {
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
- _key.iv = entry.attributes.IV;
1333
+ key.iv = entry.attributes.IV;
1343
1334
  }
1344
1335
  },
1345
- 'media-sequence': function mediaSequence() {
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
- 'discontinuity-sequence': function discontinuitySequence() {
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
- 'playlist-type': function playlistType() {
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
- map: function map() {
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 (_key) {
1388
- currentMap.key = _key;
1382
+ if (key) {
1383
+ currentMap.key = key;
1389
1384
  }
1390
1385
  },
1391
- 'stream-inf': function streamInf() {
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
- _extends_1(currentUri.attributes, entry.attributes);
1402
+ _extends$1(currentUri.attributes, entry.attributes);
1407
1403
  },
1408
- media: function media() {
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
- var mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE];
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
- discontinuity: function discontinuity() {
1453
+
1454
+ discontinuity() {
1457
1455
  currentTimeline += 1;
1458
1456
  currentUri.discontinuity = true;
1459
1457
  this.manifest.discontinuityStarts.push(uris.length);
1460
1458
  },
1461
- 'program-date-time': function programDateTime() {
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
- targetduration: function targetduration() {
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
- start: function start() {
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
- 'cue-out': function cueOut() {
1514
+
1515
+ 'cue-out'() {
1499
1516
  currentUri.cueOut = entry.data;
1500
1517
  },
1501
- 'cue-out-cont': function cueOutCont() {
1518
+
1519
+ 'cue-out-cont'() {
1502
1520
  currentUri.cueOutCont = entry.data;
1503
1521
  },
1504
- 'cue-in': function cueIn() {
1522
+
1523
+ 'cue-in'() {
1505
1524
  currentUri.cueIn = entry.data;
1506
1525
  },
1507
- 'skip': function skip() {
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
- var segmentIndex = this.manifest.segments.length;
1517
- var part = camelCaseKeys(entry.attributes);
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
- var partIndex = currentUri.parts.length - 1;
1530
- this.warnOnMissingAttributes_("#EXT-X-PART #" + partIndex + " for segment #" + segmentIndex, entry.attributes, ['URI', 'DURATION']);
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(function (r, i) {
1552
+ this.manifest.renditionReports.forEach((r, i) => {
1534
1553
  if (!r.hasOwnProperty('lastPart')) {
1535
- _this2.trigger('warn', {
1536
- message: "#EXT-X-RENDITION-REPORT #" + i + " lacks required attribute(s): LAST-PART"
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
- 'server-control': function serverControl() {
1543
- var attrs = this.manifest.serverControl = camelCaseKeys(entry.attributes);
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
- 'preload-hint': function preloadHint() {
1580
+
1581
+ 'preload-hint'() {
1561
1582
  // parts are always specifed before a segment
1562
- var segmentIndex = this.manifest.segments.length;
1563
- var hint = camelCaseKeys(entry.attributes);
1564
- var isPart = hint.type && hint.type === 'PART';
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
- var index = currentUri.preloadHints.length - 1;
1580
- this.warnOnMissingAttributes_("#EXT-X-PRELOAD-HINT #" + index + " for segment #" + segmentIndex, entry.attributes, ['TYPE', 'URI']);
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 (var i = 0; i < currentUri.preloadHints.length - 1; i++) {
1589
- var otherHint = currentUri.preloadHints[i];
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: "#EXT-X-PRELOAD-HINT #" + index + " for segment #" + segmentIndex + " has the same TYPE " + hint.type + " as preload hint #" + i
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
- 'rendition-report': function renditionReport() {
1603
- var report = camelCaseKeys(entry.attributes);
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
- var index = this.manifest.renditionReports.length - 1;
1607
- var required = ['LAST-MSN', 'URI'];
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_("#EXT-X-RENDITION-REPORT #" + index, entry.attributes, required);
1635
+ this.warnOnMissingAttributes_(`#EXT-X-RENDITION-REPORT #${index}`, entry.attributes, required);
1614
1636
  },
1615
- 'part-inf': function partInf() {
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
- uri: function uri() {
1725
+
1726
+ uri() {
1628
1727
  currentUri.uri = entry.uri;
1629
1728
  currentUri.lineNumberStart = nextSegmentLineNumberStart;
1630
- currentUri.lineNumberEnd = this.parseStream.lineNumber;
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 (_key) {
1642
- currentUri.key = _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; // prepare for the next URI
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
- comment: function comment() {// comments are not important for playback
1761
+
1762
+ comment() {// comments are not important for playback
1657
1763
  },
1658
- custom: function custom() {
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
- var _proto = Parser.prototype;
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 + " lacks required attribute(s): " + missing.join(', ')
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
- _proto.push = function push(chunk) {
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
- _proto.end = function end() {
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.type the type to register to the output
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
- _proto.addParser = function addParser(options) {
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
- _proto.addTagMapper = function addTagMapper(options) {
1846
+
1847
+ addTagMapper(options) {
1736
1848
  this.parseStream.addTagMapper(options);
1737
- };
1849
+ }
1738
1850
 
1739
- return Parser;
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
+ }));