@transmitlive/m3u8-parser 4.7.2-beta.6

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 (134) hide show
  1. package/CONTRIBUTING.md +30 -0
  2. package/LICENSE +13 -0
  3. package/README.md +388 -0
  4. package/dist/m3u8-parser.cjs.js +1563 -0
  5. package/dist/m3u8-parser.es.js +1549 -0
  6. package/dist/m3u8-parser.js +1748 -0
  7. package/dist/m3u8-parser.min.js +2 -0
  8. package/index.html +15 -0
  9. package/package.json +100 -0
  10. package/scripts/karma.conf.js +12 -0
  11. package/scripts/rollup.config.js +47 -0
  12. package/src/index.js +19 -0
  13. package/src/line-stream.js +35 -0
  14. package/src/parse-stream.js +619 -0
  15. package/src/parser.js +748 -0
  16. package/test/fixtures/integration/absoluteUris.js +31 -0
  17. package/test/fixtures/integration/absoluteUris.m3u8 +12 -0
  18. package/test/fixtures/integration/allowCache.js +165 -0
  19. package/test/fixtures/integration/allowCache.m3u8 +58 -0
  20. package/test/fixtures/integration/allowCacheInvalid.js +21 -0
  21. package/test/fixtures/integration/allowCacheInvalid.m3u8 +10 -0
  22. package/test/fixtures/integration/alternateAudio.js +56 -0
  23. package/test/fixtures/integration/alternateAudio.m3u8 +9 -0
  24. package/test/fixtures/integration/alternateVideo.js +48 -0
  25. package/test/fixtures/integration/alternateVideo.m3u8 +8 -0
  26. package/test/fixtures/integration/brightcove.js +57 -0
  27. package/test/fixtures/integration/brightcove.m3u8 +9 -0
  28. package/test/fixtures/integration/byteRange.js +161 -0
  29. package/test/fixtures/integration/byteRange.m3u8 +56 -0
  30. package/test/fixtures/integration/dateTime.js +27 -0
  31. package/test/fixtures/integration/dateTime.m3u8 +12 -0
  32. package/test/fixtures/integration/diff-init-key.js +164 -0
  33. package/test/fixtures/integration/diff-init-key.m3u8 +57 -0
  34. package/test/fixtures/integration/disallowCache.js +21 -0
  35. package/test/fixtures/integration/disallowCache.m3u8 +10 -0
  36. package/test/fixtures/integration/disc-sequence.js +32 -0
  37. package/test/fixtures/integration/disc-sequence.m3u8 +15 -0
  38. package/test/fixtures/integration/discontinuity.js +59 -0
  39. package/test/fixtures/integration/discontinuity.m3u8 +26 -0
  40. package/test/fixtures/integration/domainUris.js +31 -0
  41. package/test/fixtures/integration/domainUris.m3u8 +12 -0
  42. package/test/fixtures/integration/empty.js +5 -0
  43. package/test/fixtures/integration/empty.m3u8 +0 -0
  44. package/test/fixtures/integration/emptyAllowCache.js +21 -0
  45. package/test/fixtures/integration/emptyAllowCache.m3u8 +10 -0
  46. package/test/fixtures/integration/emptyMediaSequence.js +31 -0
  47. package/test/fixtures/integration/emptyMediaSequence.m3u8 +14 -0
  48. package/test/fixtures/integration/emptyPlaylistType.js +40 -0
  49. package/test/fixtures/integration/emptyPlaylistType.m3u8 +16 -0
  50. package/test/fixtures/integration/emptyTargetDuration.js +57 -0
  51. package/test/fixtures/integration/emptyTargetDuration.m3u8 +10 -0
  52. package/test/fixtures/integration/encrypted.js +61 -0
  53. package/test/fixtures/integration/encrypted.m3u8 +28 -0
  54. package/test/fixtures/integration/event.js +41 -0
  55. package/test/fixtures/integration/event.m3u8 +16 -0
  56. package/test/fixtures/integration/extXPlaylistTypeInvalidPlaylist.js +15 -0
  57. package/test/fixtures/integration/extXPlaylistTypeInvalidPlaylist.m3u8 +8 -0
  58. package/test/fixtures/integration/extinf.js +165 -0
  59. package/test/fixtures/integration/extinf.m3u8 +57 -0
  60. package/test/fixtures/integration/fmp4.js +44 -0
  61. package/test/fixtures/integration/fmp4.m3u8 +14 -0
  62. package/test/fixtures/integration/headerOnly.js +5 -0
  63. package/test/fixtures/integration/headerOnly.m3u8 +1 -0
  64. package/test/fixtures/integration/invalidAllowCache.js +21 -0
  65. package/test/fixtures/integration/invalidAllowCache.m3u8 +10 -0
  66. package/test/fixtures/integration/invalidMediaSequence.js +31 -0
  67. package/test/fixtures/integration/invalidMediaSequence.m3u8 +14 -0
  68. package/test/fixtures/integration/invalidPlaylistType.js +40 -0
  69. package/test/fixtures/integration/invalidPlaylistType.m3u8 +16 -0
  70. package/test/fixtures/integration/invalidTargetDuration.js +164 -0
  71. package/test/fixtures/integration/invalidTargetDuration.m3u8 +57 -0
  72. package/test/fixtures/integration/liveMissingSegmentDuration.js +25 -0
  73. package/test/fixtures/integration/liveMissingSegmentDuration.m3u8 +9 -0
  74. package/test/fixtures/integration/liveStart30sBefore.js +54 -0
  75. package/test/fixtures/integration/liveStart30sBefore.m3u8 +22 -0
  76. package/test/fixtures/integration/llhls-byte-range.js +253 -0
  77. package/test/fixtures/integration/llhls-byte-range.m3u8 +66 -0
  78. package/test/fixtures/integration/llhls-delta-byte-range.js +149 -0
  79. package/test/fixtures/integration/llhls-delta-byte-range.m3u8 +30 -0
  80. package/test/fixtures/integration/llhls.js +214 -0
  81. package/test/fixtures/integration/llhls.m3u8 +56 -0
  82. package/test/fixtures/integration/llhlsDelta.js +186 -0
  83. package/test/fixtures/integration/llhlsDelta.m3u8 +50 -0
  84. package/test/fixtures/integration/manifestExtTTargetdurationNegative.js +14 -0
  85. package/test/fixtures/integration/manifestExtTTargetdurationNegative.m3u8 +5 -0
  86. package/test/fixtures/integration/manifestExtXEndlistEarly.js +35 -0
  87. package/test/fixtures/integration/manifestExtXEndlistEarly.m3u8 +14 -0
  88. package/test/fixtures/integration/manifestNoExtM3u.js +15 -0
  89. package/test/fixtures/integration/manifestNoExtM3u.m3u8 +4 -0
  90. package/test/fixtures/integration/master-fmp4.js +465 -0
  91. package/test/fixtures/integration/master-fmp4.m3u8 +76 -0
  92. package/test/fixtures/integration/master.js +57 -0
  93. package/test/fixtures/integration/master.m3u8 +10 -0
  94. package/test/fixtures/integration/media.js +31 -0
  95. package/test/fixtures/integration/media.m3u8 +12 -0
  96. package/test/fixtures/integration/mediaSequence.js +31 -0
  97. package/test/fixtures/integration/mediaSequence.m3u8 +14 -0
  98. package/test/fixtures/integration/missingEndlist.js +19 -0
  99. package/test/fixtures/integration/missingEndlist.m3u8 +6 -0
  100. package/test/fixtures/integration/missingExtinf.js +27 -0
  101. package/test/fixtures/integration/missingExtinf.m3u8 +11 -0
  102. package/test/fixtures/integration/missingMediaSequence.js +31 -0
  103. package/test/fixtures/integration/missingMediaSequence.m3u8 +13 -0
  104. package/test/fixtures/integration/missingSegmentDuration.js +31 -0
  105. package/test/fixtures/integration/missingSegmentDuration.m3u8 +11 -0
  106. package/test/fixtures/integration/multipleAudioGroups.js +89 -0
  107. package/test/fixtures/integration/multipleAudioGroups.m3u8 +17 -0
  108. package/test/fixtures/integration/multipleAudioGroupsCombinedMain.js +88 -0
  109. package/test/fixtures/integration/multipleAudioGroupsCombinedMain.m3u8 +17 -0
  110. package/test/fixtures/integration/multipleTargetDurations.js +28 -0
  111. package/test/fixtures/integration/multipleTargetDurations.m3u8 +8 -0
  112. package/test/fixtures/integration/multipleVideo.js +74 -0
  113. package/test/fixtures/integration/multipleVideo.m3u8 +16 -0
  114. package/test/fixtures/integration/negativeMediaSequence.js +31 -0
  115. package/test/fixtures/integration/negativeMediaSequence.m3u8 +14 -0
  116. package/test/fixtures/integration/playlist.js +165 -0
  117. package/test/fixtures/integration/playlist.m3u8 +57 -0
  118. package/test/fixtures/integration/playlistMediaSequenceHigher.js +16 -0
  119. package/test/fixtures/integration/playlistMediaSequenceHigher.m3u8 +8 -0
  120. package/test/fixtures/integration/start.js +36 -0
  121. package/test/fixtures/integration/start.m3u8 +13 -0
  122. package/test/fixtures/integration/streamInfInvalid.js +24 -0
  123. package/test/fixtures/integration/streamInfInvalid.m3u8 +6 -0
  124. package/test/fixtures/integration/twoMediaSequences.js +31 -0
  125. package/test/fixtures/integration/twoMediaSequences.m3u8 +15 -0
  126. package/test/fixtures/integration/versionInvalid.js +16 -0
  127. package/test/fixtures/integration/versionInvalid.m3u8 +8 -0
  128. package/test/fixtures/integration/whiteSpace.js +31 -0
  129. package/test/fixtures/integration/whiteSpace.m3u8 +13 -0
  130. package/test/fixtures/integration/zeroDuration.js +16 -0
  131. package/test/fixtures/integration/zeroDuration.m3u8 +7 -0
  132. package/test/line-stream.test.js +80 -0
  133. package/test/parse-stream.test.js +903 -0
  134. package/test/parser.test.js +884 -0
@@ -0,0 +1,1748 @@
1
+ /*! @name @transmitlive/m3u8-parser @version 4.7.2-beta.6 @license Apache-2.0 */
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;
19
+
20
+ /**
21
+ * @file stream.js
22
+ */
23
+
24
+ /**
25
+ * A lightweight readable stream implemention that handles event dispatching.
26
+ *
27
+ * @class Stream
28
+ */
29
+ var Stream = /*#__PURE__*/function () {
30
+ function Stream() {
31
+ this.listeners = {};
32
+ }
33
+ /**
34
+ * Add a listener for a specified event type.
35
+ *
36
+ * @param {string} type the event name
37
+ * @param {Function} listener the callback to be invoked when an event of
38
+ * the specified type occurs
39
+ */
40
+
41
+
42
+ var _proto = Stream.prototype;
43
+
44
+ _proto.on = function on(type, listener) {
45
+ if (!this.listeners[type]) {
46
+ this.listeners[type] = [];
47
+ }
48
+
49
+ this.listeners[type].push(listener);
50
+ }
51
+ /**
52
+ * Remove a listener for a specified event type.
53
+ *
54
+ * @param {string} type the event name
55
+ * @param {Function} listener a function previously registered for this
56
+ * type of event through `on`
57
+ * @return {boolean} if we could turn it off or not
58
+ */
59
+ ;
60
+
61
+ _proto.off = function off(type, listener) {
62
+ if (!this.listeners[type]) {
63
+ return false;
64
+ }
65
+
66
+ var index = this.listeners[type].indexOf(listener); // TODO: which is better?
67
+ // In Video.js we slice listener functions
68
+ // on trigger so that it does not mess up the order
69
+ // while we loop through.
70
+ //
71
+ // Here we slice on off so that the loop in trigger
72
+ // can continue using it's old reference to loop without
73
+ // messing up the order.
74
+
75
+ this.listeners[type] = this.listeners[type].slice(0);
76
+ this.listeners[type].splice(index, 1);
77
+ return index > -1;
78
+ }
79
+ /**
80
+ * Trigger an event of the specified type on this stream. Any additional
81
+ * arguments to this function are passed as parameters to event listeners.
82
+ *
83
+ * @param {string} type the event name
84
+ */
85
+ ;
86
+
87
+ _proto.trigger = function trigger(type) {
88
+ var callbacks = this.listeners[type];
89
+
90
+ if (!callbacks) {
91
+ return;
92
+ } // Slicing the arguments on every invocation of this method
93
+ // can add a significant amount of overhead. Avoid the
94
+ // intermediate object creation for the common case of a
95
+ // single callback argument
96
+
97
+
98
+ if (arguments.length === 2) {
99
+ var length = callbacks.length;
100
+
101
+ for (var i = 0; i < length; ++i) {
102
+ callbacks[i].call(this, arguments[1]);
103
+ }
104
+ } else {
105
+ var args = Array.prototype.slice.call(arguments, 1);
106
+ var _length = callbacks.length;
107
+
108
+ for (var _i = 0; _i < _length; ++_i) {
109
+ callbacks[_i].apply(this, args);
110
+ }
111
+ }
112
+ }
113
+ /**
114
+ * Destroys the stream and cleans up.
115
+ */
116
+ ;
117
+
118
+ _proto.dispose = function dispose() {
119
+ this.listeners = {};
120
+ }
121
+ /**
122
+ * Forwards all `data` events on this stream to the destination stream. The
123
+ * destination stream should provide a method `push` to receive the data
124
+ * events as they arrive.
125
+ *
126
+ * @param {Stream} destination the stream that will receive all `data` events
127
+ * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
128
+ */
129
+ ;
130
+
131
+ _proto.pipe = function pipe(destination) {
132
+ this.on('data', function (data) {
133
+ destination.push(data);
134
+ });
135
+ };
136
+
137
+ return Stream;
138
+ }();
139
+
140
+ /**
141
+ * A stream that buffers string input and generates a `data` event for each
142
+ * line.
143
+ *
144
+ * @class LineStream
145
+ * @extends Stream
146
+ */
147
+
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;
157
+ }
158
+ /**
159
+ * Add new data to be parsed.
160
+ *
161
+ * @param {string} data the text to process
162
+ */
163
+
164
+
165
+ var _proto = LineStream.prototype;
166
+
167
+ _proto.push = function push(data) {
168
+ var nextNewline;
169
+ this.buffer += data;
170
+ nextNewline = this.buffer.indexOf('\n');
171
+
172
+ for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) {
173
+ this.trigger('data', this.buffer.substring(0, nextNewline));
174
+ this.buffer = this.buffer.substring(nextNewline + 1);
175
+ }
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
+ }
190
+
191
+ function commonjsRequire () {
192
+ throw new Error('Dynamic requires are not currently supported by @rollup/plugin-commonjs');
193
+ }
194
+
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];
200
+
201
+ for (var key in source) {
202
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
203
+ target[key] = source[key];
204
+ }
205
+ }
206
+ }
207
+
208
+ return target;
209
+ };
210
+
211
+ return _extends.apply(this, arguments);
212
+ }
213
+
214
+ module.exports = _extends;
215
+ });
216
+
217
+ var TAB = String.fromCharCode(0x09);
218
+
219
+ var parseByterange = function parseByterange(byterangeString) {
220
+ // optionally match and capture 0+ digits before `@`
221
+ // optionally match and capture 0+ digits after `@`
222
+ var match = /([0-9.]*)?@?([0-9.]*)?/.exec(byterangeString || '');
223
+ var result = {};
224
+
225
+ if (match[1]) {
226
+ result.length = parseInt(match[1], 10);
227
+ }
228
+
229
+ if (match[2]) {
230
+ result.offset = parseInt(match[2], 10);
231
+ }
232
+
233
+ return result;
234
+ };
235
+ /**
236
+ * "forgiving" attribute list psuedo-grammar:
237
+ * attributes -> keyvalue (',' keyvalue)*
238
+ * keyvalue -> key '=' value
239
+ * key -> [^=]*
240
+ * value -> '"' [^"]* '"' | [^,]*
241
+ */
242
+
243
+
244
+ var attributeSeparator = function attributeSeparator() {
245
+ var key = '[^=]*';
246
+ var value = '"[^"]*"|[^,]*';
247
+ var keyvalue = '(?:' + key + ')=(?:' + value + ')';
248
+ return new RegExp('(?:^|,)(' + keyvalue + ')');
249
+ };
250
+ /**
251
+ * Parse attributes from a line given the separator
252
+ *
253
+ * @param {string} attributes the attribute line to parse
254
+ */
255
+
256
+
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;
263
+
264
+ while (i--) {
265
+ // filter out unmatched portions of the string
266
+ if (attrs[i] === '') {
267
+ continue;
268
+ } // split the key and value
269
+
270
+
271
+ attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1); // trim whitespace and remove optional quotes around the value
272
+
273
+ attr[0] = attr[0].replace(/^\s+|\s+$/g, '');
274
+ attr[1] = attr[1].replace(/^\s+|\s+$/g, '');
275
+ attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1');
276
+ result[attr[0]] = attr[1];
277
+ }
278
+
279
+ return result;
280
+ };
281
+ /**
282
+ * A line-level M3U8 parser event stream. It expects to receive input one
283
+ * line at a time and performs a context-free parse of its contents. A stream
284
+ * interpretation of a manifest can be useful if the manifest is expected to
285
+ * be too large to fit comfortably into memory or the entirety of the input
286
+ * is not immediately available. Otherwise, it's probably much easier to work
287
+ * with a regular `Parser` object.
288
+ *
289
+ * Produces `data` events with an object that captures the parser's
290
+ * interpretation of the input. That object has a property `tag` that is one
291
+ * of `uri`, `comment`, or `tag`. URIs only have a single additional
292
+ * property, `line`, which captures the entirety of the input without
293
+ * interpretation. Comments similarly have a single additional property
294
+ * `text` which is the input without the leading `#`.
295
+ *
296
+ * Tags always have a property `tagType` which is the lower-cased version of
297
+ * the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance,
298
+ * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized
299
+ * tags are given the tag type `unknown` and a single additional property
300
+ * `data` with the remainder of the input.
301
+ *
302
+ * @class ParseStream
303
+ * @extends Stream
304
+ */
305
+
306
+
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;
318
+ }
319
+ /**
320
+ * Parses an additional line of input.
321
+ *
322
+ * @param {string} line a single line of an M3U8 file to parse
323
+ */
324
+
325
+
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
334
+
335
+ line = line.trim();
336
+
337
+ if (line.length === 0) {
338
+ // ignore empty lines
339
+ return;
340
+ } // URIs
341
+
342
+
343
+ if (line[0] !== '#') {
344
+ this.trigger('data', {
345
+ type: 'uri',
346
+ uri: line
347
+ });
348
+ return;
349
+ } // map tags
350
+
351
+
352
+ var newLines = this.tagMappers.reduce(function (acc, mapper) {
353
+ var mappedLine = mapper(line); // skip if unchanged
354
+
355
+ if (mappedLine === line) {
356
+ return acc;
357
+ }
358
+
359
+ return acc.concat([mappedLine]);
360
+ }, [line]);
361
+ newLines.forEach(function (newLine) {
362
+ for (var i = 0; i < _this2.customParsers.length; i++) {
363
+ if (_this2.customParsers[i].call(_this2, newLine)) {
364
+ return;
365
+ }
366
+ } // Comments
367
+
368
+
369
+ if (newLine.indexOf('#EXT') !== 0) {
370
+ _this2.trigger('data', {
371
+ type: 'comment',
372
+ text: newLine.slice(1)
373
+ });
374
+
375
+ return;
376
+ } // strip off any carriage returns here so the regex matching
377
+ // doesn't have to account for them.
378
+
379
+
380
+ newLine = newLine.replace('\r', ''); // Tags
381
+
382
+ match = /^#EXTM3U/.exec(newLine);
383
+
384
+ if (match) {
385
+ _this2.trigger('data', {
386
+ type: 'tag',
387
+ tagType: 'm3u'
388
+ });
389
+
390
+ return;
391
+ }
392
+
393
+ match = /^#EXTINF:?([0-9\.]*)?,?(.*)?$/.exec(newLine);
394
+
395
+ if (match) {
396
+ event = {
397
+ type: 'tag',
398
+ tagType: 'inf'
399
+ };
400
+
401
+ if (match[1]) {
402
+ event.duration = parseFloat(match[1]);
403
+ }
404
+
405
+ if (match[2]) {
406
+ event.title = match[2];
407
+ }
408
+
409
+ _this2.trigger('data', event);
410
+
411
+ return;
412
+ }
413
+
414
+ match = /^#EXT-X-TARGETDURATION:?([0-9.]*)?/.exec(newLine);
415
+
416
+ if (match) {
417
+ event = {
418
+ type: 'tag',
419
+ tagType: 'targetduration'
420
+ };
421
+
422
+ if (match[1]) {
423
+ event.duration = parseInt(match[1], 10);
424
+ }
425
+
426
+ _this2.trigger('data', event);
427
+
428
+ return;
429
+ }
430
+
431
+ match = /^#EXT-X-VERSION:?([0-9.]*)?/.exec(newLine);
432
+
433
+ if (match) {
434
+ event = {
435
+ type: 'tag',
436
+ tagType: 'version'
437
+ };
438
+
439
+ if (match[1]) {
440
+ event.version = parseInt(match[1], 10);
441
+ }
442
+
443
+ _this2.trigger('data', event);
444
+
445
+ return;
446
+ }
447
+
448
+ match = /^#EXT-X-MEDIA-SEQUENCE:?(\-?[0-9.]*)?/.exec(newLine);
449
+
450
+ if (match) {
451
+ event = {
452
+ type: 'tag',
453
+ tagType: 'media-sequence'
454
+ };
455
+
456
+ if (match[1]) {
457
+ event.number = parseInt(match[1], 10);
458
+ }
459
+
460
+ _this2.trigger('data', event);
461
+
462
+ return;
463
+ }
464
+
465
+ match = /^#EXT-X-DISCONTINUITY-SEQUENCE:?(\-?[0-9.]*)?/.exec(newLine);
466
+
467
+ if (match) {
468
+ event = {
469
+ type: 'tag',
470
+ tagType: 'discontinuity-sequence'
471
+ };
472
+
473
+ if (match[1]) {
474
+ event.number = parseInt(match[1], 10);
475
+ }
476
+
477
+ _this2.trigger('data', event);
478
+
479
+ return;
480
+ }
481
+
482
+ match = /^#EXT-X-PLAYLIST-TYPE:?(.*)?$/.exec(newLine);
483
+
484
+ if (match) {
485
+ event = {
486
+ type: 'tag',
487
+ tagType: 'playlist-type'
488
+ };
489
+
490
+ if (match[1]) {
491
+ event.playlistType = match[1];
492
+ }
493
+
494
+ _this2.trigger('data', event);
495
+
496
+ return;
497
+ }
498
+
499
+ match = /^#EXT-X-BYTERANGE:?(.*)?$/.exec(newLine);
500
+
501
+ if (match) {
502
+ event = _extends_1(parseByterange(match[1]), {
503
+ type: 'tag',
504
+ tagType: 'byterange'
505
+ });
506
+
507
+ _this2.trigger('data', event);
508
+
509
+ return;
510
+ }
511
+
512
+ match = /^#EXT-X-ALLOW-CACHE:?(YES|NO)?/.exec(newLine);
513
+
514
+ if (match) {
515
+ event = {
516
+ type: 'tag',
517
+ tagType: 'allow-cache'
518
+ };
519
+
520
+ if (match[1]) {
521
+ event.allowed = !/NO/.test(match[1]);
522
+ }
523
+
524
+ _this2.trigger('data', event);
525
+
526
+ return;
527
+ }
528
+
529
+ match = /^#EXT-X-MAP:?(.*)$/.exec(newLine);
530
+
531
+ if (match) {
532
+ event = {
533
+ type: 'tag',
534
+ tagType: 'map'
535
+ };
536
+
537
+ if (match[1]) {
538
+ var attributes = parseAttributes(match[1]);
539
+
540
+ if (attributes.URI) {
541
+ event.uri = attributes.URI;
542
+ }
543
+
544
+ if (attributes.BYTERANGE) {
545
+ event.byterange = parseByterange(attributes.BYTERANGE);
546
+ }
547
+ }
548
+
549
+ _this2.trigger('data', event);
550
+
551
+ return;
552
+ }
553
+
554
+ match = /^#EXT-X-STREAM-INF:?(.*)$/.exec(newLine);
555
+
556
+ if (match) {
557
+ event = {
558
+ type: 'tag',
559
+ tagType: 'stream-inf'
560
+ };
561
+
562
+ if (match[1]) {
563
+ event.attributes = parseAttributes(match[1]);
564
+
565
+ if (event.attributes.RESOLUTION) {
566
+ var split = event.attributes.RESOLUTION.split('x');
567
+ var resolution = {};
568
+
569
+ if (split[0]) {
570
+ resolution.width = parseInt(split[0], 10);
571
+ }
572
+
573
+ if (split[1]) {
574
+ resolution.height = parseInt(split[1], 10);
575
+ }
576
+
577
+ event.attributes.RESOLUTION = resolution;
578
+ }
579
+
580
+ if (event.attributes.BANDWIDTH) {
581
+ event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
582
+ }
583
+
584
+ if (event.attributes['PROGRAM-ID']) {
585
+ event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10);
586
+ }
587
+ }
588
+
589
+ _this2.trigger('data', event);
590
+
591
+ return;
592
+ }
593
+
594
+ match = /^#EXT-X-MEDIA:?(.*)$/.exec(newLine);
595
+
596
+ if (match) {
597
+ event = {
598
+ type: 'tag',
599
+ tagType: 'media'
600
+ };
601
+
602
+ if (match[1]) {
603
+ event.attributes = parseAttributes(match[1]);
604
+ }
605
+
606
+ _this2.trigger('data', event);
607
+
608
+ return;
609
+ }
610
+
611
+ match = /^#EXT-X-ENDLIST/.exec(newLine);
612
+
613
+ if (match) {
614
+ _this2.trigger('data', {
615
+ type: 'tag',
616
+ tagType: 'endlist'
617
+ });
618
+
619
+ return;
620
+ }
621
+
622
+ match = /^#EXT-X-DISCONTINUITY/.exec(newLine);
623
+
624
+ if (match) {
625
+ _this2.trigger('data', {
626
+ type: 'tag',
627
+ tagType: 'discontinuity'
628
+ });
629
+
630
+ return;
631
+ }
632
+
633
+ match = /^#EXT-X-PROGRAM-DATE-TIME:?(.*)$/.exec(newLine);
634
+
635
+ if (match) {
636
+ event = {
637
+ type: 'tag',
638
+ tagType: 'program-date-time'
639
+ };
640
+
641
+ if (match[1]) {
642
+ event.dateTimeString = match[1];
643
+ event.dateTimeObject = new Date(match[1]);
644
+ }
645
+
646
+ _this2.trigger('data', event);
647
+
648
+ return;
649
+ }
650
+
651
+ match = /^#EXT-X-KEY:?(.*)$/.exec(newLine);
652
+
653
+ if (match) {
654
+ event = {
655
+ type: 'tag',
656
+ tagType: 'key'
657
+ };
658
+
659
+ if (match[1]) {
660
+ event.attributes = parseAttributes(match[1]); // parse the IV string into a Uint32Array
661
+
662
+ if (event.attributes.IV) {
663
+ if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') {
664
+ event.attributes.IV = event.attributes.IV.substring(2);
665
+ }
666
+
667
+ event.attributes.IV = event.attributes.IV.match(/.{8}/g);
668
+ event.attributes.IV[0] = parseInt(event.attributes.IV[0], 16);
669
+ event.attributes.IV[1] = parseInt(event.attributes.IV[1], 16);
670
+ event.attributes.IV[2] = parseInt(event.attributes.IV[2], 16);
671
+ event.attributes.IV[3] = parseInt(event.attributes.IV[3], 16);
672
+ event.attributes.IV = new Uint32Array(event.attributes.IV);
673
+ }
674
+ }
675
+
676
+ _this2.trigger('data', event);
677
+
678
+ return;
679
+ }
680
+
681
+ match = /^#EXT-X-START:?(.*)$/.exec(newLine);
682
+
683
+ if (match) {
684
+ event = {
685
+ type: 'tag',
686
+ tagType: 'start'
687
+ };
688
+
689
+ if (match[1]) {
690
+ event.attributes = parseAttributes(match[1]);
691
+ event.attributes['TIME-OFFSET'] = parseFloat(event.attributes['TIME-OFFSET']);
692
+ event.attributes.PRECISE = /YES/.test(event.attributes.PRECISE);
693
+ }
694
+
695
+ _this2.trigger('data', event);
696
+
697
+ return;
698
+ }
699
+
700
+ match = /^#EXT-X-CUE-OUT-CONT:?(.*)?$/.exec(newLine);
701
+
702
+ if (match) {
703
+ event = {
704
+ type: 'tag',
705
+ tagType: 'cue-out-cont'
706
+ };
707
+
708
+ if (match[1]) {
709
+ event.data = match[1];
710
+ } else {
711
+ event.data = '';
712
+ }
713
+
714
+ _this2.trigger('data', event);
715
+
716
+ return;
717
+ }
718
+
719
+ match = /^#EXT-X-CUE-OUT:?(.*)?$/.exec(newLine);
720
+
721
+ if (match) {
722
+ event = {
723
+ type: 'tag',
724
+ tagType: 'cue-out'
725
+ };
726
+
727
+ if (match[1]) {
728
+ event.data = match[1];
729
+ } else {
730
+ event.data = '';
731
+ }
732
+
733
+ _this2.trigger('data', event);
734
+
735
+ return;
736
+ }
737
+
738
+ match = /^#EXT-X-CUE-IN:?(.*)?$/.exec(newLine);
739
+
740
+ if (match) {
741
+ event = {
742
+ type: 'tag',
743
+ tagType: 'cue-in'
744
+ };
745
+
746
+ if (match[1]) {
747
+ event.data = match[1];
748
+ } else {
749
+ event.data = '';
750
+ }
751
+
752
+ _this2.trigger('data', event);
753
+
754
+ return;
755
+ }
756
+
757
+ match = /^#EXT-X-SKIP:(.*)$/.exec(newLine);
758
+
759
+ if (match && match[1]) {
760
+ event = {
761
+ type: 'tag',
762
+ tagType: 'skip'
763
+ };
764
+ event.attributes = parseAttributes(match[1]);
765
+
766
+ if (event.attributes.hasOwnProperty('SKIPPED-SEGMENTS')) {
767
+ event.attributes['SKIPPED-SEGMENTS'] = parseInt(event.attributes['SKIPPED-SEGMENTS'], 10);
768
+ }
769
+
770
+ if (event.attributes.hasOwnProperty('RECENTLY-REMOVED-DATERANGES')) {
771
+ event.attributes['RECENTLY-REMOVED-DATERANGES'] = event.attributes['RECENTLY-REMOVED-DATERANGES'].split(TAB);
772
+ }
773
+
774
+ _this2.trigger('data', event);
775
+
776
+ return;
777
+ }
778
+
779
+ match = /^#EXT-X-PART:(.*)$/.exec(newLine);
780
+
781
+ if (match && match[1]) {
782
+ event = {
783
+ type: 'tag',
784
+ tagType: 'part'
785
+ };
786
+ event.attributes = parseAttributes(match[1]);
787
+ ['DURATION'].forEach(function (key) {
788
+ if (event.attributes.hasOwnProperty(key)) {
789
+ event.attributes[key] = parseFloat(event.attributes[key]);
790
+ }
791
+ });
792
+ ['INDEPENDENT', 'GAP'].forEach(function (key) {
793
+ if (event.attributes.hasOwnProperty(key)) {
794
+ event.attributes[key] = /YES/.test(event.attributes[key]);
795
+ }
796
+ });
797
+
798
+ if (event.attributes.hasOwnProperty('BYTERANGE')) {
799
+ event.attributes.byterange = parseByterange(event.attributes.BYTERANGE);
800
+ }
801
+
802
+ _this2.trigger('data', event);
803
+
804
+ return;
805
+ }
806
+
807
+ match = /^#EXT-X-SERVER-CONTROL:(.*)$/.exec(newLine);
808
+
809
+ if (match && match[1]) {
810
+ event = {
811
+ type: 'tag',
812
+ tagType: 'server-control'
813
+ };
814
+ event.attributes = parseAttributes(match[1]);
815
+ ['CAN-SKIP-UNTIL', 'PART-HOLD-BACK', 'HOLD-BACK'].forEach(function (key) {
816
+ if (event.attributes.hasOwnProperty(key)) {
817
+ event.attributes[key] = parseFloat(event.attributes[key]);
818
+ }
819
+ });
820
+ ['CAN-SKIP-DATERANGES', 'CAN-BLOCK-RELOAD'].forEach(function (key) {
821
+ if (event.attributes.hasOwnProperty(key)) {
822
+ event.attributes[key] = /YES/.test(event.attributes[key]);
823
+ }
824
+ });
825
+
826
+ _this2.trigger('data', event);
827
+
828
+ return;
829
+ }
830
+
831
+ match = /^#EXT-X-PART-INF:(.*)$/.exec(newLine);
832
+
833
+ if (match && match[1]) {
834
+ event = {
835
+ type: 'tag',
836
+ tagType: 'part-inf'
837
+ };
838
+ event.attributes = parseAttributes(match[1]);
839
+ ['PART-TARGET'].forEach(function (key) {
840
+ if (event.attributes.hasOwnProperty(key)) {
841
+ event.attributes[key] = parseFloat(event.attributes[key]);
842
+ }
843
+ });
844
+
845
+ _this2.trigger('data', event);
846
+
847
+ return;
848
+ }
849
+
850
+ match = /^#EXT-X-PRELOAD-HINT:(.*)$/.exec(newLine);
851
+
852
+ if (match && match[1]) {
853
+ event = {
854
+ type: 'tag',
855
+ tagType: 'preload-hint'
856
+ };
857
+ event.attributes = parseAttributes(match[1]);
858
+ ['BYTERANGE-START', 'BYTERANGE-LENGTH'].forEach(function (key) {
859
+ if (event.attributes.hasOwnProperty(key)) {
860
+ event.attributes[key] = parseInt(event.attributes[key], 10);
861
+ var subkey = key === 'BYTERANGE-LENGTH' ? 'length' : 'offset';
862
+ event.attributes.byterange = event.attributes.byterange || {};
863
+ event.attributes.byterange[subkey] = event.attributes[key]; // only keep the parsed byterange object.
864
+
865
+ delete event.attributes[key];
866
+ }
867
+ });
868
+
869
+ _this2.trigger('data', event);
870
+
871
+ return;
872
+ }
873
+
874
+ match = /^#EXT-X-RENDITION-REPORT:(.*)$/.exec(newLine);
875
+
876
+ if (match && match[1]) {
877
+ event = {
878
+ type: 'tag',
879
+ tagType: 'rendition-report'
880
+ };
881
+ event.attributes = parseAttributes(match[1]);
882
+ ['LAST-MSN', 'LAST-PART'].forEach(function (key) {
883
+ if (event.attributes.hasOwnProperty(key)) {
884
+ event.attributes[key] = parseInt(event.attributes[key], 10);
885
+ }
886
+ });
887
+
888
+ _this2.trigger('data', event);
889
+
890
+ return;
891
+ } // unknown tag type
892
+
893
+
894
+ _this2.trigger('data', {
895
+ type: 'tag',
896
+ data: newLine.slice(4)
897
+ });
898
+ });
899
+ }
900
+ /**
901
+ * Add a parser for custom headers
902
+ *
903
+ * @param {Object} options a map of options for the added parser
904
+ * @param {RegExp} options.expression a regular expression to match the custom header
905
+ * @param {string} options.customType the custom type to register to the output
906
+ * @param {Function} [options.dataParser] function to parse the line into an object
907
+ * @param {boolean} [options.segment] should tag data be attached to the segment object
908
+ */
909
+ ;
910
+
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
+
919
+ if (typeof dataParser !== 'function') {
920
+ dataParser = function dataParser(line) {
921
+ return line;
922
+ };
923
+ }
924
+
925
+ this.customParsers.push(function (line) {
926
+ var match = expression.exec(line);
927
+
928
+ if (match) {
929
+ _this3.trigger('data', {
930
+ type: 'custom',
931
+ data: dataParser(line),
932
+ customType: customType,
933
+ segment: segment
934
+ });
935
+
936
+ return true;
937
+ }
938
+ });
939
+ }
940
+ /**
941
+ * Add a custom header mapper
942
+ *
943
+ * @param {Object} options
944
+ * @param {RegExp} options.expression a regular expression to match the custom header
945
+ * @param {Function} options.map function to translate tag into a different tag
946
+ */
947
+ ;
948
+
949
+ _proto.addTagMapper = function addTagMapper(_ref2) {
950
+ var expression = _ref2.expression,
951
+ map = _ref2.map;
952
+
953
+ var mapFn = function mapFn(line) {
954
+ if (expression.test(line)) {
955
+ return map(line);
956
+ }
957
+
958
+ return line;
959
+ };
960
+
961
+ 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
+ }
971
+
972
+ return self;
973
+ }
974
+
975
+ var assertThisInitialized = _assertThisInitialized;
976
+
977
+ var atob = function atob(s) {
978
+ return window__default['default'].atob ? window__default['default'].atob(s) : Buffer.from(s, 'base64').toString('binary');
979
+ };
980
+
981
+ function decodeB64ToUint8Array(b64Text) {
982
+ var decodedString = atob(b64Text);
983
+ var array = new Uint8Array(decodedString.length);
984
+
985
+ for (var i = 0; i < decodedString.length; i++) {
986
+ array[i] = decodedString.charCodeAt(i);
987
+ }
988
+
989
+ return array;
990
+ }
991
+
992
+ var camelCase = function camelCase(str) {
993
+ return str.toLowerCase().replace(/-(\w)/g, function (a) {
994
+ return a[1].toUpperCase();
995
+ });
996
+ };
997
+
998
+ var camelCaseKeys = function camelCaseKeys(attributes) {
999
+ var result = {};
1000
+ Object.keys(attributes).forEach(function (key) {
1001
+ result[camelCase(key)] = attributes[key];
1002
+ });
1003
+ return result;
1004
+ }; // set SERVER-CONTROL hold back based upon targetDuration and partTargetDuration
1005
+ // we need this helper because defaults are based upon targetDuration and
1006
+ // partTargetDuration being set, but they may not be if SERVER-CONTROL appears before
1007
+ // target durations are set.
1008
+
1009
+
1010
+ var setHoldBack = function setHoldBack(manifest) {
1011
+ var serverControl = manifest.serverControl,
1012
+ targetDuration = manifest.targetDuration,
1013
+ partTargetDuration = manifest.partTargetDuration;
1014
+
1015
+ if (!serverControl) {
1016
+ return;
1017
+ }
1018
+
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;
1024
+
1025
+ if (targetDuration && !serverControl.hasOwnProperty(hb)) {
1026
+ serverControl[hb] = minTargetDuration;
1027
+ this.trigger('info', {
1028
+ message: tag + " defaulting HOLD-BACK to targetDuration * 3 (" + minTargetDuration + ")."
1029
+ });
1030
+ }
1031
+
1032
+ if (minTargetDuration && serverControl[hb] < minTargetDuration) {
1033
+ this.trigger('warn', {
1034
+ message: tag + " clamping HOLD-BACK (" + serverControl[hb] + ") to targetDuration * 3 (" + minTargetDuration + ")"
1035
+ });
1036
+ serverControl[hb] = minTargetDuration;
1037
+ } // default no part hold back to part target duration * 3
1038
+
1039
+
1040
+ if (partTargetDuration && !serverControl.hasOwnProperty(phb)) {
1041
+ serverControl[phb] = partTargetDuration * 3;
1042
+ this.trigger('info', {
1043
+ message: tag + " defaulting PART-HOLD-BACK to partTargetDuration * 3 (" + serverControl[phb] + ")."
1044
+ });
1045
+ } // if part hold back is too small default it to part target duration * 2
1046
+
1047
+
1048
+ if (partTargetDuration && serverControl[phb] < minPartDuration) {
1049
+ this.trigger('warn', {
1050
+ message: tag + " clamping PART-HOLD-BACK (" + serverControl[phb] + ") to partTargetDuration * 2 (" + minPartDuration + ")."
1051
+ });
1052
+ serverControl[phb] = minPartDuration;
1053
+ }
1054
+ };
1055
+ /**
1056
+ * A parser for M3U8 files. The current interpretation of the input is
1057
+ * exposed as a property `manifest` on parser objects. It's just two lines to
1058
+ * create and parse a manifest once you have the contents available as a string:
1059
+ *
1060
+ * ```js
1061
+ * var parser = new m3u8.Parser();
1062
+ * parser.push(xhr.responseText);
1063
+ * ```
1064
+ *
1065
+ * New input can later be applied to update the manifest object by calling
1066
+ * `push` again.
1067
+ *
1068
+ * The parser attempts to create a usable manifest object even if the
1069
+ * underlying input is somewhat nonsensical. It emits `info` and `warning`
1070
+ * events during the parse if it encounters input that seems invalid or
1071
+ * requires some property of the manifest object to be defaulted.
1072
+ *
1073
+ * @class Parser
1074
+ * @extends Stream
1075
+ */
1076
+
1077
+
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);
1089
+ /* eslint-disable consistent-this */
1090
+
1091
+
1092
+ var self = assertThisInitialized(_this);
1093
+ /* eslint-enable consistent-this */
1094
+
1095
+
1096
+ var uris = [];
1097
+ var currentUri = {}; // if specified, the active EXT-X-MAP definition
1098
+
1099
+ var currentMap; // if specified, the active decryption key
1100
+
1101
+ var _key;
1102
+
1103
+ var hasParts = false;
1104
+
1105
+ var noop = function noop() {};
1106
+
1107
+ var defaultMediaGroups = {
1108
+ 'AUDIO': {},
1109
+ 'VIDEO': {},
1110
+ 'CLOSED-CAPTIONS': {},
1111
+ 'SUBTITLES': {}
1112
+ }; // This is the Widevine UUID from DASH IF IOP. The same exact string is
1113
+ // used in MPDs with Widevine encrypted streams.
1114
+
1115
+ var widevineUuid = 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'; // group segments into numbered timelines delineated by discontinuities
1116
+
1117
+ var currentTimeline = 0; // the manifest is empty until the parse stream begins delivering data
1118
+
1119
+ _this.manifest = {
1120
+ allowCache: true,
1121
+ discontinuityStarts: [],
1122
+ segments: []
1123
+ }; // keep track of the last seen segment's byte range end, as segments are not required
1124
+ // to provide the offset, in which case it defaults to the next byte after the
1125
+ // previous segment
1126
+
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
1130
+
1131
+ var nextSegmentLineNumberStart = 0;
1132
+
1133
+ _this.on('end', function () {
1134
+ // only add preloadSegment if we don't yet have a uri for it.
1135
+ // and we actually have parts/preloadHints
1136
+ if (currentUri.uri || !currentUri.parts && !currentUri.preloadHints) {
1137
+ return;
1138
+ }
1139
+
1140
+ if (!currentUri.map && currentMap) {
1141
+ currentUri.map = currentMap;
1142
+ }
1143
+
1144
+ if (!currentUri.key && _key) {
1145
+ currentUri.key = _key;
1146
+ }
1147
+
1148
+ if (!currentUri.timeline && typeof currentTimeline === 'number') {
1149
+ currentUri.timeline = currentTimeline;
1150
+ }
1151
+
1152
+ _this.manifest.preloadSegment = currentUri;
1153
+ }); // update the manifest with the m3u8 entry from the parse stream
1154
+
1155
+
1156
+ _this.parseStream.on('data', function (entry) {
1157
+ var mediaGroup;
1158
+ var rendition; // starting a new segment
1159
+
1160
+ if (!Object.keys(currentUri).length) {
1161
+ nextSegmentLineNumberStart = this.lineNumber;
1162
+ }
1163
+
1164
+ ({
1165
+ tag: function tag() {
1166
+ // switch based on the tag type
1167
+ (({
1168
+ version: function version() {
1169
+ if (entry.version) {
1170
+ this.manifest.version = entry.version;
1171
+ }
1172
+ },
1173
+ 'allow-cache': function allowCache() {
1174
+ this.manifest.allowCache = entry.allowed;
1175
+
1176
+ if (!('allowed' in entry)) {
1177
+ this.trigger('info', {
1178
+ message: 'defaulting allowCache to YES'
1179
+ });
1180
+ this.manifest.allowCache = true;
1181
+ }
1182
+ },
1183
+ byterange: function byterange() {
1184
+ var byterange = {};
1185
+
1186
+ if ('length' in entry) {
1187
+ currentUri.byterange = byterange;
1188
+ byterange.length = entry.length;
1189
+
1190
+ if (!('offset' in entry)) {
1191
+ /*
1192
+ * From the latest spec (as of this writing):
1193
+ * https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.2
1194
+ *
1195
+ * Same text since EXT-X-BYTERANGE's introduction in draft 7:
1196
+ * https://tools.ietf.org/html/draft-pantos-http-live-streaming-07#section-3.3.1)
1197
+ *
1198
+ * "If o [offset] is not present, the sub-range begins at the next byte
1199
+ * following the sub-range of the previous media segment."
1200
+ */
1201
+ entry.offset = lastByterangeEnd;
1202
+ }
1203
+ }
1204
+
1205
+ if ('offset' in entry) {
1206
+ currentUri.byterange = byterange;
1207
+ byterange.offset = entry.offset;
1208
+ }
1209
+
1210
+ lastByterangeEnd = byterange.offset + byterange.length;
1211
+ },
1212
+ endlist: function endlist() {
1213
+ this.manifest.endList = true;
1214
+ },
1215
+ inf: function inf() {
1216
+ if (!('mediaSequence' in this.manifest)) {
1217
+ this.manifest.mediaSequence = 0;
1218
+ this.trigger('info', {
1219
+ message: 'defaulting media sequence to zero'
1220
+ });
1221
+ }
1222
+
1223
+ if (!('discontinuitySequence' in this.manifest)) {
1224
+ this.manifest.discontinuitySequence = 0;
1225
+ this.trigger('info', {
1226
+ message: 'defaulting discontinuity sequence to zero'
1227
+ });
1228
+ }
1229
+
1230
+ if (entry.duration > 0) {
1231
+ currentUri.duration = entry.duration;
1232
+ }
1233
+
1234
+ if (entry.duration === 0) {
1235
+ currentUri.duration = 0.01;
1236
+ this.trigger('info', {
1237
+ message: 'updating zero segment duration to a small value'
1238
+ });
1239
+ }
1240
+
1241
+ this.manifest.segments = uris;
1242
+ },
1243
+ key: function key() {
1244
+ if (!entry.attributes) {
1245
+ this.trigger('warn', {
1246
+ message: 'ignoring key declaration without attribute list'
1247
+ });
1248
+ return;
1249
+ } // clear the active encryption key
1250
+
1251
+
1252
+ if (entry.attributes.METHOD === 'NONE') {
1253
+ _key = null;
1254
+ return;
1255
+ }
1256
+
1257
+ if (!entry.attributes.URI) {
1258
+ this.trigger('warn', {
1259
+ message: 'ignoring key declaration without URI'
1260
+ });
1261
+ return;
1262
+ }
1263
+
1264
+ if (entry.attributes.KEYFORMAT === 'com.apple.streamingkeydelivery') {
1265
+ this.manifest.contentProtection = this.manifest.contentProtection || {}; // TODO: add full support for this.
1266
+
1267
+ this.manifest.contentProtection['com.apple.fps.1_0'] = {
1268
+ attributes: entry.attributes
1269
+ };
1270
+ return;
1271
+ }
1272
+
1273
+ if (entry.attributes.KEYFORMAT === 'com.microsoft.playready') {
1274
+ this.manifest.contentProtection = this.manifest.contentProtection || {}; // TODO: add full support for this.
1275
+
1276
+ this.manifest.contentProtection['com.microsoft.playready'] = {
1277
+ uri: entry.attributes.URI
1278
+ };
1279
+ return;
1280
+ } // check if the content is encrypted for Widevine
1281
+ // Widevine/HLS spec: https://storage.googleapis.com/wvdocs/Widevine_DRM_HLS.pdf
1282
+
1283
+
1284
+ if (entry.attributes.KEYFORMAT === widevineUuid) {
1285
+ var VALID_METHODS = ['SAMPLE-AES', 'SAMPLE-AES-CTR', 'SAMPLE-AES-CENC'];
1286
+
1287
+ if (VALID_METHODS.indexOf(entry.attributes.METHOD) === -1) {
1288
+ this.trigger('warn', {
1289
+ message: 'invalid key method provided for Widevine'
1290
+ });
1291
+ return;
1292
+ }
1293
+
1294
+ if (entry.attributes.METHOD === 'SAMPLE-AES-CENC') {
1295
+ this.trigger('warn', {
1296
+ message: 'SAMPLE-AES-CENC is deprecated, please use SAMPLE-AES-CTR instead'
1297
+ });
1298
+ }
1299
+
1300
+ if (entry.attributes.URI.substring(0, 23) !== 'data:text/plain;base64,') {
1301
+ this.trigger('warn', {
1302
+ message: 'invalid key URI provided for Widevine'
1303
+ });
1304
+ return;
1305
+ }
1306
+
1307
+ if (!(entry.attributes.KEYID && entry.attributes.KEYID.substring(0, 2) === '0x')) {
1308
+ this.trigger('warn', {
1309
+ message: 'invalid key ID provided for Widevine'
1310
+ });
1311
+ return;
1312
+ } // if Widevine key attributes are valid, store them as `contentProtection`
1313
+ // on the manifest to emulate Widevine tag structure in a DASH mpd
1314
+
1315
+
1316
+ this.manifest.contentProtection = this.manifest.contentProtection || {};
1317
+ this.manifest.contentProtection['com.widevine.alpha'] = {
1318
+ attributes: {
1319
+ schemeIdUri: entry.attributes.KEYFORMAT,
1320
+ // remove '0x' from the key id string
1321
+ keyId: entry.attributes.KEYID.substring(2)
1322
+ },
1323
+ // decode the base64-encoded PSSH box
1324
+ pssh: decodeB64ToUint8Array(entry.attributes.URI.split(',')[1])
1325
+ };
1326
+ return;
1327
+ }
1328
+
1329
+ if (!entry.attributes.METHOD) {
1330
+ this.trigger('warn', {
1331
+ message: 'defaulting key method to AES-128'
1332
+ });
1333
+ } // setup an encryption key for upcoming segments
1334
+
1335
+
1336
+ _key = {
1337
+ method: entry.attributes.METHOD || 'AES-128',
1338
+ uri: entry.attributes.URI
1339
+ };
1340
+
1341
+ if (typeof entry.attributes.IV !== 'undefined') {
1342
+ _key.iv = entry.attributes.IV;
1343
+ }
1344
+ },
1345
+ 'media-sequence': function mediaSequence() {
1346
+ if (!isFinite(entry.number)) {
1347
+ this.trigger('warn', {
1348
+ message: 'ignoring invalid media sequence: ' + entry.number
1349
+ });
1350
+ return;
1351
+ }
1352
+
1353
+ this.manifest.mediaSequence = entry.number;
1354
+ },
1355
+ 'discontinuity-sequence': function discontinuitySequence() {
1356
+ if (!isFinite(entry.number)) {
1357
+ this.trigger('warn', {
1358
+ message: 'ignoring invalid discontinuity sequence: ' + entry.number
1359
+ });
1360
+ return;
1361
+ }
1362
+
1363
+ this.manifest.discontinuitySequence = entry.number;
1364
+ currentTimeline = entry.number;
1365
+ },
1366
+ 'playlist-type': function playlistType() {
1367
+ if (!/VOD|EVENT/.test(entry.playlistType)) {
1368
+ this.trigger('warn', {
1369
+ message: 'ignoring unknown playlist type: ' + entry.playlist
1370
+ });
1371
+ return;
1372
+ }
1373
+
1374
+ this.manifest.playlistType = entry.playlistType;
1375
+ },
1376
+ map: function map() {
1377
+ currentMap = {};
1378
+
1379
+ if (entry.uri) {
1380
+ currentMap.uri = entry.uri;
1381
+ }
1382
+
1383
+ if (entry.byterange) {
1384
+ currentMap.byterange = entry.byterange;
1385
+ }
1386
+
1387
+ if (_key) {
1388
+ currentMap.key = _key;
1389
+ }
1390
+ },
1391
+ 'stream-inf': function streamInf() {
1392
+ this.manifest.playlists = uris;
1393
+ this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
1394
+
1395
+ if (!entry.attributes) {
1396
+ this.trigger('warn', {
1397
+ message: 'ignoring empty stream-inf attributes'
1398
+ });
1399
+ return;
1400
+ }
1401
+
1402
+ if (!currentUri.attributes) {
1403
+ currentUri.attributes = {};
1404
+ }
1405
+
1406
+ _extends_1(currentUri.attributes, entry.attributes);
1407
+ },
1408
+ media: function media() {
1409
+ this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
1410
+
1411
+ if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) {
1412
+ this.trigger('warn', {
1413
+ message: 'ignoring incomplete or missing media group'
1414
+ });
1415
+ return;
1416
+ } // find the media group, creating defaults as necessary
1417
+
1418
+
1419
+ var mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE];
1420
+ mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {};
1421
+ mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']]; // collect the rendition metadata
1422
+
1423
+ rendition = {
1424
+ default: /yes/i.test(entry.attributes.DEFAULT)
1425
+ };
1426
+
1427
+ if (rendition.default) {
1428
+ rendition.autoselect = true;
1429
+ } else {
1430
+ rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT);
1431
+ }
1432
+
1433
+ if (entry.attributes.LANGUAGE) {
1434
+ rendition.language = entry.attributes.LANGUAGE;
1435
+ }
1436
+
1437
+ if (entry.attributes.URI) {
1438
+ rendition.uri = entry.attributes.URI;
1439
+ }
1440
+
1441
+ if (entry.attributes['INSTREAM-ID']) {
1442
+ rendition.instreamId = entry.attributes['INSTREAM-ID'];
1443
+ }
1444
+
1445
+ if (entry.attributes.CHARACTERISTICS) {
1446
+ rendition.characteristics = entry.attributes.CHARACTERISTICS;
1447
+ }
1448
+
1449
+ if (entry.attributes.FORCED) {
1450
+ rendition.forced = /yes/i.test(entry.attributes.FORCED);
1451
+ } // insert the new rendition
1452
+
1453
+
1454
+ mediaGroup[entry.attributes.NAME] = rendition;
1455
+ },
1456
+ discontinuity: function discontinuity() {
1457
+ currentTimeline += 1;
1458
+ currentUri.discontinuity = true;
1459
+ this.manifest.discontinuityStarts.push(uris.length);
1460
+ },
1461
+ 'program-date-time': function programDateTime() {
1462
+ if (typeof this.manifest.dateTimeString === 'undefined') {
1463
+ // PROGRAM-DATE-TIME is a media-segment tag, but for backwards
1464
+ // compatibility, we add the first occurence of the PROGRAM-DATE-TIME tag
1465
+ // to the manifest object
1466
+ // TODO: Consider removing this in future major version
1467
+ this.manifest.dateTimeString = entry.dateTimeString;
1468
+ this.manifest.dateTimeObject = entry.dateTimeObject;
1469
+ }
1470
+
1471
+ currentUri.dateTimeString = entry.dateTimeString;
1472
+ currentUri.dateTimeObject = entry.dateTimeObject;
1473
+ },
1474
+ targetduration: function targetduration() {
1475
+ if (!isFinite(entry.duration) || entry.duration < 0) {
1476
+ this.trigger('warn', {
1477
+ message: 'ignoring invalid target duration: ' + entry.duration
1478
+ });
1479
+ return;
1480
+ }
1481
+
1482
+ this.manifest.targetDuration = entry.duration;
1483
+ setHoldBack.call(this, this.manifest);
1484
+ },
1485
+ start: function start() {
1486
+ if (!entry.attributes || isNaN(entry.attributes['TIME-OFFSET'])) {
1487
+ this.trigger('warn', {
1488
+ message: 'ignoring start declaration without appropriate attribute list'
1489
+ });
1490
+ return;
1491
+ }
1492
+
1493
+ this.manifest.start = {
1494
+ timeOffset: entry.attributes['TIME-OFFSET'],
1495
+ precise: entry.attributes.PRECISE
1496
+ };
1497
+ },
1498
+ 'cue-out': function cueOut() {
1499
+ currentUri.cueOut = entry.data;
1500
+ },
1501
+ 'cue-out-cont': function cueOutCont() {
1502
+ currentUri.cueOutCont = entry.data;
1503
+ },
1504
+ 'cue-in': function cueIn() {
1505
+ currentUri.cueIn = entry.data;
1506
+ },
1507
+ 'skip': function skip() {
1508
+ this.manifest.skip = camelCaseKeys(entry.attributes);
1509
+ this.warnOnMissingAttributes_('#EXT-X-SKIP', entry.attributes, ['SKIPPED-SEGMENTS']);
1510
+ },
1511
+ 'part': function part() {
1512
+ var _this2 = this;
1513
+
1514
+ hasParts = true; // parts are always specifed before a segment
1515
+
1516
+ var segmentIndex = this.manifest.segments.length;
1517
+ var part = camelCaseKeys(entry.attributes);
1518
+ currentUri.parts = currentUri.parts || [];
1519
+ currentUri.parts.push(part);
1520
+
1521
+ if (part.byterange) {
1522
+ if (!part.byterange.hasOwnProperty('offset')) {
1523
+ part.byterange.offset = lastPartByterangeEnd;
1524
+ }
1525
+
1526
+ lastPartByterangeEnd = part.byterange.offset + part.byterange.length;
1527
+ }
1528
+
1529
+ var partIndex = currentUri.parts.length - 1;
1530
+ this.warnOnMissingAttributes_("#EXT-X-PART #" + partIndex + " for segment #" + segmentIndex, entry.attributes, ['URI', 'DURATION']);
1531
+
1532
+ if (this.manifest.renditionReports) {
1533
+ this.manifest.renditionReports.forEach(function (r, i) {
1534
+ if (!r.hasOwnProperty('lastPart')) {
1535
+ _this2.trigger('warn', {
1536
+ message: "#EXT-X-RENDITION-REPORT #" + i + " lacks required attribute(s): LAST-PART"
1537
+ });
1538
+ }
1539
+ });
1540
+ }
1541
+ },
1542
+ 'server-control': function serverControl() {
1543
+ var attrs = this.manifest.serverControl = camelCaseKeys(entry.attributes);
1544
+
1545
+ if (!attrs.hasOwnProperty('canBlockReload')) {
1546
+ attrs.canBlockReload = false;
1547
+ this.trigger('info', {
1548
+ message: '#EXT-X-SERVER-CONTROL defaulting CAN-BLOCK-RELOAD to false'
1549
+ });
1550
+ }
1551
+
1552
+ setHoldBack.call(this, this.manifest);
1553
+
1554
+ if (attrs.canSkipDateranges && !attrs.hasOwnProperty('canSkipUntil')) {
1555
+ this.trigger('warn', {
1556
+ message: '#EXT-X-SERVER-CONTROL lacks required attribute CAN-SKIP-UNTIL which is required when CAN-SKIP-DATERANGES is set'
1557
+ });
1558
+ }
1559
+ },
1560
+ 'preload-hint': function preloadHint() {
1561
+ // 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';
1565
+ currentUri.preloadHints = currentUri.preloadHints || [];
1566
+ currentUri.preloadHints.push(hint);
1567
+
1568
+ if (hint.byterange) {
1569
+ if (!hint.byterange.hasOwnProperty('offset')) {
1570
+ // use last part byterange end or zero if not a part.
1571
+ hint.byterange.offset = isPart ? lastPartByterangeEnd : 0;
1572
+
1573
+ if (isPart) {
1574
+ lastPartByterangeEnd = hint.byterange.offset + hint.byterange.length;
1575
+ }
1576
+ }
1577
+ }
1578
+
1579
+ var index = currentUri.preloadHints.length - 1;
1580
+ this.warnOnMissingAttributes_("#EXT-X-PRELOAD-HINT #" + index + " for segment #" + segmentIndex, entry.attributes, ['TYPE', 'URI']);
1581
+
1582
+ if (!hint.type) {
1583
+ return;
1584
+ } // search through all preload hints except for the current one for
1585
+ // a duplicate type.
1586
+
1587
+
1588
+ for (var i = 0; i < currentUri.preloadHints.length - 1; i++) {
1589
+ var otherHint = currentUri.preloadHints[i];
1590
+
1591
+ if (!otherHint.type) {
1592
+ continue;
1593
+ }
1594
+
1595
+ if (otherHint.type === hint.type) {
1596
+ this.trigger('warn', {
1597
+ message: "#EXT-X-PRELOAD-HINT #" + index + " for segment #" + segmentIndex + " has the same TYPE " + hint.type + " as preload hint #" + i
1598
+ });
1599
+ }
1600
+ }
1601
+ },
1602
+ 'rendition-report': function renditionReport() {
1603
+ var report = camelCaseKeys(entry.attributes);
1604
+ this.manifest.renditionReports = this.manifest.renditionReports || [];
1605
+ this.manifest.renditionReports.push(report);
1606
+ var index = this.manifest.renditionReports.length - 1;
1607
+ var required = ['LAST-MSN', 'URI'];
1608
+
1609
+ if (hasParts) {
1610
+ required.push('LAST-PART');
1611
+ }
1612
+
1613
+ this.warnOnMissingAttributes_("#EXT-X-RENDITION-REPORT #" + index, entry.attributes, required);
1614
+ },
1615
+ 'part-inf': function partInf() {
1616
+ this.manifest.partInf = camelCaseKeys(entry.attributes);
1617
+ this.warnOnMissingAttributes_('#EXT-X-PART-INF', entry.attributes, ['PART-TARGET']);
1618
+
1619
+ if (this.manifest.partInf.partTarget) {
1620
+ this.manifest.partTargetDuration = this.manifest.partInf.partTarget;
1621
+ }
1622
+
1623
+ setHoldBack.call(this, this.manifest);
1624
+ }
1625
+ })[entry.tagType] || noop).call(self);
1626
+ },
1627
+ uri: function uri() {
1628
+ currentUri.uri = entry.uri;
1629
+ currentUri.lineNumberStart = nextSegmentLineNumberStart;
1630
+ currentUri.lineNumberEnd = this.parseStream.lineNumber;
1631
+ uris.push(currentUri); // if no explicit duration was declared, use the target duration
1632
+
1633
+ if (this.manifest.targetDuration && !('duration' in currentUri)) {
1634
+ this.trigger('warn', {
1635
+ message: 'defaulting segment duration to the target duration'
1636
+ });
1637
+ currentUri.duration = this.manifest.targetDuration;
1638
+ } // annotate with encryption information, if necessary
1639
+
1640
+
1641
+ if (_key) {
1642
+ currentUri.key = _key;
1643
+ }
1644
+
1645
+ currentUri.timeline = currentTimeline; // annotate with initialization segment information, if necessary
1646
+
1647
+ if (currentMap) {
1648
+ currentUri.map = currentMap;
1649
+ } // reset the last byterange end as it needs to be 0 between parts
1650
+
1651
+
1652
+ lastPartByterangeEnd = 0; // prepare for the next URI
1653
+
1654
+ currentUri = {};
1655
+ },
1656
+ comment: function comment() {// comments are not important for playback
1657
+ },
1658
+ custom: function custom() {
1659
+ // if this is segment-level data attach the output to the segment
1660
+ if (entry.segment) {
1661
+ currentUri.custom = currentUri.custom || {};
1662
+ currentUri.custom[entry.customType] = entry.data; // if this is manifest-level data attach to the top level manifest object
1663
+ } else {
1664
+ this.manifest.custom = this.manifest.custom || {};
1665
+ this.manifest.custom[entry.customType] = entry.data;
1666
+ }
1667
+ }
1668
+ })[entry.type].call(self);
1669
+ });
1670
+
1671
+ return _this;
1672
+ }
1673
+
1674
+ var _proto = Parser.prototype;
1675
+
1676
+ _proto.warnOnMissingAttributes_ = function warnOnMissingAttributes_(identifier, attributes, required) {
1677
+ var missing = [];
1678
+ required.forEach(function (key) {
1679
+ if (!attributes.hasOwnProperty(key)) {
1680
+ missing.push(key);
1681
+ }
1682
+ });
1683
+
1684
+ if (missing.length) {
1685
+ this.trigger('warn', {
1686
+ message: identifier + " lacks required attribute(s): " + missing.join(', ')
1687
+ });
1688
+ }
1689
+ }
1690
+ /**
1691
+ * Parse the input string and update the manifest object.
1692
+ *
1693
+ * @param {string} chunk a potentially incomplete portion of the manifest
1694
+ */
1695
+ ;
1696
+
1697
+ _proto.push = function push(chunk) {
1698
+ this.lineStream.push(chunk);
1699
+ }
1700
+ /**
1701
+ * Flush any remaining input. This can be handy if the last line of an M3U8
1702
+ * manifest did not contain a trailing newline but the file has been
1703
+ * completely received.
1704
+ */
1705
+ ;
1706
+
1707
+ _proto.end = function end() {
1708
+ // flush any buffered input
1709
+ this.lineStream.push('\n');
1710
+ this.trigger('end');
1711
+ }
1712
+ /**
1713
+ * Add an additional parser for non-standard tags
1714
+ *
1715
+ * @param {Object} options a map of options for the added parser
1716
+ * @param {RegExp} options.expression a regular expression to match the custom header
1717
+ * @param {string} options.type the type to register to the output
1718
+ * @param {Function} [options.dataParser] function to parse the line into an object
1719
+ * @param {boolean} [options.segment] should tag data be attached to the segment object
1720
+ */
1721
+ ;
1722
+
1723
+ _proto.addParser = function addParser(options) {
1724
+ this.parseStream.addParser(options);
1725
+ }
1726
+ /**
1727
+ * Add a custom header mapper
1728
+ *
1729
+ * @param {Object} options
1730
+ * @param {RegExp} options.expression a regular expression to match the custom header
1731
+ * @param {Function} options.map function to translate tag into a different tag
1732
+ */
1733
+ ;
1734
+
1735
+ _proto.addTagMapper = function addTagMapper(options) {
1736
+ this.parseStream.addTagMapper(options);
1737
+ };
1738
+
1739
+ return Parser;
1740
+ }(Stream);
1741
+
1742
+ exports.LineStream = LineStream;
1743
+ exports.ParseStream = ParseStream;
1744
+ exports.Parser = Parser;
1745
+
1746
+ Object.defineProperty(exports, '__esModule', { value: true });
1747
+
1748
+ })));