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