PageSourceSearch

https://cdn.iol.pt/js/utils/video-js-7.0.5/video.js

js iol.pt collected 2026-09-24 08:27:27 UTC 1,513,423 bytes, 47,810 lines download raw bytes

vendor: 27,531 bytes, lines 1-989
1/**
2 * @license
3 * Video.js 7.0.5 <http://videojs.com/>
4 * Copyright Brightcove, Inc. <https://www.brightcove.com/>
5 * Available under Apache License Version 2.0
6 * <https://github.com/videojs/video.js/blob/master/LICENSE>
7 *
8 * Includes vtt.js <https://github.com/mozilla/vtt.js>
9 * Available under Apache License Version 2.0
10 * <https://github.com/mozilla/vtt.js/blob/master/LICENSE>
11 */
12
13(function (global, factory) {
14  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
15  typeof define === 'function' && define.amd ? define(factory) :
16  (global.videojs = factory());
17}(this, (function () {
18  var version = "7.0.5";
19
20  var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
21
22  function createCommonjsModule(fn, module) {
23  	return module = { exports: {} }, fn(module, module.exports), module.exports;
24  }
25
26  var win;
27
28  if (typeof window !== "undefined") {
29      win = window;
30  } else if (typeof commonjsGlobal !== "undefined") {
31      win = commonjsGlobal;
32  } else if (typeof self !== "undefined") {
33      win = self;
34  } else {
35      win = {};
36  }
37
38  var window_1 = win;
39
40  var empty = {};
41
42  var empty$1 = /*#__PURE__*/Object.freeze({
43    default: empty
44  });
45
46  var minDoc = ( empty$1 && empty ) || empty$1;
47
48  var topLevel = typeof commonjsGlobal !== 'undefined' ? commonjsGlobal : typeof window !== 'undefined' ? window : {};
49
50  var doccy;
51
52  if (typeof document !== 'undefined') {
53      doccy = document;
54  } else {
55      doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'];
56
57      if (!doccy) {
58          doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc;
59      }
60  }
61
62  var document_1 = doccy;
63
64  /**
65   * @file log.js
66   * @module log
67   */
68
69  var log = void 0;
70
71  // This is the private tracking variable for logging level.
72  var level = 'info';
73
74  // This is the private tracking variable for the logging history.
75  var history = [];
76
77  /**
78   * Log messages to the console and history based on the type of message
79   *
80   * @private
81   * @param  {string} type
82   *         The name of the console method to use.
83   *
84   * @param  {Array} args
85   *         The arguments to be passed to the matching console method.
86   */
87  var logByType = function logByType(type, args) {
88    var lvl = log.levels[level];
89    var lvlRegExp = new RegExp('^(' + lvl + ')$');
90
91    if (type !== 'log') {
92
93      // Add the type to the front of the message when it's not "log".
94      args.unshift(type.toUpperCase() + ':');
95    }
96
97    // Add a clone of the args at this point to history.
98    if (history) {
99      history.push([].concat(args));
100    }
101
102    // Add console prefix after adding to history.
103    args.unshift('VIDEOJS:');
104
105    // If there's no console then don't try to output messages, but they will
106    // still be stored in history.
107    if (!window_1.console) {
108      return;
109    }
110
111    // Was setting these once outside of this function, but containing them
112    // in the function makes it easier to test cases where console doesn't exist
113    // when the module is executed.
114    var fn = window_1.console[type];
115
116    if (!fn && type === 'debug') {
117      // Certain browsers don't have support for console.debug. For those, we
118      // should default to the closest comparable log.
119      fn = window_1.console.info || window_1.console.log;
120    }
121
122    // Bail out if there's no console or if this type is not allowed by the
123    // current logging level.
124    if (!fn || !lvl || !lvlRegExp.test(type)) {
125      return;
126    }
127
128    fn[Array.isArray(args) ? 'apply' : 'call'](window_1.console, args);
129  };
130
131  /**
132   * Logs plain debug messages. Similar to `console.log`.
133   *
134   * @class
135   * @param    {Mixed[]} args
136   *           One or more messages or objects that should be logged.
137   */
138  log = function log() {
139    for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
140      args[_key] = arguments[_key];
141    }
142
143    logByType('log', args);
144  };
145
146  /**
147   * Enumeration of available logging levels, where the keys are the level names
148   * and the values are `|`-separated strings containing logging methods allowed
149   * in that logging level. These strings are used to create a regular expression
150   * matching the function name being called.
151   *
152   * Levels provided by video.js are:
153   *
154   * - `off`: Matches no calls. Any value that can be cast to `false` will have
155   *   this effect. The most restrictive.
156   * - `all`: Matches only Video.js-provided functions (`debug`, `log`,
157   *   `log.warn`, and `log.error`).
158   * - `debug`: Matches `log.debug`, `log`, `log.warn`, and `log.error` calls.
159   * - `info` (default): Matches `log`, `log.warn`, and `log.error` calls.
160   * - `warn`: Matches `log.warn` and `log.error` calls.
161   * - `error`: Matches only `log.error` calls.
162   *
163   * @type {Object}
164   */
165  log.levels = {
166    all: 'debug|log|warn|error',
167    off: '',
168    debug: 'debug|log|warn|error',
169    info: 'log|warn|error',
170    warn: 'warn|error',
171    error: 'error',
172    DEFAULT: level
173  };
174
175  /**
176   * Get or set the current logging level. If a string matching a key from
177   * {@link log.levels} is provided, acts as a setter. Regardless of argument,
178   * returns the current logging level.
179   *
180   * @param  {string} [lvl]
181   *         Pass to set a new logging level.
182   *
183   * @return {string}
184   *         The current logging level.
185   */
186  log.level = function (lvl) {
187    if (typeof lvl === 'string') {
188      if (!log.levels.hasOwnProperty(lvl)) {
189        throw new Error('"' + lvl + '" in not a valid log level');
190      }
191      level = lvl;
192    }
193    return level;
194  };
195
196  /**
197   * Returns an array containing everything that has been logged to the history.
198   *
199   * This array is a shallow clone of the internal history record. However, its
200   * contents are _not_ cloned; so, mutating objects inside this array will
201   * mutate them in history.
202   *
203   * @return {Array}
204   */
205  log.history = function () {
206    return history ? [].concat(history) : [];
207  };
208
209  /**
210   * Clears the internal history tracking, but does not prevent further history
211   * tracking.
212   */
213  log.history.clear = function () {
214    if (history) {
215      history.length = 0;
216    }
217  };
218
219  /**
220   * Disable history tracking if it is currently enabled.
221   */
222  log.history.disable = function () {
223    if (history !== null) {
224      history.length = 0;
225      history = null;
226    }
227  };
228
229  /**
230   * Enable history tracking if it is currently disabled.
231   */
232  log.history.enable = function () {
233    if (history === null) {
234      history = [];
235    }
236  };
237
238  /**
239   * Logs error messages. Similar to `console.error`.
240   *
241   * @param {Mixed[]} args
242   *        One or more messages or objects that should be logged as an error
243   */
244  log.error = function () {
245    for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
246      args[_key2] = arguments[_key2];
247    }
248
249    return logByType('error', args);
250  };
251
252  /**
253   * Logs warning messages. Similar to `console.warn`.
254   *
255   * @param {Mixed[]} args
256   *        One or more messages or objects that should be logged as a warning.
257   */
258  log.warn = function () {
259    for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
260      args[_key3] = arguments[_key3];
261    }
262
263    return logByType('warn', args);
264  };
265
266  /**
267   * Logs debug messages. Similar to `console.debug`, but may also act as a comparable
268   * log if `console.debug` is not available
269   *
270   * @param {Mixed[]} args
271   *        One or more messages or objects that should be logged as debug.
272   */
273  log.debug = function () {
274    for (var _len4 = arguments.length, args = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
275      args[_key4] = arguments[_key4];
276    }
277
278    return logByType('debug', args);
279  };
280
281  var log$1 = log;
282
283  function clean(s) {
284    return s.replace(/\n\r?\s*/g, '');
285  }
286
287  var tsml = function tsml(sa) {
288    var s = '',
289        i = 0;
290
291    for (; i < arguments.length; i++) {
292      s += clean(sa[i]) + (arguments[i + 1] || '');
293    }return s;
294  };
295
296  var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
297    return typeof obj;
298  } : function (obj) {
299    return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
300  };
301
302  var classCallCheck = function (instance, Constructor) {
303    if (!(instance instanceof Constructor)) {
304      throw new TypeError("Cannot call a class as a function");
305    }
306  };
307
308  var inherits = function (subClass, superClass) {
309    if (typeof superClass !== "function" && superClass !== null) {
310      throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
311    }
312
313    subClass.prototype = Object.create(superClass && superClass.prototype, {
314      constructor: {
315        value: subClass,
316        enumerable: false,
317        writable: true,
318        configurable: true
319      }
320    });
321    if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
322  };
323
324  var possibleConstructorReturn = function (self, call) {
325    if (!self) {
326      throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
327    }
328
329    return call && (typeof call === "object" || typeof call === "function") ? call : self;
330  };
331
332  var taggedTemplateLiteralLoose = function (strings, raw) {
333    strings.raw = raw;
334    return strings;
335  };
336
337  /**
338   * @file obj.js
339   * @module obj
340   */
341
342  /**
343   * @callback obj:EachCallback
344   *
345   * @param {Mixed} value
346   *        The current key for the object that is being iterated over.
347   *
348   * @param {string} key
349   *        The current key-value for object that is being iterated over
350   */
351
352  /**
353   * @callback obj:ReduceCallback
354   *
355   * @param {Mixed} accum
356   *        The value that is accumulating over the reduce loop.
357   *
358   * @param {Mixed} value
359   *        The current key for the object that is being iterated over.
360   *
361   * @param {string} key
362   *        The current key-value for object that is being iterated over
363   *
364   * @return {Mixed}
365   *         The new accumulated value.
366   */
367  var toString = Object.prototype.toString;
368
369  /**
370   * Get the keys of an Object
371   *
372   * @param {Object}
373   *        The Object to get the keys from
374   *
375   * @return {string[]}
376   *         An array of the keys from the object. Returns an empty array if the
377   *         object passed in was invalid or had no keys.
378   *
379   * @private
380   */
381  var keys = function keys(object) {
382    return isObject(object) ? Object.keys(object) : [];
383  };
384
385  /**
386   * Array-like iteration for objects.
387   *
388   * @param {Object} object
389   *        The object to iterate over
390   *
391   * @param {obj:EachCallback} fn
392   *        The callback function which is called for each key in the object.
393   */
394  function each(object, fn) {
395    keys(object).forEach(function (key) {
396      return fn(object[key], key);
397    });
398  }
399
400  /**
401   * Array-like reduce for objects.
402   *
403   * @param {Object} object
404   *        The Object that you want to reduce.
405   *
406   * @param {Function} fn
407   *         A callback function which is called for each key in the object. It
408   *         receives the accumulated value and the per-iteration value and key
409   *         as arguments.
410   *
411   * @param {Mixed} [initial = 0]
412   *        Starting value
413   *
414   * @return {Mixed}
415   *         The final accumulated value.
416   */
417  function reduce(object, fn) {
418    var initial = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
419
420    return keys(object).reduce(function (accum, key) {
421      return fn(accum, object[key], key);
422    }, initial);
423  }
424
425  /**
426   * Object.assign-style object shallow merge/extend.
427   *
428   * @param  {Object} target
429   * @param  {Object} ...sources
430   * @return {Object}
431   */
432  function assign(target) {
433    for (var _len = arguments.length, sources = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
434      sources[_key - 1] = arguments[_key];
435    }
436
437    if (Object.assign) {
438      return Object.assign.apply(Object, [target].concat(sources));
439    }
440
441    sources.forEach(function (source) {
442      if (!source) {
443        return;
444      }
445
446      each(source, function (value, key) {
447        target[key] = value;
448      });
449    });
450
451    return target;
452  }
453
454  /**
455   * Returns whether a value is an object of any kind - including DOM nodes,
456   * arrays, regular expressions, etc. Not functions, though.
457   *
458   * This avoids the gotcha where using `typeof` on a `null` value
459   * results in `'object'`.
460   *
461   * @param  {Object} value
462   * @return {Boolean}
463   */
464  function isObject(value) {
465    return !!value && (typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object';
466  }
467
468  /**
469   * Returns whether an object appears to be a "plain" object - that is, a
470   * direct instance of `Object`.
471   *
472   * @param  {Object} value
473   * @return {Boolean}
474   */
475  function isPlain(value) {
476    return isObject(value) && toString.call(value) === '[object Object]' && value.constructor === Object;
477  }
478
479  /**
480   * @file computed-style.js
481   * @module computed-style
482   */
483
484  /**
485   * A safe getComputedStyle.
486   *
487   * This is needed because in Firefox, if the player is loaded in an iframe with
488   * `display:none`, then `getComputedStyle` returns `null`, so, we do a null-check to
489   * make sure  that the player doesn't break in these cases.
490   *
491   * @param {Element} el
492   *        The element you want the computed style of
493   *
494   * @param {string} prop
495   *        The property name you want
496   *
497   * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397
498   *
499   * @static
500   * @const
501   */
502  function computedStyle(el, prop) {
503    if (!el || !prop) {
504      return '';
505    }
506
507    if (typeof window_1.getComputedStyle === 'function') {
508      var cs = window_1.getComputedStyle(el);
509
510      return cs ? cs[prop] : '';
511    }
512
513    return '';
514  }
515
516  var _templateObject = taggedTemplateLiteralLoose(['Setting attributes in the second argument of createEl()\n                has been deprecated. Use the third argument instead.\n                createEl(type, properties, attributes). Attempting to set ', ' to ', '.'], ['Setting attributes in the second argument of createEl()\n                has been deprecated. Use the third argument instead.\n                createEl(type, properties, attributes). Attempting to set ', ' to ', '.']);
517
518  /**
519   * Detect if a value is a string with any non-whitespace characters.
520   *
521   * @param {string} str
522   *        The string to check
523   *
524   * @return {boolean}
525   *         - True if the string is non-blank
526   *         - False otherwise
527   *
528   */
529  function isNonBlankString(str) {
530    return typeof str === 'string' && /\S/.test(str);
531  }
532
533  /**
534   * Throws an error if the passed string has whitespace. This is used by
535   * class methods to be relatively consistent with the classList API.
536   *
537   * @param {string} str
538   *         The string to check for whitespace.
539   *
540   * @throws {Error}
541   *         Throws an error if there is whitespace in the string.
542   *
543   */
544  function throwIfWhitespace(str) {
545    if (/\s/.test(str)) {
546      throw new Error('class has illegal whitespace characters');
547    }
548  }
549
550  /**
551   * Produce a regular expression for matching a className within an elements className.
552   *
553   * @param {string} className
554   *         The className to generate the RegExp for.
555   *
556   * @return {RegExp}
557   *         The RegExp that will check for a specific `className` in an elements
558   *         className.
559   */
560  function classRegExp(className) {
561    return new RegExp('(^|\\s)' + className + '($|\\s)');
562  }
563
564  /**
565   * Whether the current DOM interface appears to be real.
566   *
567   * @return {Boolean}
568   */
569  function isReal() {
570    // Both document and window will never be undefined thanks to `global`.
571    return document_1 === window_1.document;
572  }
573
574  /**
575   * Determines, via duck typing, whether or not a value is a DOM element.
576   *
577   * @param {Mixed} value
578   *        The thing to check
579   *
580   * @return {boolean}
581   *         - True if it is a DOM element
582   *         - False otherwise
583   */
584  function isEl(value) {
585    return isObject(value) && value.nodeType === 1;
586  }
587
588  /**
589   * Determines if the current DOM is embedded in an iframe.
590   *
591   * @return {boolean}
592   *
593   */
594  function isInFrame() {
595
596    // We need a try/catch here because Safari will throw errors when attempting
597    // to get either `parent` or `self`
598    try {
599      return window_1.parent !== window_1.self;
600    } catch (x) {
601      return true;
602    }
603  }
604
605  /**
606   * Creates functions to query the DOM using a given method.
607   *
608   * @param {string} method
609   *         The method to create the query with.
610   *
611   * @return {Function}
612   *         The query method
613   */
614  function createQuerier(method) {
615    return function (selector, context) {
616      if (!isNonBlankString(selector)) {
617        return document_1[method](null);
618      }
619      if (isNonBlankString(context)) {
620        context = document_1.querySelector(context);
621      }
622
623      var ctx = isEl(context) ? context : document_1;
624
625      return ctx[method] && ctx[method](selector);
626    };
627  }
628
629  /**
630   * Creates an element and applies properties.
631   *
632   * @param {string} [tagName='div']
633   *         Name of tag to be created.
634   *
635   * @param {Object} [properties={}]
636   *         Element properties to be applied.
637   *
638   * @param {Object} [attributes={}]
639   *         Element attributes to be applied.
640   *
641   * @param {String|Element|TextNode|Array|Function} [content]
642   *         Contents for the element (see: {@link dom:normalizeContent})
643   *
644   * @return {Element}
645   *         The element that was created.
646   */
647  function createEl() {
648    var tagName = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'div';
649    var properties = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
650    var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
651    var content = arguments[3];
652
653    var el = document_1.createElement(tagName);
654
655    Object.getOwnPropertyNames(properties).forEach(function (propName) {
656      var val = properties[propName];
657
658      // See #2176
659      // We originally were accepting both properties and attributes in the
660      // same object, but that doesn't work so well.
661      if (propName.indexOf('aria-') !== -1 || propName === 'role' || propName === 'type') {
662        log$1.warn(tsml(_templateObject, propName, val));
663        el.setAttribute(propName, val);
664
665        // Handle textContent since it's not supported everywhere and we have a
666        // method for it.
667      } else if (propName === 'textContent') {
668        textContent(el, val);
669      } else {
670        el[propName] = val;
671      }
672    });
673
674    Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
675      el.setAttribute(attrName, attributes[attrName]);
676    });
677
678    if (content) {
679      appendContent(el, content);
680    }
681
682    return el;
683  }
684
685  /**
686   * Injects text into an element, replacing any existing contents entirely.
687   *
688   * @param {Element} el
689   *        The element to add text content into
690   *
691   * @param {string} text
692   *        The text content to add.
693   *
694   * @return {Element}
695   *         The element with added text content.
696   */
697  function textContent(el, text) {
698    if (typeof el.textContent === 'undefined') {
699      el.innerText = text;
700    } else {
701      el.textContent = text;
702    }
703    return el;
704  }
705
706  /**
707   * Insert an element as the first child node of another
708   *
709   * @param {Element} child
710   *        Element to insert
711   *
712   * @param {Element} parent
713   *        Element to insert child into
714   */
715  function prependTo(child, parent) {
716    if (parent.firstChild) {
717      parent.insertBefore(child, parent.firstChild);
718    } else {
719      parent.appendChild(child);
720    }
721  }
722
723  /**
724   * Check if an element has a CSS class
725   *
726   * @param {Element} element
727   *        Element to check
728   *
729   * @param {string} classToCheck
730   *        Class name to check for
731   *
732   * @return {boolean}
733   *         - True if the element had the class
734   *         - False otherwise.
735   *
736   * @throws {Error}
737   *         Throws an error if `classToCheck` has white space.
738   */
739  function hasClass(element, classToCheck) {
740    throwIfWhitespace(classToCheck);
741    if (element.classList) {
742      return element.classList.contains(classToCheck);
743    }
744    return classRegExp(classToCheck).test(element.className);
745  }
746
747  /**
748   * Add a CSS class name to an element
749   *
750   * @param {Element} element
751   *        Element to add class name to.
752   *
753   * @param {string} classToAdd
754   *        Class name to add.
755   *
756   * @return {Element}
757   *         The dom element with the added class name.
758   */
759  function addClass(element, classToAdd) {
760    if (element.classList) {
761      element.classList.add(classToAdd);
762
763      // Don't need to `throwIfWhitespace` here because `hasElClass` will do it
764      // in the case of classList not being supported.
765    } else if (!hasClass(element, classToAdd)) {
766      element.className = (element.className + ' ' + classToAdd).trim();
767    }
768
769    return element;
770  }
771
772  /**
773   * Remove a CSS class name from an element
774   *
775   * @param {Element} element
776   *        Element to remove a class name from.
777   *
778   * @param {string} classToRemove
779   *        Class name to remove
780   *
781   * @return {Element}
782   *         The dom element with class name removed.
783   */
784  function removeClass(element, classToRemove) {
785    if (element.classList) {
786      element.classList.remove(classToRemove);
787    } else {
788      throwIfWhitespace(classToRemove);
789      element.className = element.className.split(/\s+/).filter(function (c) {
790        return c !== classToRemove;
791      }).join(' ');
792    }
793
794    return element;
795  }
796
797  /**
798   * The callback definition for toggleElClass.
799   *
800   * @callback Dom~PredicateCallback
801   * @param {Element} element
802   *        The DOM element of the Component.
803   *
804   * @param {string} classToToggle
805   *        The `className` that wants to be toggled
806   *
807   * @return {boolean|undefined}
808   *         - If true the `classToToggle` will get added to `element`.
809   *         - If false the `classToToggle` will get removed from `element`.
810   *         - If undefined this callback will be ignored
811   */
812
813  /**
814   * Adds or removes a CSS class name on an element depending on an optional
815   * condition or the presence/absence of the class name.
816   *
817   * @param {Element} element
818   *        The element to toggle a class name on.
819   *
820   * @param {string} classToToggle
821   *        The class that should be toggled
822   *
823   * @param {boolean|PredicateCallback} [predicate]
824   *        See the return value for {@link Dom~PredicateCallback}
825   *
826   * @return {Element}
827   *         The element with a class that has been toggled.
828   */
829  function toggleClass(element, classToToggle, predicate) {
830
831    // This CANNOT use `classList` internally because IE11 does not support the
832    // second parameter to the `classList.toggle()` method! Which is fine because
833    // `classList` will be used by the add/remove functions.
834    var has = hasClass(element, classToToggle);
835
836    if (typeof predicate === 'function') {
837      predicate = predicate(element, classToToggle);
838    }
839
840    if (typeof predicate !== 'boolean') {
841      predicate = !has;
842    }
843
844    // If the necessary class operation matches the current state of the
845    // element, no action is required.
846    if (predicate === has) {
847      return;
848    }
849
850    if (predicate) {
851      addClass(element, classToToggle);
852    } else {
853      removeClass(element, classToToggle);
854    }
855
856    return element;
857  }
858
859  /**
860   * Apply attributes to an HTML element.
861   *
862   * @param {Element} el
863   *        Element to add attributes to.
864   *
865   * @param {Object} [attributes]
866   *        Attributes to be applied.
867   */
868  function setAttributes(el, attributes) {
869    Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
870      var attrValue = attributes[attrName];
871
872      if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) {
873        el.removeAttribute(attrName);
874      } else {
875        el.setAttribute(attrName, attrValue === true ? '' : attrValue);
876      }
877    });
878  }
879
880  /**
881   * Get an element's attribute values, as defined on the HTML tag
882   * Attributes are not the same as properties. They're defined on the tag
883   * or with setAttribute (which shouldn't be used with HTML)
884   * This will return true or false for boolean attributes.
885   *
886   * @param {Element} tag
887   *        Element from which to get tag attributes.
888   *
889   * @return {Object}
890   *         All attributes of the element.
891   */
892  function getAttributes(tag) {
893    var obj = {};
894
895    // known boolean attributes
896    // we can check for matching boolean properties, but not all browsers
897    // and not all tags know about these attributes, so, we still want to check them manually
898    var knownBooleans = ',' + 'autoplay,controls,playsinline,loop,muted,default,defaultMuted' + ',';
899
900    if (tag && tag.attributes && tag.attributes.length > 0) {
901      var attrs = tag.attributes;
902
903      for (var i = attrs.length - 1; i >= 0; i--) {
904        var attrName = attrs[i].name;
905        var attrVal = attrs[i].value;
906
907        // check for known booleans
908        // the matching element property will return a value for typeof
909        if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) {
910          // the value of an included boolean attribute is typically an empty
911          // string ('') which would equal false if we just check for a false value.
912          // we also don't want support bad code like autoplay='false'
913          attrVal = attrVal !== null ? true : false;
914        }
915
916        obj[attrName] = attrVal;
917      }
918    }
919
920    return obj;
921  }
922
923  /**
924   * Get the value of an element's attribute
925   *
926   * @param {Element} el
927   *        A DOM element
928   *
929   * @param {string} attribute
930   *        Attribute to get the value of
931   *
932   * @return {string}
933   *         value of the attribute
934   */
935  function getAttribute(el, attribute) {
936    return el.getAttribute(attribute);
937  }
938
939  /**
940   * Set the value of an element's attribute
941   *
942   * @param {Element} el
943   *        A DOM element
944   *
945   * @param {string} attribute
946   *        Attribute to set
947   *
948   * @param {string} value
949   *        Value to set the attribute to
950   */
951  function setAttribute(el, attribute, value) {
952    el.setAttribute(attribute, value);
953  }
954
955  /**
956   * Remove an element's attribute
957   *
958   * @param {Element} el
959   *        A DOM element
960   *
961   * @param {string} attribute
962   *        Attribute to remove
963   */
964  function removeAttribute(el, attribute) {
965    el.removeAttribute(attribute);
966  }
967
968  /**
969   * Attempt to block the ability to select text while dragging controls
970   */
971  function blockTextSelection() {
972    document_1.body.focus();
973    document_1.onselectstart = function () {
974      return false;
975    };
976  }
977
978  /**
979   * Turn off text selection blocking
980   */
981  function unblockTextSelection() {
982    document_1.onselectstart = function () {
983      return true;
984    };
985  }
986
987  /**
988   * Identical to the native `getBoundingClientRect` function, but ensures that
989   * the method is supported at all (it is in all browsers we claim to supp
989ort)
990   * and that the element is in the DOM before continuing.
991   *
992   * This wrapper function also shims properties which are not provided by some
993   * older browsers (namely, IE8).
994   *
995   * Additionally, some browsers do not support adding properties to a
996   * `ClientRect`/`DOMRect` object; so, we shallow-copy it with the standard
997   * properties (except `x` and `y` which are not widely supported). This helps
998   * avoid implementations where keys are non-enumerable.
999   *
1000   * @param  {Element} el
1001   *         Element whose `ClientRect` we want to calculate.
1002   *
1003   * @return {Object|undefined}
1004   *         Always returns a plain
1005   */
1006  function getBoundingClientRect(el) {
1007    if (el && el.getBoundingClientRect && el.parentNode) {
1008      var rect = el.getBoundingClientRect();
1009      var result = {};
1010
1011      ['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(function (k) {
1012        if (rect[k] !== undefined) {
1013          result[k] = rect[k];
1014        }
1015      });
1016
1017      if (!result.height) {
1018        result.height = parseFloat(computedStyle(el, 'height'));
1019      }
1020
1021      if (!result.width) {
1022        result.width = parseFloat(computedStyle(el, 'width'));
1023      }
1024
1025      return result;
1026    }
1027  }
1028
1029  /**
1030   * The postion of a DOM element on the page.
1031   *
1032   * @typedef {Object} module:dom~Position
1033   *
1034   * @property {number} left
1035   *           Pixels to the left
1036   *
1037   * @property {number} top
1038   *           Pixels on top
1039   */
1040
1041  /**
1042   * Offset Left.
1043   * getBoundingClientRect technique from
1044   * John Resig
1045   *
1046   * @see http://ejohn.org/blog/getboundingclientrect-is-awesome/
1047   *
1048   * @param {Element} el
1049   *        Element from which to get offset
1050   *
1051   * @return {module:dom~Position}
1052   *         The position of the element that was passed in.
1053   */
1054  function findPosition(el) {
1055    var box = void 0;
1056
1057    if (el.getBoundingClientRect && el.parentNode) {
1058      box = el.getBoundingClientRect();
1059    }
1060
1061    if (!box) {
1062      return {
1063        left: 0,
1064        top: 0
1065      };
1066    }
1067
1068    var docEl = document_1.documentElement;
1069    var body = document_1.body;
1070
1071    var clientLeft = docEl.clientLeft || body.clientLeft || 0;
1072    var scrollLeft = window_1.pageXOffset || body.scrollLeft;
1073    var left = box.left + scrollLeft - clientLeft;
1074
1075    var clientTop = docEl.clientTop || body.clientTop || 0;
1076    var scrollTop = window_1.pageYOffset || body.scrollTop;
1077    var top = box.top + scrollTop - clientTop;
1078
1079    // Android sometimes returns slightly off decimal values, so need to round
1080    return {
1081      left: Math.round(left),
1082      top: Math.round(top)
1083    };
1084  }
1085
1086  /**
1087   * x and y coordinates for a dom element or mouse pointer
1088   *
1089   * @typedef {Object} Dom~Coordinates
1090   *
1091   * @property {number} x
1092   *           x coordinate in pixels
1093   *
1094   * @property {number} y
1095   *           y coordinate in pixels
1096   */
1097
1098  /**
1099   * Get pointer position in element
1100   * Returns an object with x and y coordinates.
1101   * The base on the coordinates are the bottom left of the element.
1102   *
1103   * @param {Element} el
1104   *        Element on which to get the pointer position on
1105   *
1106   * @param {EventTarget~Event} event
1107   *        Event object
1108   *
1109   * @return {Dom~Coordinates}
1110   *         A Coordinates object corresponding to the mouse position.
1111   *
1112   */
1113  function getPointerPosition(el, event) {
1114    var position = {};
1115    var box = findPosition(el);
1116    var boxW = el.offsetWidth;
1117    var boxH = el.offsetHeight;
1118
1119    var boxY = box.top;
1120    var boxX = box.left;
1121    var pageY = event.pageY;
1122    var pageX = event.pageX;
1123
1124    if (event.changedTouches) {
1125      pageX = event.changedTouches[0].pageX;
1126      pageY = event.changedTouches[0].pageY;
1127    }
1128
1129    position.y = Math.max(0, Math.min(1, (boxY - pageY + boxH) / boxH));
1130    position.x = Math.max(0, Math.min(1, (pageX - boxX) / boxW));
1131
1132    return position;
1133  }
1134
1135  /**
1136   * Determines, via duck typing, whether or not a value is a text node.
1137   *
1138   * @param {Mixed} value
1139   *        Check if this value is a text node.
1140   *
1141   * @return {boolean}
1142   *         - True if it is a text node
1143   *         - False otherwise
1144   */
1145  function isTextNode(value) {
1146    return isObject(value) && value.nodeType === 3;
1147  }
1148
1149  /**
1150   * Empties the contents of an element.
1151   *
1152   * @param {Element} el
1153   *        The element to empty children from
1154   *
1155   * @return {Element}
1156   *         The element with no children
1157   */
1158  function emptyEl(el) {
1159    while (el.firstChild) {
1160      el.removeChild(el.firstChild);
1161    }
1162    return el;
1163  }
1164
1165  /**
1166   * Normalizes content for eventual insertion into the DOM.
1167   *
1168   * This allows a wide range of content definition methods, but protects
1169   * from falling into the trap of simply writing to `innerHTML`, which is
1170   * an XSS concern.
1171   *
1172   * The content for an element can be passed in multiple types and
1173   * combinations, whose behavior is as follows:
1174   *
1175   * @param {String|Element|TextNode|Array|Function} content
1176   *        - String: Normalized into a text node.
1177   *        - Element/TextNode: Passed through.
1178   *        - Array: A one-dimensional array of strings, elements, nodes, or functions
1179   *          (which return single strings, elements, or nodes).
1180   *        - Function: If the sole argument, is expected to produce a string, element,
1181   *          node, or array as defined above.
1182   *
1183   * @return {Array}
1184   *         All of the content that was passed in normalized.
1185   */
1186  function normalizeContent(content) {
1187
1188    // First, invoke content if it is a function. If it produces an array,
1189    // that needs to happen before normalization.
1190    if (typeof content === 'function') {
1191      content = content();
1192    }
1193
1194    // Next up, normalize to an array, so one or many items can be normalized,
1195    // filtered, and returned.
1196    return (Array.isArray(content) ? content : [content]).map(function (value) {
1197
1198      // First, invoke value if it is a function to produce a new value,
1199      // which will be subsequently normalized to a Node of some kind.
1200      if (typeof value === 'function') {
1201        value = value();
1202      }
1203
1204      if (isEl(value) || isTextNode(value)) {
1205        return value;
1206      }
1207
1208      if (typeof value === 'string' && /\S/.test(value)) {
1209        return document_1.createTextNode(value);
1210      }
1211    }).filter(function (value) {
1212      return value;
1213    });
1214  }
1215
1216  /**
1217   * Normalizes and appends content to an element.
1218   *
1219   * @param {Element} el
1220   *        Element to append normalized content to.
1221   *
1222   *
1223   * @param {String|Element|TextNode|Array|Function} content
1224   *        See the `content` argument of {@link dom:normalizeContent}
1225   *
1226   * @return {Element}
1227   *         The element with appended normalized content.
1228   */
1229  function appendContent(el, content) {
1230    normalizeContent(content).forEach(function (node) {
1231      return el.appendChild(node);
1232    });
1233    return el;
1234  }
1235
1236  /**
1237   * Normalizes and inserts content into an element; this is identical to
1238   * `appendContent()`, except it empties the element first.
1239   *
1240   * @param {Element} el
1241   *        Element to insert normalized content into.
1242   *
1243   * @param {String|Element|TextNode|Array|Function} content
1244   *        See the `content` argument of {@link dom:normalizeContent}
1245   *
1246   * @return {Element}
1247   *         The element with inserted normalized content.
1248   *
1249   */
1250  function insertContent(el, content) {
1251    return appendContent(emptyEl(el), content);
1252  }
1253
1254  /**
1255   * Check if event was a single left click
1256   *
1257   * @param {EventTarget~Event} event
1258   *        Event object
1259   *
1260   * @return {boolean}
1261   *         - True if a left click
1262   *         - False if not a left click
1263   */
1264  function isSingleLeftClick(event) {
1265    // Note: if you create something draggable, be sure to
1266    // call it on both `mousedown` and `mousemove` event,
1267    // otherwise `mousedown` should be enough for a button
1268
1269    if (event.button === undefined && event.buttons === undefined) {
1270      // Why do we need `buttons` ?
1271      // Because, middle mouse sometimes have this:
1272      // e.button === 0 and e.buttons === 4
1273      // Furthermore, we want to prevent combination click, something like
1274      // HOLD middlemouse then left click, that would be
1275      // e.button === 0, e.buttons === 5
1276      // just `button` is not gonna work
1277
1278      // Alright, then what this block does ?
1279      // this is for chrome `simulate mobile devices`
1280      // I want to support this as well
1281
1282      return true;
1283    }
1284
1285    if (event.button === 0 && event.buttons === undefined) {
1286      // Touch screen, sometimes on some specific device, `buttons`
1287      // doesn't have anything (safari on ios, blackberry...)
1288
1289      return true;
1290    }
1291
1292    if (event.button !== 0 || event.buttons !== 1) {
1293      // This is the reason we have those if else block above
1294      // if any special case we can catch and let it slide
1295      // we do it above, when get to here, this definitely
1296      // is-not-left-click
1297
1298      return false;
1299    }
1300
1301    return true;
1302  }
1303
1304  /**
1305   * Finds a single DOM element matching `selector` within the optional
1306   * `context` of another DOM element (defaulting to `document`).
1307   *
1308   * @param {string} selector
1309   *        A valid CSS selector, which will be passed to `querySelector`.
1310   *
1311   * @param {Element|String} [context=document]
1312   *        A DOM element within which to query. Can also be a selector
vendor: 21,809 bytes, lines 1313-2093
1313   *        string in which case the first matching element will be used
1314   *        as context. If missing (or no element matches selector), falls
1315   *        back to `document`.
1316   *
1317   * @return {Element|null}
1318   *         The element that was found or null.
1319   */
1320  var $ = createQuerier('querySelector');
1321
1322  /**
1323   * Finds a all DOM elements matching `selector` within the optional
1324   * `context` of another DOM element (defaulting to `document`).
1325   *
1326   * @param {string} selector
1327   *           A valid CSS selector, which will be passed to `querySelectorAll`.
1328   *
1329   * @param {Element|String} [context=document]
1330   *           A DOM element within which to query. Can also be a selector
1331   *           string in which case the first matching element will be used
1332   *           as context. If missing (or no element matches selector), falls
1333   *           back to `document`.
1334   *
1335   * @return {NodeList}
1336   *         A element list of elements that were found. Will be empty if none were found.
1337   *
1338   */
1339  var $$ = createQuerier('querySelectorAll');
1340
1341  var Dom = /*#__PURE__*/Object.freeze({
1342    isReal: isReal,
1343    isEl: isEl,
1344    isInFrame: isInFrame,
1345    createEl: createEl,
1346    textContent: textContent,
1347    prependTo: prependTo,
1348    hasClass: hasClass,
1349    addClass: addClass,
1350    removeClass: removeClass,
1351    toggleClass: toggleClass,
1352    setAttributes: setAttributes,
1353    getAttributes: getAttributes,
1354    getAttribute: getAttribute,
1355    setAttribute: setAttribute,
1356    removeAttribute: removeAttribute,
1357    blockTextSelection: blockTextSelection,
1358    unblockTextSelection: unblockTextSelection,
1359    getBoundingClientRect: getBoundingClientRect,
1360    findPosition: findPosition,
1361    getPointerPosition: getPointerPosition,
1362    isTextNode: isTextNode,
1363    emptyEl: emptyEl,
1364    normalizeContent: normalizeContent,
1365    appendContent: appendContent,
1366    insertContent: insertContent,
1367    isSingleLeftClick: isSingleLeftClick,
1368    $: $,
1369    $$: $$
1370  });
1371
1372  /**
1373   * @file guid.js
1374   * @module guid
1375   */
1376
1377  /**
1378   * Unique ID for an element or function
1379   * @type {Number}
1380   */
1381  var _guid = 1;
1382
1383  /**
1384   * Get a unique auto-incrementing ID by number that has not been returned before.
1385   *
1386   * @return {number}
1387   *         A new unique ID.
1388   */
1389  function newGUID() {
1390    return _guid++;
1391  }
1392
1393  /**
1394   * @file dom-data.js
1395   * @module dom-data
1396   */
1397
1398  /**
1399   * Element Data Store.
1400   *
1401   * Allows for binding data to an element without putting it directly on the
1402   * element. Ex. Event listeners are stored here.
1403   * (also from jsninja.com, slightly modified and updated for closure compiler)
1404   *
1405   * @type {Object}
1406   * @private
1407   */
1408  var elData = {};
1409
1410  /*
1411   * Unique attribute name to store an element's guid in
1412   *
1413   * @type {String}
1414   * @constant
1415   * @private
1416   */
1417  var elIdAttr = 'vdata' + new Date().getTime();
1418
1419  /**
1420   * Returns the cache object where data for an element is stored
1421   *
1422   * @param {Element} el
1423   *        Element to store data for.
1424   *
1425   * @return {Object}
1426   *         The cache object for that el that was passed in.
1427   */
1428  function getData(el) {
1429    var id = el[elIdAttr];
1430
1431    if (!id) {
1432      id = el[elIdAttr] = newGUID();
1433    }
1434
1435    if (!elData[id]) {
1436      elData[id] = {};
1437    }
1438
1439    return elData[id];
1440  }
1441
1442  /**
1443   * Returns whether or not an element has cached data
1444   *
1445   * @param {Element} el
1446   *        Check if this element has cached data.
1447   *
1448   * @return {boolean}
1449   *         - True if the DOM element has cached data.
1450   *         - False otherwise.
1451   */
1452  function hasData(el) {
1453    var id = el[elIdAttr];
1454
1455    if (!id) {
1456      return false;
1457    }
1458
1459    return !!Object.getOwnPropertyNames(elData[id]).length;
1460  }
1461
1462  /**
1463   * Delete data for the element from the cache and the guid attr from getElementById
1464   *
1465   * @param {Element} el
1466   *        Remove cached data for this element.
1467   */
1468  function removeData(el) {
1469    var id = el[elIdAttr];
1470
1471    if (!id) {
1472      return;
1473    }
1474
1475    // Remove all stored data
1476    delete elData[id];
1477
1478    // Remove the elIdAttr property from the DOM node
1479    try {
1480      delete el[elIdAttr];
1481    } catch (e) {
1482      if (el.removeAttribute) {
1483        el.removeAttribute(elIdAttr);
1484      } else {
1485        // IE doesn't appear to support removeAttribute on the document element
1486        el[elIdAttr] = null;
1487      }
1488    }
1489  }
1490
1491  /**
1492   * @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/)
1493   * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible)
1494   * This should work very similarly to jQuery's events, however it's based off the book version which isn't as
1495   * robust as jquery's, so there's probably some differences.
1496   *
1497   * @module events
1498   */
1499
1500  /**
1501   * Clean up the listener cache and dispatchers
1502   *
1503   * @param {Element|Object} elem
1504   *        Element to clean up
1505   *
1506   * @param {string} type
1507   *        Type of event to clean up
1508   */
1509  function _cleanUpEvents(elem, type) {
1510    var data = getData(elem);
1511
1512    // Remove the events of a particular type if there are none left
1513    if (data.handlers[type].length === 0) {
1514      delete data.handlers[type];
1515      // data.handlers[type] = null;
1516      // Setting to null was causing an error with data.handlers
1517
1518      // Remove the meta-handler from the element
1519      if (elem.removeEventListener) {
1520        elem.removeEventListener(type, data.dispatcher, false);
1521      } else if (elem.detachEvent) {
1522        elem.detachEvent('on' + type, data.dispatcher);
1523      }
1524    }
1525
1526    // Remove the events object if there are no types left
1527    if (Object.getOwnPropertyNames(data.handlers).length <= 0) {
1528      delete data.handlers;
1529      delete data.dispatcher;
1530      delete data.disabled;
1531    }
1532
1533    // Finally remove the element data if there is no data left
1534    if (Object.getOwnPropertyNames(data).length === 0) {
1535      removeData(elem);
1536    }
1537  }
1538
1539  /**
1540   * Loops through an array of event types and calls the requested method for each type.
1541   *
1542   * @param {Function} fn
1543   *        The event method we want to use.
1544   *
1545   * @param {Element|Object} elem
1546   *        Element or object to bind listeners to
1547   *
1548   * @param {string} type
1549   *        Type of event to bind to.
1550   *
1551   * @param {EventTarget~EventListener} callback
1552   *        Event listener.
1553   */
1554  function _handleMultipleEvents(fn, elem, types, callback) {
1555    types.forEach(function (type) {
1556      // Call the event method for each one of the types
1557      fn(elem, type, callback);
1558    });
1559  }
1560
1561  /**
1562   * Fix a native event to have standard property values
1563   *
1564   * @param {Object} event
1565   *        Event object to fix.
1566   *
1567   * @return {Object}
1568   *         Fixed event object.
1569   */
1570  function fixEvent(event) {
1571
1572    function returnTrue() {
1573      return true;
1574    }
1575
1576    function returnFalse() {
1577      return false;
1578    }
1579
1580    // Test if fixing up is needed
1581    // Used to check if !event.stopPropagation instead of isPropagationStopped
1582    // But native events return true for stopPropagation, but don't have
1583    // other expected methods like isPropagationStopped. Seems to be a problem
1584    // with the Javascript Ninja code. So we're just overriding all events now.
1585    if (!event || !event.isPropagationStopped) {
1586      var old = event || window_1.event;
1587
1588      event = {};
1589      // Clone the old object so that we can modify the values event = {};
1590      // IE8 Doesn't like when you mess with native event properties
1591      // Firefox returns false for event.hasOwnProperty('type') and other props
1592      //  which makes copying more difficult.
1593      // TODO: Probably best to create a whitelist of event props
1594      for (var key in old) {
1595        // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y
1596        // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation
1597        // and webkitMovementX/Y
1598        if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && key !== 'webkitMovementX' && key !== 'webkitMovementY') {
1599          // Chrome 32+ warns if you try to copy deprecated returnValue, but
1600          // we still want to if preventDefault isn't supported (IE8).
1601          if (!(key === 'returnValue' && old.preventDefault)) {
1602            event[key] = old[key];
1603          }
1604        }
1605      }
1606
1607      // The event occurred on this element
1608      if (!event.target) {
1609        event.target = event.srcElement || document_1;
1610      }
1611
1612      // Handle which other element the event is related to
1613      if (!event.relatedTarget) {
1614        event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
1615      }
1616
1617      // Stop the default browser action
1618      event.preventDefault = function () {
1619        if (old.preventDefault) {
1620          old.preventDefault();
1621        }
1622        event.returnValue = false;
1623        old.returnValue = false;
1624        event.defaultPrevented = true;
1625      };
1626
1627      event.defaultPrevented = false;
1628
1629      // Stop the event from bubbling
1630      event.stopPropagation = function () {
1631        if (old.stopPropagation) {
1632          old.stopPropagation();
1633        }
1634        event.cancelBubble = true;
1635        old.cancelBubble = true;
1636        event.isPropagationStopped = returnTrue;
1637      };
1638
1639      event.isPropagationStopped = returnFalse;
1640
1641      // Stop the event from bubbling and executing other handlers
1642      event.stopImmediatePropagation = function () {
1643        if (old.stopImmediatePropagation) {
1644          old.stopImmediatePropagation();
1645        }
1646        event.isImmediatePropagationStopped = returnTrue;
1647        event.stopPropagation();
1648      };
1649
1650      event.isImmediatePropagationStopped = returnFalse;
1651
1652      // Handle mouse position
1653      if (event.clientX !== null && event.clientX !== undefined) {
1654        var doc = document_1.documentElement;
1655        var body = document_1.body;
1656
1657        event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
1658        event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
1659      }
1660
1661      // Handle key presses
1662      event.which = event.charCode || event.keyCode;
1663
1664      // Fix button for mouse clicks:
1665      // 0 == left; 1 == middle; 2 == right
1666      if (event.button !== null && event.button !== undefined) {
1667
1668        // The following is disabled because it does not pass videojs-standard
1669        // and... yikes.
1670        /* eslint-disable */
1671        event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0;
1672        /* eslint-enable */
1673      }
1674    }
1675
1676    // Returns fixed-up instance
1677    return event;
1678  }
1679
1680  /**
1681   * Whether passive event listeners are supported
1682   */
1683  var _supportsPassive = false;
1684
1685  (function () {
1686    try {
1687      var opts = Object.defineProperty({}, 'passive', {
1688        get: function get() {
1689          _supportsPassive = true;
1690        }
1691      });
1692
1693      window_1.addEventListener('test', null, opts);
1694      window_1.removeEventListener('test', null, opts);
1695    } catch (e) {
1696      // disregard
1697    }
1698  })();
1699
1700  /**
1701   * Touch events Chrome expects to be passive
1702   */
1703  var passiveEvents = ['touchstart', 'touchmove'];
1704
1705  /**
1706   * Add an event listener to element
1707   * It stores the handler function in a separate cache object
1708   * and adds a generic handler to the element's event,
1709   * along with a unique id (guid) to the element.
1710   *
1711   * @param {Element|Object} elem
1712   *        Element or object to bind listeners to
1713   *
1714   * @param {string|string[]} type
1715   *        Type of event to bind to.
1716   *
1717   * @param {EventTarget~EventListener} fn
1718   *        Event listener.
1719   */
1720  function on(elem, type, fn) {
1721    if (Array.isArray(type)) {
1722      return _handleMultipleEvents(on, elem, type, fn);
1723    }
1724
1725    var data = getData(elem);
1726
1727    // We need a place to store all our handler data
1728    if (!data.handlers) {
1729      data.handlers = {};
1730    }
1731
1732    if (!data.handlers[type]) {
1733      data.handlers[type] = [];
1734    }
1735
1736    if (!fn.guid) {
1737      fn.guid = newGUID();
1738    }
1739
1740    data.handlers[type].push(fn);
1741
1742    if (!data.dispatcher) {
1743      data.disabled = false;
1744
1745      data.dispatcher = function (event, hash) {
1746
1747        if (data.disabled) {
1748          return;
1749        }
1750
1751        event = fixEvent(event);
1752
1753        var handlers = data.handlers[event.type];
1754
1755        if (handlers) {
1756          // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
1757          var handlersCopy = handlers.slice(0);
1758
1759          for (var m = 0, n = handlersCopy.length; m < n; m++) {
1760            if (event.isImmediatePropagationStopped()) {
1761              break;
1762            } else {
1763              try {
1764                handlersCopy[m].call(elem, event, hash);
1765              } catch (e) {
1766                log$1.error(e);
1767              }
1768            }
1769          }
1770        }
1771      };
1772    }
1773
1774    if (data.handlers[type].length === 1) {
1775      if (elem.addEventListener) {
1776        var options = false;
1777
1778        if (_supportsPassive && passiveEvents.indexOf(type) > -1) {
1779          options = { passive: true };
1780        }
1781        elem.addEventListener(type, data.dispatcher, options);
1782      } else if (elem.attachEvent) {
1783        elem.attachEvent('on' + type, data.dispatcher);
1784      }
1785    }
1786  }
1787
1788  /**
1789   * Removes event listeners from an element
1790   *
1791   * @param {Element|Object} elem
1792   *        Object to remove listeners from.
1793   *
1794   * @param {string|string[]} [type]
1795   *        Type of listener to remove. Don't include to remove all events from element.
1796   *
1797   * @param {EventTarget~EventListener} [fn]
1798   *        Specific listener to remove. Don't include to remove listeners for an event
1799   *        type.
1800   */
1801  function off(elem, type, fn) {
1802    // Don't want to add a cache object through getElData if not needed
1803    if (!hasData(elem)) {
1804      return;
1805    }
1806
1807    var data = getData(elem);
1808
1809    // If no events exist, nothing to unbind
1810    if (!data.handlers) {
1811      return;
1812    }
1813
1814    if (Array.isArray(type)) {
1815      return _handleMultipleEvents(off, elem, type, fn);
1816    }
1817
1818    // Utility function
1819    var removeType = function removeType(el, t) {
1820      data.handlers[t] = [];
1821      _cleanUpEvents(el, t);
1822    };
1823
1824    // Are we removing all bound events?
1825    if (type === undefined) {
1826      for (var t in data.handlers) {
1827        if (Object.prototype.hasOwnProperty.call(data.handlers || {}, t)) {
1828          removeType(elem, t);
1829        }
1830      }
1831      return;
1832    }
1833
1834    var handlers = data.handlers[type];
1835
1836    // If no handlers exist, nothing to unbind
1837    if (!handlers) {
1838      return;
1839    }
1840
1841    // If no listener was provided, remove all listeners for type
1842    if (!fn) {
1843      removeType(elem, type);
1844      return;
1845    }
1846
1847    // We're only removing a single handler
1848    if (fn.guid) {
1849      for (var n = 0; n < handlers.length; n++) {
1850        if (handlers[n].guid === fn.guid) {
1851          handlers.splice(n--, 1);
1852        }
1853      }
1854    }
1855
1856    _cleanUpEvents(elem, type);
1857  }
1858
1859  /**
1860   * Trigger an event for an element
1861   *
1862   * @param {Element|Object} elem
1863   *        Element to trigger an event on
1864   *
1865   * @param {EventTarget~Event|string} event
1866   *        A string (the type) or an event object with a type attribute
1867   *
1868   * @param {Object} [hash]
1869   *        data hash to pass along with the event
1870   *
1871   * @return {boolean|undefined}
1872   *         - Returns the opposite of `defaultPrevented` if default was prevented
1873   *         - Otherwise returns undefined
1874   */
1875  function trigger(elem, event, hash) {
1876    // Fetches element data and a reference to the parent (for bubbling).
1877    // Don't want to add a data object to cache for every parent,
1878    // so checking hasElData first.
1879    var elemData = hasData(elem) ? getData(elem) : {};
1880    var parent = elem.parentNode || elem.ownerDocument;
1881    // type = event.type || event,
1882    // handler;
1883
1884    // If an event name was passed as a string, creates an event out of it
1885    if (typeof event === 'string') {
1886      event = { type: event, target: elem };
1887    } else if (!event.target) {
1888      event.target = elem;
1889    }
1890
1891    // Normalizes the event properties.
1892    event = fixEvent(event);
1893
1894    // If the passed element has a dispatcher, executes the established handlers.
1895    if (elemData.dispatcher) {
1896      elemData.dispatcher.call(elem, event, hash);
1897    }
1898
1899    // Unless explicitly stopped or the event does not bubble (e.g. media events)
1900    // recursively calls this function to bubble the event up the DOM.
1901    if (parent && !event.isPropagationStopped() && event.bubbles === true) {
1902      trigger.call(null, parent, event, hash);
1903
1904      // If at the top of the DOM, triggers the default action unless disabled.
1905    } else if (!parent && !event.defaultPrevented) {
1906      var targetData = getData(event.target);
1907
1908      // Checks if the target has a default action for this event.
1909      if (event.target[event.type]) {
1910        // Temporarily disables event dispatching on the target as we have already executed the handler.
1911        targetData.disabled = true;
1912        // Executes the default action.
1913        if (typeof event.target[event.type] === 'function') {
1914          event.target[event.type]();
1915        }
1916        // Re-enables event dispatching.
1917        targetData.disabled = false;
1918      }
1919    }
1920
1921    // Inform the triggerer if the default was prevented by returning false
1922    return !event.defaultPrevented;
1923  }
1924
1925  /**
1926   * Trigger a listener only once for an event
1927   *
1928   * @param {Element|Object} elem
1929   *        Element or object to bind to.
1930   *
1931   * @param {string|string[]} type
1932   *        Name/type of event
1933   *
1934   * @param {Event~EventListener} fn
1935   *        Event Listener function
1936   */
1937  function one(elem, type, fn) {
1938    if (Array.isArray(type)) {
1939      return _handleMultipleEvents(one, elem, type, fn);
1940    }
1941    var func = function func() {
1942      off(elem, type, func);
1943      fn.apply(this, arguments);
1944    };
1945
1946    // copy the guid to the new function so it can removed using the original function's ID
1947    func.guid = fn.guid = fn.guid || newGUID();
1948    on(elem, type, func);
1949  }
1950
1951  var Events = /*#__PURE__*/Object.freeze({
1952    fixEvent: fixEvent,
1953    on: on,
1954    off: off,
1955    trigger: trigger,
1956    one: one
1957  });
1958
1959  /**
1960   * @file setup.js - Functions for setting up a player without
1961   * user interaction based on the data-setup `attribute` of the video tag.
1962   *
1963   * @module setup
1964   */
1965
1966  var _windowLoaded = false;
1967  var videojs = void 0;
1968
1969  /**
1970   * Set up any tags that have a data-setup `attribute` when the player is started.
1971   */
1972  var autoSetup = function autoSetup() {
1973
1974    // Protect against breakage in non-browser environments and check global autoSetup option.
1975    if (!isReal() || videojs.options.autoSetup === false) {
1976      return;
1977    }
1978
1979    var vids = Array.prototype.slice.call(document_1.getElementsByTagName('video'));
1980    var audios = Array.prototype.slice.call(document_1.getElementsByTagName('audio'));
1981    var divs = Array.prototype.slice.call(document_1.getElementsByTagName('video-js'));
1982    var mediaEls = vids.concat(audios, divs);
1983
1984    // Check if any media elements exist
1985    if (mediaEls && mediaEls.length > 0) {
1986
1987      for (var i = 0, e = mediaEls.length; i < e; i++) {
1988        var mediaEl = mediaEls[i];
1989
1990        // Check if element exists, has getAttribute func.
1991        if (mediaEl && mediaEl.getAttribute) {
1992
1993          // Make sure this player hasn't already been set up.
1994          if (mediaEl.player === undefined) {
1995            var options = mediaEl.getAttribute('data-setup');
1996
1997            // Check if data-setup attr exists.
1998            // We only auto-setup if they've added the data-setup attr.
1999            if (options !== null) {
2000              // Create new video.js instance.
2001              videojs(mediaEl);
2002            }
2003          }
2004
2005          // If getAttribute isn't defined, we need to wait for the DOM.
2006        } else {
2007          autoSetupTimeout(1);
2008          break;
2009        }
2010      }
2011
2012      // No videos were found, so keep looping unless page is finished loading.
2013    } else if (!_windowLoaded) {
2014      autoSetupTimeout(1);
2015    }
2016  };
2017
2018  /**
2019   * Wait until the page is loaded before running autoSetup. This will be called in
2020   * autoSetup if `hasLoaded` returns false.
2021   *
2022   * @param {number} wait
2023   *        How long to wait in ms
2024   *
2025   * @param {module:videojs} [vjs]
2026   *        The videojs library function
2027   */
2028  function autoSetupTimeout(wait, vjs) {
2029    if (vjs) {
2030      videojs = vjs;
2031    }
2032
2033    window_1.setTimeout(autoSetup, wait);
2034  }
2035
2036  if (isReal() && document_1.readyState === 'complete') {
2037    _windowLoaded = true;
2038  } else {
2039    /**
2040     * Listen for the load event on window, and set _windowLoaded to true.
2041     *
2042     * @listens load
2043     */
2044    one(window_1, 'load', function () {
2045      _windowLoaded = true;
2046    });
2047  }
2048
2049  /**
2050   * @file stylesheet.js
2051   * @module stylesheet
2052   */
2053
2054  /**
2055   * Create a DOM syle element given a className for it.
2056   *
2057   * @param {string} className
2058   *        The className to add to the created style element.
2059   *
2060   * @return {Element}
2061   *         The element that was created.
2062   */
2063  var createStyleElement = function createStyleElement(className) {
2064    var style = document_1.createElement('style');
2065
2066    style.className = className;
2067
2068    return style;
2069  };
2070
2071  /**
2072   * Add text to a DOM element.
2073   *
2074   * @param {Element} el
2075   *        The Element to add text content to.
2076   *
2077   * @param {string} content
2078   *        The text to add to the element.
2079   */
2080  var setTextContent = function setTextContent(el, content) {
2081    if (el.styleSheet) {
2082      el.styleSheet.cssText = content;
2083    } else {
2084      el.textContent = content;
2085    }
2086  };
2087
2088  /**
2089   * @file fn.js
2090   * @module fn
2091   */
2092
2093  /**
2094   * Bind (a.k.a proxy or Context). A simple method for changing the context of a function
2095   * It also stores a unique id on the function so it can be easily removed from events.
2096   *
2097   * @param {Mixed} context
2098   *        The object to bind as scope.
2099   *
2100   * @param {Function} fn
2101   *        The function to be bound to a scope.
2102   *
2103   * @param {number} [uid]
2104   *        An optional unique ID for the function to be set
2105   *
2106   * @return {Function}
2107   *         The new function that will be bound into the context given
2108   */
2109  var bind = function bind(context, fn, uid) {
2110    // Make sure the function has a unique ID
2111    if (!fn.guid) {
2112      fn.guid = newGUID();
2113    }
2114
2115    // Create the new function that changes the context
2116    var bound = function bound() {
2117      return fn.apply(context, arguments);
2118    };
2119
2120    // Allow for the ability to individualize this function
2121    // Needed in the case where multiple objects might share the same prototype
2122    // IF both items add an event listener with the same function, then you try to remove just one
2123    // it will remove both because they both have the same guid.
2124    // when using this, you need to use the bind method when you remove the listener as well.
2125    // currently used in text tracks
2126    bound.guid = uid ? uid + '_' + fn.guid : fn.guid;
2127
2128    return bound;
2129  };
2130
2131  /**
2132   * Wraps the given function, `fn`, with a new function that only invokes `fn`
2133   * at most once per every `wait` milliseconds.
2134   *
2135   * @param  {Function} fn
2136   *         The function to be throttled.
2137   *
2138   * @param  {Number}   wait
2139   *         The number of milliseconds by which to throttle.
2140   *
2141   * @return {Function}
2142   */
2143  var throttle = function throttle(fn, wait) {
2144    var last = Date.now();
2145
2146    var throttled = function throttled() {
2147      var now = Date.now();
2148
2149      if (now - last >= wait) {
2150        fn.apply(undefined, arguments);
2151        last = now;
2152      }
2153    };
2154
2155    return throttled;
2156  };
2157
2158  /**
2159   * Creates a debounced function that delays invoking `func` until after `wait`
2160   * milliseconds have elapsed since the last time the debounced function was
2161   * invoked.
2162   *
2163   * Inspired by lodash and underscore implementations.
2164   *
2165   * @param  {Function} func
2166   *         The function to wrap with debounce behavior.
2167   *
2168   * @param  {number} wait
2169   *         The number of milliseconds to wait after the last invocation.
2170   *
2171   * @param  {boolean} [immediate]
2172   *         Whether or not to invoke the function immediately upon creation.
2173   *
2174   * @param  {Object} [context=window]
2175   *         The "context" in which the debounced function should debounce. For
2176   *         example, if this function should be tied to a Video.js player,
2177   *         the player can be passed here. Alternatively, defaults to the
2178   *         global `window` object.
2179   *
2180   * @return {Function}
2181   *         A debounced function.
2182   */
2183  var debounce = function debounce(func, wait, immediate) {
2184    var context = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : window_1;
2185
2186    var timeout = void 0;
2187
2188    /* eslint-disable consistent-this */
2189    return function () {
2190      var self = this;
2191      var args = arguments;
2192
2193      var _later = function later() {
2194        timeout = null;
2195        _later = null;
2196        if (!immediate) {
2197          func.apply(self, args);
2198        }
2199      };
2200
2201      if (!timeout && immediate) {
2202        func.apply(self, args);
2203      }
2204
2205      context.clearTimeout(timeout);
2206      timeout = context.setTimeout(_later, wait);
2207    };
2208    /* eslint-enable consistent-this */
2209  };
2210
2211  /**
2212   * @file src/js/event-target.js
2213   */
2214
2215  /**
2216   * `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It
2217   * adds shorthand functions that wrap around lengthy functions. For example:
2218   * the `on` function is a wrapper around `addEventListener`.
2219   *
2220   * @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget}
2221   * @class EventTarget
2222   */
2223  var EventTarget = function EventTarget() {};
2224
2225  /**
2226   * A Custom DOM event.
2227   *
2228   * @typedef {Object} EventTarget~Event
2229   * @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent}
2230   */
2231
2232  /**
2233   * All event listeners should follow the following format.
2234   *
2235   * @callback EventTarget~EventListener
2236   * @this {EventTarget}
2237   *
2238   * @param {EventTarget~Event} event
2239   *        the event that triggered this function
2240   *
2241   * @param {Object} [hash]
2242   *        hash of data sent during the event
2243   */
2244
2245  /**
2246   * An object containing event names as keys and booleans as values.
2247   *
2248   * > NOTE: If an event name is set to a true value here {@link EventTarget#trigger}
2249   *         will have extra functionality. See that function for more information.
2250   *
2251   * @property EventTarget.prototype.allowedEvents_
2252   * @private
2253   */
2254  EventTarget.prototype.allowedEvents_ = {};
2255
2256  /**
2257   * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a
2258   * function that will get called when an event with a certain name gets triggered.
2259   *
2260   * @param {string|string[]} type
2261   *        An event name or an array of event names.
2262   *
2263   * @param {EventTarget~EventListener} fn
2264   *        The function to call with `EventTarget`s
2265   */
2266  EventTarget.prototype.on = function (type, fn) {
2267    // Remove the addEventListener alias before calling Events.on
2268    // so we don't get into an infinite type loop
2269    var ael = this.addEventListener;
2270
2271    this.addEventListener = function () {};
2272    on(this, type, fn);
2273    this.addEventListener = ael;
2274  };
2275
2276  /**
2277   * An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic
2278   * the standard DOM API.
2279   *
2280   * @function
2281   * @see {@link EventTarget#on}
2282   */
2283  EventTarget.prototype.addEventListener = EventTarget.prototype.on;
2284
2285  /**
2286   * Removes an `event listener` for a specific event from an instance of `EventTarget`.
2287   * This makes it so that the `event listener` will no longer get called when the
2288   * named event happens.
2289   *
2290   * @param {string|string[]} type
2291   *        An event name or an array of event names.
2292   *
2293   * @param {EventTarget~EventListener} fn
2294   *        The function to remove.
2295   */
2296  EventTarget.prototype.off = function (type, fn) {
2297    off(this, type, fn);
2298  };
2299
2300  /**
2301   * An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic
2302   * the standard DOM API.
2303   *
2304   * @function
2305   * @see {@link EventTarget#off}
2306   */
2307  EventTarget.prototype.removeEventListener = EventTarget.prototype.off;
2308
2309  /**
2310   * This function will add an `event listener` that gets triggered only once. After the
2311   * first trigger it will get removed. This is like adding an `event listener`
2312   * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself.
2313   *
2314   * @param {string|string[]} type
2315   *        An event name or an array of event names.
2316   *
2317   * @param {EventTarget~EventListener} fn
2318   *        The function to be called once for each event name.
2319   */
2320  EventTarget.prototype.one = function (type, fn) {
2321    // Remove the addEventListener alialing Events.on
2322    // so we don't get into an infinite type loop
2323    var ael = this.addEventListener;
2324
2325    this.addEventListener = function () {};
2326    one(this, type, fn);
2327    this.addEventListener = ael;
2328  };
2329
2330  /**
2331   * This function causes an event to happen. This will then cause any `event listeners`
2332   * that are waiting for that event, to get called. If there are no `event listeners`
2333   * for an event then nothing will happen.
2334   *
2335   * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`.
2336   * Trigger will also call the `on` + `uppercaseEventName` function.
2337   *
2338   * Example:
2339   * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call
2340   * `onClick` if it exists.
2341   *
2342   * @param {string|EventTarget~Event|Object} event
2343   *        The name of the event, an `Event`, or an object with a key of type set to
2344   *        an event name.
2345   */
2346  EventTarget.prototype.trigger = function (event) {
2347    var type = event.type || event;
2348
2349    if (typeof event === 'string') {
2350      event = { type: type };
2351    }
2352    event = fixEvent(event);
2353
2354    if (this.allowedEvents_[type] && this['on' + type]) {
2355      this['on' + type](event);
2356    }
2357
2358    trigger(this, event);
2359  };
2360
2361  /**
2362   * An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic
2363   * the standard DOM API.
2364   *
2365   * @function
2366   * @see {@link EventTarget#trigger}
2367   */
2368  EventTarget.prototype.dispatchEvent = EventTarget.prototype.trigger;
2369
2370  /**
2371   * @file mixins/evented.js
2372   * @module evented
2373   */
2374
2375  /**
2376   * Returns whether or not an object has had the evented mixin applied.
2377   *
2378   * @param  {Object} object
2379   *         An object to test.
2380   *
2381   * @return {boolean}
2382   *         Whether or not the object appears to be evented.
2383   */
2384  var isEvented = function isEvented(object) {
2385    return object instanceof EventTarget || !!object.eventBusEl_ && ['on', 'one', 'off', 'trigger'].every(function (k) {
2386      return typeof object[k] === 'function';
2387    });
2388  };
2389
2390  /**
2391   * Whether a value is a valid event type - non-empty string or array.
2392   *
2393   * @private
2394   * @param  {string|Array} type
2395   *         The type value to test.
2396   *
2397   * @return {boolean}
2398   *         Whether or not the type is a valid event type.
2399   */
2400  var isValidEventType = function isValidEventType(type) {
2401    return (
2402      // The regex here verifies that the `type` contains at least one non-
2403      // whitespace character.
2404      typeof type === 'string' && /\S/.test(type) || Array.isArray(type) && !!type.length
2405    );
2406  };
2407
2408  /**
2409   * Validates a value to determine if it is a valid event target. Throws if not.
2410   *
2411   * @private
2412   * @throws {Error}
2413   *         If the target does not appear to be a valid event target.
2414   *
2415   * @param  {Object} target
2416   *         The object to test.
2417   */
vendor: 6,324 bytes, lines 2418-2611
2418  var validateTarget = function validateTarget(target) {
2419    if (!target.nodeName && !isEvented(target)) {
2420      throw new Error('Invalid target; must be a DOM node or evented object.');
2421    }
2422  };
2423
2424  /**
2425   * Validates a value to determine if it is a valid event target. Throws if not.
2426   *
2427   * @private
2428   * @throws {Error}
2429   *         If the type does not appear to be a valid event type.
2430   *
2431   * @param  {string|Array} type
2432   *         The type to test.
2433   */
2434  var validateEventType = function validateEventType(type) {
2435    if (!isValidEventType(type)) {
2436      throw new Error('Invalid event type; must be a non-empty string or array.');
2437    }
2438  };
2439
2440  /**
2441   * Validates a value to determine if it is a valid listener. Throws if not.
2442   *
2443   * @private
2444   * @throws {Error}
2445   *         If the listener is not a function.
2446   *
2447   * @param  {Function} listener
2448   *         The listener to test.
2449   */
2450  var validateListener = function validateListener(listener) {
2451    if (typeof listener !== 'function') {
2452      throw new Error('Invalid listener; must be a function.');
2453    }
2454  };
2455
2456  /**
2457   * Takes an array of arguments given to `on()` or `one()`, validates them, and
2458   * normalizes them into an object.
2459   *
2460   * @private
2461   * @param  {Object} self
2462   *         The evented object on which `on()` or `one()` was called. This
2463   *         object will be bound as the `this` value for the listener.
2464   *
2465   * @param  {Array} args
2466   *         An array of arguments passed to `on()` or `one()`.
2467   *
2468   * @return {Object}
2469   *         An object containing useful values for `on()` or `one()` calls.
2470   */
2471  var normalizeListenArgs = function normalizeListenArgs(self, args) {
2472
2473    // If the number of arguments is less than 3, the target is always the
2474    // evented object itself.
2475    var isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_;
2476    var target = void 0;
2477    var type = void 0;
2478    var listener = void 0;
2479
2480    if (isTargetingSelf) {
2481      target = self.eventBusEl_;
2482
2483      // Deal with cases where we got 3 arguments, but we are still listening to
2484      // the evented object itself.
2485      if (args.length >= 3) {
2486        args.shift();
2487      }
2488
2489      type = args[0];
2490      listener = args[1];
2491    } else {
2492      target = args[0];
2493      type = args[1];
2494      listener = args[2];
2495    }
2496
2497    validateTarget(target);
2498    validateEventType(type);
2499    validateListener(listener);
2500
2501    listener = bind(self, listener);
2502
2503    return { isTargetingSelf: isTargetingSelf, target: target, type: type, listener: listener };
2504  };
2505
2506  /**
2507   * Adds the listener to the event type(s) on the target, normalizing for
2508   * the type of target.
2509   *
2510   * @private
2511   * @param  {Element|Object} target
2512   *         A DOM node or evented object.
2513   *
2514   * @param  {string} method
2515   *         The event binding method to use ("on" or "one").
2516   *
2517   * @param  {string|Array} type
2518   *         One or more event type(s).
2519   *
2520   * @param  {Function} listener
2521   *         A listener function.
2522   */
2523  var listen = function listen(target, method, type, listener) {
2524    validateTarget(target);
2525
2526    if (target.nodeName) {
2527      Events[method](target, type, listener);
2528    } else {
2529      target[method](type, listener);
2530    }
2531  };
2532
2533  /**
2534   * Contains methods that provide event capabilities to an object which is passed
2535   * to {@link module:evented|evented}.
2536   *
2537   * @mixin EventedMixin
2538   */
2539  var EventedMixin = {
2540
2541    /**
2542     * Add a listener to an event (or events) on this object or another evented
2543     * object.
2544     *
2545     * @param  {string|Array|Element|Object} targetOrType
2546     *         If this is a string or array, it represents the event type(s)
2547     *         that will trigger the listener.
2548     *
2549     *         Another evented object can be passed here instead, which will
2550     *         cause the listener to listen for events on _that_ object.
2551     *
2552     *         In either case, the listener's `this` value will be bound to
2553     *         this object.
2554     *
2555     * @param  {string|Array|Function} typeOrListener
2556     *         If the first argument was a string or array, this should be the
2557     *         listener function. Otherwise, this is a string or array of event
2558     *         type(s).
2559     *
2560     * @param  {Function} [listener]
2561     *         If the first argument was another evented object, this will be
2562     *         the listener function.
2563     */
2564    on: function on$$1() {
2565      var _this = this;
2566
2567      for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
2568        args[_key] = arguments[_key];
2569      }
2570
2571      var _normalizeListenArgs = normalizeListenArgs(this, args),
2572          isTargetingSelf = _normalizeListenArgs.isTargetingSelf,
2573          target = _normalizeListenArgs.target,
2574          type = _normalizeListenArgs.type,
2575          listener = _normalizeListenArgs.listener;
2576
2577      listen(target, 'on', type, listener);
2578
2579      // If this object is listening to another evented object.
2580      if (!isTargetingSelf) {
2581
2582        // If this object is disposed, remove the listener.
2583        var removeListenerOnDispose = function removeListenerOnDispose() {
2584          return _this.off(target, type, listener);
2585        };
2586
2587        // Use the same function ID as the listener so we can remove it later it
2588        // using the ID of the original listener.
2589        removeListenerOnDispose.guid = listener.guid;
2590
2591        // Add a listener to the target's dispose event as well. This ensures
2592        // that if the target is disposed BEFORE this object, we remove the
2593        // removal listener that was just added. Otherwise, we create a memory leak.
2594        var removeRemoverOnTargetDispose = function removeRemoverOnTargetDispose() {
2595          return _this.off('dispose', removeListenerOnDispose);
2596        };
2597
2598        // Use the same function ID as the listener so we can remove it later
2599        // it using the ID of the original listener.
2600        removeRemoverOnTargetDispose.guid = listener.guid;
2601
2602        listen(this, 'on', 'dispose', removeListenerOnDispose);
2603        listen(target, 'on', 'dispose', removeRemoverOnTargetDispose);
2604      }
2605    },
2606
2607
2608    /**
2609     * Add a listener to an event (or events) on this object or another evented
2610     * object. The listener will only be called once and then removed.
2611     *
2612     * @param  {string|Array|Element|Object} targetOrType
2613     *         If this is a string or array, it represents the event type(s)
2614     *         that will trigger the listener.
2615     *
2616     *         Another evented object can be passed here instead, which will
2617     *         cause the listener to listen for events on _that_ object.
2618     *
2619     *         In either case, the listener's `this` value will be bound to
2620     *         this object.
2621     *
2622     * @param  {string|Array|Function} typeOrListener
2623     *         If the first argument was a string or array, this should be the
2624     *         listener function. Otherwise, this is a string or array of event
2625     *         type(s).
2626     *
2627     * @param  {Function} [listener]
2628     *         If the first argument was another evented object, this will be
2629     *         the listener function.
2630     */
2631    one: function one$$1() {
2632      var _this2 = this;
2633
2634      for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
2635        args[_key2] = arguments[_key2];
2636      }
2637
2638      var _normalizeListenArgs2 = normalizeListenArgs(this, args),
2639          isTargetingSelf = _normalizeListenArgs2.isTargetingSelf,
2640          target = _normalizeListenArgs2.target,
2641          type = _normalizeListenArgs2.type,
2642          listener = _normalizeListenArgs2.listener;
2643
2644      // Targeting this evented object.
2645
2646
2647      if (isTargetingSelf) {
2648        listen(target, 'one', type, listener);
2649
2650        // Targeting another evented object.
2651      } else {
2652        var wrapper = function wrapper() {
2653          for (var _len3 = arguments.length, largs = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
2654            largs[_key3] = arguments[_key3];
2655          }
2656
2657          _this2.off(target, type, wrapper);
2658          listener.apply(null, largs);
2659        };
2660
2661        // Use the same function ID as the listener so we can remove it later
2662        // it using the ID of the original listener.
2663        wrapper.guid = listener.guid;
2664        listen(target, 'one', type, wrapper);
2665      }
2666    },
2667
2668
2669    /**
2670     * Removes listener(s) from event(s) on an evented object.
2671     *
2672     * @param  {string|Array|Element|Object} [targetOrType]
2673     *         If this is a string or array, it represents the event type(s).
2674     *
2675     *         Another evented object can be passed here instead, in which case
2676     *         ALL 3 arguments are _required_.
2677     *
2678     * @param  {string|Array|Function} [typeOrListener]
2679     *         If the first argument was a string or array, this may be the
2680     *         listener function. Otherwise, this is a string or array of event
2681     *         type(s).
2682     *
2683     * @param  {Function} [listener]
2684     *         If the first argument was another evented object, this will be
2685     *         the listener function; otherwise, _all_ listeners bound to the
2686     *         event type(s) will be removed.
2687     */
2688    off: function off$$1(targetOrType, typeOrListener, listener) {
2689
2690      // Targeting this evented object.
2691      if (!targetOrType || isValidEventType(targetOrType)) {
2692        off(this.eventBusEl_, targetOrType, typeOrListener);
2693
2694        // Targeting another evented object.
2695      } else {
2696        var target = targetOrType;
2697        var type = typeOrListener;
2698
2699        // Fail fast and in a meaningful way!
2700        validateTarget(target);
2701        validateEventType(type);
2702        validateListener(listener);
2703
2704        // Ensure there's at least a guid, even if the function hasn't been used
2705        listener = bind(this, listener);
2706
2707        // Remove the dispose listener on this evented object, which was given
2708        // the same guid as the event listener in on().
2709        this.off('dispose', listener);
2710
2711        if (target.nodeName) {
2712          off(target, type, listener);
2713          off(target, 'dispose', listener);
2714        } else if (isEvented(target)) {
2715          target.off(type, listener);
2716          target.off('dispose', listener);
2717        }
2718      }
2719    },
2720
2721
2722    /**
2723     * Fire an event on this evented object, causing its listeners to be called.
2724     *
2725     * @param   {string|Object} event
2726     *          An event type or an object with a type property.
2727     *
2728     * @param   {Object} [hash]
2729     *          An additional object to pass along to listeners.
2730     *
2731     * @returns {boolean}
2732     *          Whether or not the default behavior was prevented.
2733     */
2734    trigger: function trigger$$1(event, hash) {
2735      return trigger(this.eventBusEl_, event, hash);
2736    }
2737  };
2738
2739  /**
2740   * Applies {@link module:evented~EventedMixin|EventedMixin} to a target object.
2741   *
2742   * @param  {Object} target
2743   *         The object to which to add event methods.
2744   *
2745   * @param  {Object} [options={}]
2746   *         Options for customizing the mixin behavior.
2747   *
2748   * @param  {String} [options.eventBusKey]
2749   *         By default, adds a `eventBusEl_` DOM element to the target object,
2750   *         which is used as an event bus. If the target object already has a
2751   *         DOM element that should be used, pass its key here.
2752   *
2753   * @return {Object}
2754   *         The target object.
2755   */
2756  function evented(target) {
2757    var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
2758    var eventBusKey = options.eventBusKey;
2759
2760    // Set or create the eventBusEl_.
2761
vendor: 50,457 bytes, lines 2762-4373
2762    if (eventBusKey) {
2763      if (!target[eventBusKey].nodeName) {
2764        throw new Error('The eventBusKey "' + eventBusKey + '" does not refer to an element.');
2765      }
2766      target.eventBusEl_ = target[eventBusKey];
2767    } else {
2768      target.eventBusEl_ = createEl('span', { className: 'vjs-event-bus' });
2769    }
2770
2771    assign(target, EventedMixin);
2772
2773    // When any evented object is disposed, it removes all its listeners.
2774    target.on('dispose', function () {
2775      target.off();
2776      window_1.setTimeout(function () {
2777        target.eventBusEl_ = null;
2778      }, 0);
2779    });
2780
2781    return target;
2782  }
2783
2784  /**
2785   * @file mixins/stateful.js
2786   * @module stateful
2787   */
2788
2789  /**
2790   * Contains methods that provide statefulness to an object which is passed
2791   * to {@link module:stateful}.
2792   *
2793   * @mixin StatefulMixin
2794   */
2795  var StatefulMixin = {
2796
2797    /**
2798     * A hash containing arbitrary keys and values representing the state of
2799     * the object.
2800     *
2801     * @type {Object}
2802     */
2803    state: {},
2804
2805    /**
2806     * Set the state of an object by mutating its
2807     * {@link module:stateful~StatefulMixin.state|state} object in place.
2808     *
2809     * @fires   module:stateful~StatefulMixin#statechanged
2810     * @param   {Object|Function} stateUpdates
2811     *          A new set of properties to shallow-merge into the plugin state.
2812     *          Can be a plain object or a function returning a plain object.
2813     *
2814     * @returns {Object|undefined}
2815     *          An object containing changes that occurred. If no changes
2816     *          occurred, returns `undefined`.
2817     */
2818    setState: function setState(stateUpdates) {
2819      var _this = this;
2820
2821      // Support providing the `stateUpdates` state as a function.
2822      if (typeof stateUpdates === 'function') {
2823        stateUpdates = stateUpdates();
2824      }
2825
2826      var changes = void 0;
2827
2828      each(stateUpdates, function (value, key) {
2829
2830        // Record the change if the value is different from what's in the
2831        // current state.
2832        if (_this.state[key] !== value) {
2833          changes = changes || {};
2834          changes[key] = {
2835            from: _this.state[key],
2836            to: value
2837          };
2838        }
2839
2840        _this.state[key] = value;
2841      });
2842
2843      // Only trigger "statechange" if there were changes AND we have a trigger
2844      // function. This allows us to not require that the target object be an
2845      // evented object.
2846      if (changes && isEvented(this)) {
2847
2848        /**
2849         * An event triggered on an object that is both
2850         * {@link module:stateful|stateful} and {@link module:evented|evented}
2851         * indicating that its state has changed.
2852         *
2853         * @event    module:stateful~StatefulMixin#statechanged
2854         * @type     {Object}
2855         * @property {Object} changes
2856         *           A hash containing the properties that were changed and
2857         *           the values they were changed `from` and `to`.
2858         */
2859        this.trigger({
2860          changes: changes,
2861          type: 'statechanged'
2862        });
2863      }
2864
2865      return changes;
2866    }
2867  };
2868
2869  /**
2870   * Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target
2871   * object.
2872   *
2873   * If the target object is {@link module:evented|evented} and has a
2874   * `handleStateChanged` method, that method will be automatically bound to the
2875   * `statechanged` event on itself.
2876   *
2877   * @param   {Object} target
2878   *          The object to be made stateful.
2879   *
2880   * @param   {Object} [defaultState]
2881   *          A default set of properties to populate the newly-stateful object's
2882   *          `state` property.
2883   *
2884   * @returns {Object}
2885   *          Returns the `target`.
2886   */
2887  function stateful(target, defaultState) {
2888    assign(target, StatefulMixin);
2889
2890    // This happens after the mixing-in because we need to replace the `state`
2891    // added in that step.
2892    target.state = assign({}, target.state, defaultState);
2893
2894    // Auto-bind the `handleStateChanged` method of the target object if it exists.
2895    if (typeof target.handleStateChanged === 'function' && isEvented(target)) {
2896      target.on('statechanged', target.handleStateChanged);
2897    }
2898
2899    return target;
2900  }
2901
2902  /**
2903   * @file to-title-case.js
2904   * @module to-title-case
2905   */
2906
2907  /**
2908   * Uppercase the first letter of a string.
2909   *
2910   * @param {string} string
2911   *        String to be uppercased
2912   *
2913   * @return {string}
2914   *         The string with an uppercased first letter
2915   */
2916  function toTitleCase(string) {
2917    if (typeof string !== 'string') {
2918      return string;
2919    }
2920
2921    return string.charAt(0).toUpperCase() + string.slice(1);
2922  }
2923
2924  /**
2925   * Compares the TitleCase versions of the two strings for equality.
2926   *
2927   * @param {string} str1
2928   *        The first string to compare
2929   *
2930   * @param {string} str2
2931   *        The second string to compare
2932   *
2933   * @return {boolean}
2934   *         Whether the TitleCase versions of the strings are equal
2935   */
2936  function titleCaseEquals(str1, str2) {
2937    return toTitleCase(str1) === toTitleCase(str2);
2938  }
2939
2940  /**
2941   * @file merge-options.js
2942   * @module merge-options
2943   */
2944
2945  /**
2946   * Deep-merge one or more options objects, recursively merging **only** plain
2947   * object properties.
2948   *
2949   * @param   {Object[]} sources
2950   *          One or more objects to merge into a new object.
2951   *
2952   * @returns {Object}
2953   *          A new object that is the merged result of all sources.
2954   */
2955  function mergeOptions() {
2956    var result = {};
2957
2958    for (var _len = arguments.length, sources = Array(_len), _key = 0; _key < _len; _key++) {
2959      sources[_key] = arguments[_key];
2960    }
2961
2962    sources.forEach(function (source) {
2963      if (!source) {
2964        return;
2965      }
2966
2967      each(source, function (value, key) {
2968        if (!isPlain(value)) {
2969          result[key] = value;
2970          return;
2971        }
2972
2973        if (!isPlain(result[key])) {
2974          result[key] = {};
2975        }
2976
2977        result[key] = mergeOptions(result[key], value);
2978      });
2979    });
2980
2981    return result;
2982  }
2983
2984  /**
2985   * Player Component - Base class for all UI objects
2986   *
2987   * @file component.js
2988   */
2989
2990  /**
2991   * Base class for all UI Components.
2992   * Components are UI objects which represent both a javascript object and an element
2993   * in the DOM. They can be children of other components, and can have
2994   * children themselves.
2995   *
2996   * Components can also use methods from {@link EventTarget}
2997   */
2998
2999  var Component = function () {
3000
3001    /**
3002     * A callback that is called when a component is ready. Does not have any
3003     * paramters and any callback value will be ignored.
3004     *
3005     * @callback Component~ReadyCallback
3006     * @this Component
3007     */
3008
3009    /**
3010     * Creates an instance of this class.
3011     *
3012     * @param {Player} player
3013     *        The `Player` that this class should be attached to.
3014     *
3015     * @param {Object} [options]
3016     *        The key/value store of player options.
3017     *
3018     * @param {Object[]} [options.children]
3019     *        An array of children objects to intialize this component with. Children objects have
3020     *        a name property that will be used if more than one component of the same type needs to be
3021     *        added.
3022     *
3023     * @param {Component~ReadyCallback} [ready]
3024     *        Function that gets called when the `Component` is ready.
3025     */
3026    function Component(player, options, ready) {
3027      classCallCheck(this, Component);
3028
3029
3030      // The component might be the player itself and we can't pass `this` to super
3031      if (!player && this.play) {
3032        this.player_ = player = this; // eslint-disable-line
3033      } else {
3034        this.player_ = player;
3035      }
3036
3037      // Make a copy of prototype.options_ to protect against overriding defaults
3038      this.options_ = mergeOptions({}, this.options_);
3039
3040      // Updated options with supplied options
3041      options = this.options_ = mergeOptions(this.options_, options);
3042
3043      // Get ID from options or options element if one is supplied
3044      this.id_ = options.id || options.el && options.el.id;
3045
3046      // If there was no ID from the options, generate one
3047      if (!this.id_) {
3048        // Don't require the player ID function in the case of mock players
3049        var id = player && player.id && player.id() || 'no_player';
3050
3051        this.id_ = id + '_component_' + newGUID();
3052      }
3053
3054      this.name_ = options.name || null;
3055
3056      // Create element if one wasn't provided in options
3057      if (options.el) {
3058        this.el_ = options.el;
3059      } else if (options.createEl !== false) {
3060        this.el_ = this.createEl();
3061      }
3062
3063      // if evented is anything except false, we want to mixin in evented
3064      if (options.evented !== false) {
3065        // Make this an evented object and use `el_`, if available, as its event bus
3066        evented(this, { eventBusKey: this.el_ ? 'el_' : null });
3067      }
3068      stateful(this, this.constructor.defaultState);
3069
3070      this.children_ = [];
3071      this.childIndex_ = {};
3072      this.childNameIndex_ = {};
3073
3074      // Add any child components in options
3075      if (options.initChildren !== false) {
3076        this.initChildren();
3077      }
3078
3079      this.ready(ready);
3080      // Don't want to trigger ready here or it will before init is actually
3081      // finished for all children that run this constructor
3082
3083      if (options.reportTouchActivity !== false) {
3084        this.enableTouchActivity();
3085      }
3086    }
3087
3088    /**
3089     * Dispose of the `Component` and all child components.
3090     *
3091     * @fires Component#dispose
3092     */
3093
3094
3095    Component.prototype.dispose = function dispose() {
3096
3097      /**
3098       * Triggered when a `Component` is disposed.
3099       *
3100       * @event Component#dispose
3101       * @type {EventTarget~Event}
3102       *
3103       * @property {boolean} [bubbles=false]
3104       *           set to false so that the close event does not
3105       *           bubble up
3106       */
3107      this.trigger({ type: 'dispose', bubbles: false });
3108
3109      // Dispose all children.
3110      if (this.children_) {
3111        for (var i = this.children_.length - 1; i >= 0; i--) {
3112          if (this.children_[i].dispose) {
3113            this.children_[i].dispose();
3114          }
3115        }
3116      }
3117
3118      // Delete child references
3119      this.children_ = null;
3120      this.childIndex_ = null;
3121      this.childNameIndex_ = null;
3122
3123      if (this.el_) {
3124        // Remove element from DOM
3125        if (this.el_.parentNode) {
3126          this.el_.parentNode.removeChild(this.el_);
3127        }
3128
3129        removeData(this.el_);
3130        this.el_ = null;
3131      }
3132
3133      // remove reference to the player after disposing of the element
3134      this.player_ = null;
3135    };
3136
3137    /**
3138     * Return the {@link Player} that the `Component` has attached to.
3139     *
3140     * @return {Player}
3141     *         The player that this `Component` has attached to.
3142     */
3143
3144
3145    Component.prototype.player = function player() {
3146      return this.player_;
3147    };
3148
3149    /**
3150     * Deep merge of options objects with new options.
3151     * > Note: When both `obj` and `options` contain properties whose values are objects.
3152     *         The two properties get merged using {@link module:mergeOptions}
3153     *
3154     * @param {Object} obj
3155     *        The object that contains new options.
3156     *
3157     * @return {Object}
3158     *         A new object of `this.options_` and `obj` merged together.
3159     *
3160     * @deprecated since version 5
3161     */
3162
3163
3164    Component.prototype.options = function options(obj) {
3165      log$1.warn('this.options() has been deprecated and will be moved to the constructor in 6.0');
3166
3167      if (!obj) {
3168        return this.options_;
3169      }
3170
3171      this.options_ = mergeOptions(this.options_, obj);
3172      return this.options_;
3173    };
3174
3175    /**
3176     * Get the `Component`s DOM element
3177     *
3178     * @return {Element}
3179     *         The DOM element for this `Component`.
3180     */
3181
3182
3183    Component.prototype.el = function el() {
3184      return this.el_;
3185    };
3186
3187    /**
3188     * Create the `Component`s DOM element.
3189     *
3190     * @param {string} [tagName]
3191     *        Element's DOM node type. e.g. 'div'
3192     *
3193     * @param {Object} [properties]
3194     *        An object of properties that should be set.
3195     *
3196     * @param {Object} [attributes]
3197     *        An object of attributes that should be set.
3198     *
3199     * @return {Element}
3200     *         The element that gets created.
3201     */
3202
3203
3204    Component.prototype.createEl = function createEl$$1(tagName, properties, attributes) {
3205      return createEl(tagName, properties, attributes);
3206    };
3207
3208    /**
3209     * Localize a string given the string in english.
3210     *
3211     * If tokens are provided, it'll try and run a simple token replacement on the provided string.
3212     * The tokens it looks for look like `{1}` with the index being 1-indexed into the tokens array.
3213     *
3214     * If a `defaultValue` is provided, it'll use that over `string`,
3215     * if a value isn't found in provided language files.
3216     * This is useful if you want to have a descriptive key for token replacement
3217     * but have a succinct localized string and not require `en.json` to be included.
3218     *
3219     * Currently, it is used for the progress bar timing.
3220     * ```js
3221     * {
3222     *   "progress bar timing: currentTime={1} duration={2}": "{1} of {2}"
3223     * }
3224     * ```
3225     * It is then used like so:
3226     * ```js
3227     * this.localize('progress bar timing: currentTime={1} duration{2}',
3228     *               [this.player_.currentTime(), this.player_.duration()],
3229     *               '{1} of {2}');
3230     * ```
3231     *
3232     * Which outputs something like: `01:23 of 24:56`.
3233     *
3234     *
3235     * @param {string} string
3236     *        The string to localize and the key to lookup in the language files.
3237     * @param {string[]} [tokens]
3238     *        If the current item has token replacements, provide the tokens here.
3239     * @param {string} [defaultValue]
3240     *        Defaults to `string`. Can be a default value to use for token replacement
3241     *        if the lookup key is needed to be separate.
3242     *
3243     * @return {string}
3244     *         The localized string or if no localization exists the english string.
3245     */
3246
3247
3248    Component.prototype.localize = function localize(string, tokens) {
3249      var defaultValue = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : string;
3250
3251      var code = this.player_.language && this.player_.language();
3252      var languages = this.player_.languages && this.player_.languages();
3253      var language = languages && languages[code];
3254      var primaryCode = code && code.split('-')[0];
3255      var primaryLang = languages && languages[primaryCode];
3256
3257      var localizedString = defaultValue;
3258
3259      if (language && language[string]) {
3260        localizedString = language[string];
3261      } else if (primaryLang && primaryLang[string]) {
3262        localizedString = primaryLang[string];
3263      }
3264
3265      if (tokens) {
3266        localizedString = localizedString.replace(/\{(\d+)\}/g, function (match, index) {
3267          var value = tokens[index - 1];
3268          var ret = value;
3269
3270          if (typeof value === 'undefined') {
3271            ret = match;
3272          }
3273
3274          return ret;
3275        });
3276      }
3277
3278      return localizedString;
3279    };
3280
3281    /**
3282     * Return the `Component`s DOM element. This is where children get inserted.
3283     * This will usually be the the same as the element returned in {@link Component#el}.
3284     *
3285     * @return {Element}
3286     *         The content element for this `Component`.
3287     */
3288
3289
3290    Component.prototype.contentEl = function contentEl() {
3291      return this.contentEl_ || this.el_;
3292    };
3293
3294    /**
3295     * Get this `Component`s ID
3296     *
3297     * @return {string}
3298     *         The id of this `Component`
3299     */
3300
3301
3302    Component.prototype.id = function id() {
3303      return this.id_;
3304    };
3305
3306    /**
3307     * Get the `Component`s name. The name gets used to reference the `Component`
3308     * and is set during registration.
3309     *
3310     * @return {string}
3311     *         The name of this `Component`.
3312     */
3313
3314
3315    Component.prototype.name = function name() {
3316      return this.name_;
3317    };
3318
3319    /**
3320     * Get an array of all child components
3321     *
3322     * @return {Array}
3323     *         The children
3324     */
3325
3326
3327    Component.prototype.children = function children() {
3328      return this.children_;
3329    };
3330
3331    /**
3332     * Returns the child `Component` with the given `id`.
3333     *
3334     * @param {string} id
3335     *        The id of the child `Component` to get.
3336     *
3337     * @return {Component|undefined}
3338     *         The child `Component` with the given `id` or undefined.
3339     */
3340
3341
3342    Component.prototype.getChildById = function getChildById(id) {
3343      return this.childIndex_[id];
3344    };
3345
3346    /**
3347     * Returns the child `Component` with the given `name`.
3348     *
3349     * @param {string} name
3350     *        The name of the child `Component` to get.
3351     *
3352     * @return {Component|undefined}
3353     *         The child `Component` with the given `name` or undefined.
3354     */
3355
3356
3357    Component.prototype.getChild = function getChild(name) {
3358      if (!name) {
3359        return;
3360      }
3361
3362      name = toTitleCase(name);
3363
3364      return this.childNameIndex_[name];
3365    };
3366
3367    /**
3368     * Add a child `Component` inside the current `Component`.
3369     *
3370     *
3371     * @param {string|Component} child
3372     *        The name or instance of a child to add.
3373     *
3374     * @param {Object} [options={}]
3375     *        The key/value store of options that will get passed to children of
3376     *        the child.
3377     *
3378     * @param {number} [index=this.children_.length]
3379     *        The index to attempt to add a child into.
3380     *
3381     * @return {Component}
3382     *         The `Component` that gets added as a child. When using a string the
3383     *         `Component` will get created by this process.
3384     */
3385
3386
3387    Component.prototype.addChild = function addChild(child) {
3388      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
3389      var index = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.children_.length;
3390
3391      var component = void 0;
3392      var componentName = void 0;
3393
3394      // If child is a string, create component with options
3395      if (typeof child === 'string') {
3396        componentName = toTitleCase(child);
3397
3398        var componentClassName = options.componentClass || componentName;
3399
3400        // Set name through options
3401        options.name = componentName;
3402
3403        // Create a new object & element for this controls set
3404        // If there's no .player_, this is a player
3405        var ComponentClass = Component.getComponent(componentClassName);
3406
3407        if (!ComponentClass) {
3408          throw new Error('Component ' + componentClassName + ' does not exist');
3409        }
3410
3411        // data stored directly on the videojs object may be
3412        // misidentified as a component to retain
3413        // backwards-compatibility with 4.x. check to make sure the
3414        // component class can be instantiated.
3415        if (typeof ComponentClass !== 'function') {
3416          return null;
3417        }
3418
3419        component = new ComponentClass(this.player_ || this, options);
3420
3421        // child is a component instance
3422      } else {
3423        component = child;
3424      }
3425
3426      this.children_.splice(index, 0, component);
3427
3428      if (typeof component.id === 'function') {
3429        this.childIndex_[component.id()] = component;
3430      }
3431
3432      // If a name wasn't used to create the component, check if we can use the
3433      // name function of the component
3434      componentName = componentName || component.name && toTitleCase(component.name());
3435
3436      if (componentName) {
3437        this.childNameIndex_[componentName] = component;
3438      }
3439
3440      // Add the UI object's element to the container div (box)
3441      // Having an element is not required
3442      if (typeof component.el === 'function' && component.el()) {
3443        var childNodes = this.contentEl().children;
3444        var refNode = childNodes[index] || null;
3445
3446        this.contentEl().insertBefore(component.el(), refNode);
3447      }
3448
3449      // Return so it can stored on parent object if desired.
3450      return component;
3451    };
3452
3453    /**
3454     * Remove a child `Component` from this `Component`s list of children. Also removes
3455     * the child `Component`s element from this `Component`s element.
3456     *
3457     * @param {Component} component
3458     *        The child `Component` to remove.
3459     */
3460
3461
3462    Component.prototype.removeChild = function removeChild(component) {
3463      if (typeof component === 'string') {
3464        component = this.getChild(component);
3465      }
3466
3467      if (!component || !this.children_) {
3468        return;
3469      }
3470
3471      var childFound = false;
3472
3473      for (var i = this.children_.length - 1; i >= 0; i--) {
3474        if (this.children_[i] === component) {
3475          childFound = true;
3476          this.children_.splice(i, 1);
3477          break;
3478        }
3479      }
3480
3481      if (!childFound) {
3482        return;
3483      }
3484
3485      this.childIndex_[component.id()] = null;
3486      this.childNameIndex_[component.name()] = null;
3487
3488      var compEl = component.el();
3489
3490      if (compEl && compEl.parentNode === this.contentEl()) {
3491        this.contentEl().removeChild(component.el());
3492      }
3493    };
3494
3495    /**
3496     * Add and initialize default child `Component`s based upon options.
3497     */
3498
3499
3500    Component.prototype.initChildren = function initChildren() {
3501      var _this = this;
3502
3503      var children = this.options_.children;
3504
3505      if (children) {
3506        // `this` is `parent`
3507        var parentOptions = this.options_;
3508
3509        var handleAdd = function handleAdd(child) {
3510          var name = child.name;
3511          var opts = child.opts;
3512
3513          // Allow options for children to be set at the parent options
3514          // e.g. videojs(id, { controlBar: false });
3515          // instead of videojs(id, { children: { controlBar: false });
3516          if (parentOptions[name] !== undefined) {
3517            opts = parentOptions[name];
3518          }
3519
3520          // Allow for disabling default components
3521          // e.g. options['children']['posterImage'] = false
3522          if (opts === false) {
3523            return;
3524          }
3525
3526          // Allow options to be passed as a simple boolean if no configuration
3527          // is necessary.
3528          if (opts === true) {
3529            opts = {};
3530          }
3531
3532          // We also want to pass the original player options
3533          // to each component as well so they don't need to
3534          // reach back into the player for options later.
3535          opts.playerOptions = _this.options_.playerOptions;
3536
3537          // Create and add the child component.
3538          // Add a direct reference to the child by name on the parent instance.
3539          // If two of the same component are used, different names should be supplied
3540          // for each
3541          var newChild = _this.addChild(name, opts);
3542
3543          if (newChild) {
3544            _this[name] = newChild;
3545          }
3546        };
3547
3548        // Allow for an array of children details to passed in the options
3549        var workingChildren = void 0;
3550        var Tech = Component.getComponent('Tech');
3551
3552        if (Array.isArray(children)) {
3553          workingChildren = children;
3554        } else {
3555          workingChildren = Object.keys(children);
3556        }
3557
3558        workingChildren
3559        // children that are in this.options_ but also in workingChildren  would
3560        // give us extra children we do not want. So, we want to filter them out.
3561        .concat(Object.keys(this.options_).filter(function (child) {
3562          return !workingChildren.some(function (wchild) {
3563            if (typeof wchild === 'string') {
3564              return child === wchild;
3565            }
3566            return child === wchild.name;
3567          });
3568        })).map(function (child) {
3569          var name = void 0;
3570          var opts = void 0;
3571
3572          if (typeof child === 'string') {
3573            name = child;
3574            opts = children[name] || _this.options_[name] || {};
3575          } else {
3576            name = child.name;
3577            opts = child;
3578          }
3579
3580          return { name: name, opts: opts };
3581        }).filter(function (child) {
3582          // we have to make sure that child.name isn't in the techOrder since
3583          // techs are registerd as Components but can't aren't compatible
3584          // See https://github.com/videojs/video.js/issues/2772
3585          var c = Component.getComponent(child.opts.componentClass || toTitleCase(child.name));
3586
3587          return c && !Tech.isTech(c);
3588        }).forEach(handleAdd);
3589      }
3590    };
3591
3592    /**
3593     * Builds the default DOM class name. Should be overriden by sub-components.
3594     *
3595     * @return {string}
3596     *         The DOM class name for this object.
3597     *
3598     * @abstract
3599     */
3600
3601
3602    Component.prototype.buildCSSClass = function buildCSSClass() {
3603      // Child classes can include a function that does:
3604      // return 'CLASS NAME' + this._super();
3605      return '';
3606    };
3607
3608    /**
3609     * Bind a listener to the component's ready state.
3610     * Different from event listeners in that if the ready event has already happened
3611     * it will trigger the function immediately.
3612     *
3613     * @return {Component}
3614     *         Returns itself; method can be chained.
3615     */
3616
3617
3618    Component.prototype.ready = function ready(fn) {
3619      var sync = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
3620
3621      if (!fn) {
3622        return;
3623      }
3624
3625      if (!this.isReady_) {
3626        this.readyQueue_ = this.readyQueue_ || [];
3627        this.readyQueue_.push(fn);
3628        return;
3629      }
3630
3631      if (sync) {
3632        fn.call(this);
3633      } else {
3634        // Call the function asynchronously by default for consistency
3635        this.setTimeout(fn, 1);
3636      }
3637    };
3638
3639    /**
3640     * Trigger all the ready listeners for this `Component`.
3641     *
3642     * @fires Component#ready
3643     */
3644
3645
3646    Component.prototype.triggerReady = function triggerReady() {
3647      this.isReady_ = true;
3648
3649      // Ensure ready is triggered asynchronously
3650      this.setTimeout(function () {
3651        var readyQueue = this.readyQueue_;
3652
3653        // Reset Ready Queue
3654        this.readyQueue_ = [];
3655
3656        if (readyQueue && readyQueue.length > 0) {
3657          readyQueue.forEach(function (fn) {
3658            fn.call(this);
3659          }, this);
3660        }
3661
3662        // Allow for using event listeners also
3663        /**
3664         * Triggered when a `Component` is ready.
3665         *
3666         * @event Component#ready
3667         * @type {EventTarget~Event}
3668         */
3669        this.trigger('ready');
3670      }, 1);
3671    };
3672
3673    /**
3674     * Find a single DOM element matching a `selector`. This can be within the `Component`s
3675     * `contentEl()` or another custom context.
3676     *
3677     * @param {string} selector
3678     *        A valid CSS selector, which will be passed to `querySelector`.
3679     *
3680     * @param {Element|string} [context=this.contentEl()]
3681     *        A DOM element within which to query. Can also be a selector string in
3682     *        which case the first matching element will get used as context. If
3683     *        missing `this.contentEl()` gets used. If  `this.contentEl()` returns
3684     *        nothing it falls back to `document`.
3685     *
3686     * @return {Element|null}
3687     *         the dom element that was found, or null
3688     *
3689     * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
3690     */
3691
3692
3693    Component.prototype.$ = function $$$1(selector, context) {
3694      return $(selector, context || this.contentEl());
3695    };
3696
3697    /**
3698     * Finds all DOM element matching a `selector`. This can be within the `Component`s
3699     * `contentEl()` or another custom context.
3700     *
3701     * @param {string} selector
3702     *        A valid CSS selector, which will be passed to `querySelectorAll`.
3703     *
3704     * @param {Element|string} [context=this.contentEl()]
3705     *        A DOM element within which to query. Can also be a selector string in
3706     *        which case the first matching element will get used as context. If
3707     *        missing `this.contentEl()` gets used. If  `this.contentEl()` returns
3708     *        nothing it falls back to `document`.
3709     *
3710     * @return {NodeList}
3711     *         a list of dom elements that were found
3712     *
3713     * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
3714     */
3715
3716
3717    Component.prototype.$$ = function $$$$1(selector, context) {
3718      return $$(selector, context || this.contentEl());
3719    };
3720
3721    /**
3722     * Check if a component's element has a CSS class name.
3723     *
3724     * @param {string} classToCheck
3725     *        CSS class name to check.
3726     *
3727     * @return {boolean}
3728     *         - True if the `Component` has the class.
3729     *         - False if the `Component` does not have the class`
3730     */
3731
3732
3733    Component.prototype.hasClass = function hasClass$$1(classToCheck) {
3734      return hasClass(this.el_, classToCheck);
3735    };
3736
3737    /**
3738     * Add a CSS class name to the `Component`s element.
3739     *
3740     * @param {string} classToAdd
3741     *        CSS class name to add
3742     */
3743
3744
3745    Component.prototype.addClass = function addClass$$1(classToAdd) {
3746      addClass(this.el_, classToAdd);
3747    };
3748
3749    /**
3750     * Remove a CSS class name from the `Component`s element.
3751     *
3752     * @param {string} classToRemove
3753     *        CSS class name to remove
3754     */
3755
3756
3757    Component.prototype.removeClass = function removeClass$$1(classToRemove) {
3758      removeClass(this.el_, classToRemove);
3759    };
3760
3761    /**
3762     * Add or remove a CSS class name from the component's element.
3763     * - `classToToggle` gets added when {@link Component#hasClass} would return false.
3764     * - `classToToggle` gets removed when {@link Component#hasClass} would return true.
3765     *
3766     * @param  {string} classToToggle
3767     *         The class to add or remove based on (@link Component#hasClass}
3768     *
3769     * @param  {boolean|Dom~predicate} [predicate]
3770     *         An {@link Dom~predicate} function or a boolean
3771     */
3772
3773
3774    Component.prototype.toggleClass = function toggleClass$$1(classToToggle, predicate) {
3775      toggleClass(this.el_, classToToggle, predicate);
3776    };
3777
3778    /**
3779     * Show the `Component`s element if it is hidden by removing the
3780     * 'vjs-hidden' class name from it.
3781     */
3782
3783
3784    Component.prototype.show = function show() {
3785      this.removeClass('vjs-hidden');
3786    };
3787
3788    /**
3789     * Hide the `Component`s element if it is currently showing by adding the
3790     * 'vjs-hidden` class name to it.
3791     */
3792
3793
3794    Component.prototype.hide = function hide() {
3795      this.addClass('vjs-hidden');
3796    };
3797
3798    /**
3799     * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing'
3800     * class name to it. Used during fadeIn/fadeOut.
3801     *
3802     * @private
3803     */
3804
3805
3806    Component.prototype.lockShowing = function lockShowing() {
3807      this.addClass('vjs-lock-showing');
3808    };
3809
3810    /**
3811     * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing'
3812     * class name from it. Used during fadeIn/fadeOut.
3813     *
3814     * @private
3815     */
3816
3817
3818    Component.prototype.unlockShowing = function unlockShowing() {
3819      this.removeClass('vjs-lock-showing');
3820    };
3821
3822    /**
3823     * Get the value of an attribute on the `Component`s element.
3824     *
3825     * @param {string} attribute
3826     *        Name of the attribute to get the value from.
3827     *
3828     * @return {string|null}
3829     *         - The value of the attribute that was asked for.
3830     *         - Can be an empty string on some browsers if the attribute does not exist
3831     *           or has no value
3832     *         - Most browsers will return null if the attibute does not exist or has
3833     *           no value.
3834     *
3835     * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute}
3836     */
3837
3838
3839    Component.prototype.getAttribute = function getAttribute$$1(attribute) {
3840      return getAttribute(this.el_, attribute);
3841    };
3842
3843    /**
3844     * Set the value of an attribute on the `Component`'s element
3845     *
3846     * @param {string} attribute
3847     *        Name of the attribute to set.
3848     *
3849     * @param {string} value
3850     *        Value to set the attribute to.
3851     *
3852     * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute}
3853     */
3854
3855
3856    Component.prototype.setAttribute = function setAttribute$$1(attribute, value) {
3857      setAttribute(this.el_, attribute, value);
3858    };
3859
3860    /**
3861     * Remove an attribute from the `Component`s element.
3862     *
3863     * @param {string} attribute
3864     *        Name of the attribute to remove.
3865     *
3866     * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute}
3867     */
3868
3869
3870    Component.prototype.removeAttribute = function removeAttribute$$1(attribute) {
3871      removeAttribute(this.el_, attribute);
3872    };
3873
3874    /**
3875     * Get or set the width of the component based upon the CSS styles.
3876     * See {@link Component#dimension} for more detailed information.
3877     *
3878     * @param {number|string} [num]
3879     *        The width that you want to set postfixed with '%', 'px' or nothing.
3880     *
3881     * @param {boolean} [skipListeners]
3882     *        Skip the componentresize event trigger
3883     *
3884     * @return {number|string}
3885     *         The width when getting, zero if there is no width. Can be a string
3886     *           postpixed with '%' or 'px'.
3887     */
3888
3889
3890    Component.prototype.width = function width(num, skipListeners) {
3891      return this.dimension('width', num, skipListeners);
3892    };
3893
3894    /**
3895     * Get or set the height of the component based upon the CSS styles.
3896     * See {@link Component#dimension} for more detailed information.
3897     *
3898     * @param {number|string} [num]
3899     *        The height that you want to set postfixed with '%', 'px' or nothing.
3900     *
3901     * @param {boolean} [skipListeners]
3902     *        Skip the componentresize event trigger
3903     *
3904     * @return {number|string}
3905     *         The width when getting, zero if there is no width. Can be a string
3906     *         postpixed with '%' or 'px'.
3907     */
3908
3909
3910    Component.prototype.height = function height(num, skipListeners) {
3911      return this.dimension('height', num, skipListeners);
3912    };
3913
3914    /**
3915     * Set both the width and height of the `Component` element at the same time.
3916     *
3917     * @param  {number|string} width
3918     *         Width to set the `Component`s element to.
3919     *
3920     * @param  {number|string} height
3921     *         Height to set the `Component`s element to.
3922     */
3923
3924
3925    Component.prototype.dimensions = function dimensions(width, height) {
3926      // Skip componentresize listeners on width for optimization
3927      this.width(width, true);
3928      this.height(height);
3929    };
3930
3931    /**
3932     * Get or set width or height of the `Component` element. This is the shared code
3933     * for the {@link Component#width} and {@link Component#height}.
3934     *
3935     * Things to know:
3936     * - If the width or height in an number this will return the number postfixed with 'px'.
3937     * - If the width/height is a percent this will return the percent postfixed with '%'
3938     * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function
3939     *   defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`.
3940     *   See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/}
3941     *   for more information
3942     * - If you want the computed style of the component, use {@link Component#currentWidth}
3943     *   and {@link {Component#currentHeight}
3944     *
3945     * @fires Component#componentresize
3946     *
3947     * @param {string} widthOrHeight
3948     8        'width' or 'height'
3949     *
3950     * @param  {number|string} [num]
3951     8         New dimension
3952     *
3953     * @param  {boolean} [skipListeners]
3954     *         Skip componentresize event trigger
3955     *
3956     * @return {number}
3957     *         The dimension when getting or 0 if unset
3958     */
3959
3960
3961    Component.prototype.dimension = function dimension(widthOrHeight, num, skipListeners) {
3962      if (num !== undefined) {
3963        // Set to zero if null or literally NaN (NaN !== NaN)
3964        if (num === null || num !== num) {
3965          num = 0;
3966        }
3967
3968        // Check if using css width/height (% or px) and adjust
3969        if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) {
3970          this.el_.style[widthOrHeight] = num;
3971        } else if (num === 'auto') {
3972          this.el_.style[widthOrHeight] = '';
3973        } else {
3974          this.el_.style[widthOrHeight] = num + 'px';
3975        }
3976
3977        // skipListeners allows us to avoid triggering the resize event when setting both width and height
3978        if (!skipListeners) {
3979          /**
3980           * Triggered when a component is resized.
3981           *
3982           * @event Component#componentresize
3983           * @type {EventTarget~Event}
3984           */
3985          this.trigger('componentresize');
3986        }
3987
3988        return;
3989      }
3990
3991      // Not setting a value, so getting it
3992      // Make sure element exists
3993      if (!this.el_) {
3994        return 0;
3995      }
3996
3997      // Get dimension value from style
3998      var val = this.el_.style[widthOrHeight];
3999      var pxIndex = val.indexOf('px');
4000
4001      if (pxIndex !== -1) {
4002        // Return the pixel value with no 'px'
4003        return parseInt(val.slice(0, pxIndex), 10);
4004      }
4005
4006      // No px so using % or no style was set, so falling back to offsetWidth/height
4007      // If component has display:none, offset will return 0
4008      // TODO: handle display:none and no dimension style using px
4009      return parseInt(this.el_['offset' + toTitleCase(widthOrHeight)], 10);
4010    };
4011
4012    /**
4013     * Get the width or the height of the `Component` elements computed style. Uses
4014     * `window.getComputedStyle`.
4015     *
4016     * @param {string} widthOrHeight
4017     *        A string containing 'width' or 'height'. Whichever one you want to get.
4018     *
4019     * @return {number}
4020     *         The dimension that gets asked for or 0 if nothing was set
4021     *         for that dimension.
4022     */
4023
4024
4025    Component.prototype.currentDimension = function currentDimension(widthOrHeight) {
4026      var computedWidthOrHeight = 0;
4027
4028      if (widthOrHeight !== 'width' && widthOrHeight !== 'height') {
4029        throw new Error('currentDimension only accepts width or height value');
4030      }
4031
4032      if (typeof window_1.getComputedStyle === 'function') {
4033        var computedStyle = window_1.getComputedStyle(this.el_);
4034
4035        computedWidthOrHeight = computedStyle.getPropertyValue(widthOrHeight) || computedStyle[widthOrHeight];
4036      }
4037
4038      // remove 'px' from variable and parse as integer
4039      computedWidthOrHeight = parseFloat(computedWidthOrHeight);
4040
4041      // if the computed value is still 0, it's possible that the browser is lying
4042      // and we want to check the offset values.
4043      // This code also runs wherever getComputedStyle doesn't exist.
4044      if (computedWidthOrHeight === 0) {
4045        var rule = 'offset' + toTitleCase(widthOrHeight);
4046
4047        computedWidthOrHeight = this.el_[rule];
4048      }
4049
4050      return computedWidthOrHeight;
4051    };
4052
4053    /**
4054     * An object that contains width and height values of the `Component`s
4055     * computed style. Uses `window.getComputedStyle`.
4056     *
4057     * @typedef {Object} Component~DimensionObject
4058     *
4059     * @property {number} width
4060     *           The width of the `Component`s computed style.
4061     *
4062     * @property {number} height
4063     *           The height of the `Component`s computed style.
4064     */
4065
4066    /**
4067     * Get an object that contains width and height values of the `Component`s
4068     * computed style.
4069     *
4070     * @return {Component~DimensionObject}
4071     *         The dimensions of the components element
4072     */
4073
4074
4075    Component.prototype.currentDimensions = function currentDimensions() {
4076      return {
4077        width: this.currentDimension('width'),
4078        height: this.currentDimension('height')
4079      };
4080    };
4081
4082    /**
4083     * Get the width of the `Component`s computed style. Uses `window.getComputedStyle`.
4084     *
4085     * @return {number} width
4086     *           The width of the `Component`s computed style.
4087     */
4088
4089
4090    Component.prototype.currentWidth = function currentWidth() {
4091      return this.currentDimension('width');
4092    };
4093
4094    /**
4095     * Get the height of the `Component`s computed style. Uses `window.getComputedStyle`.
4096     *
4097     * @return {number} height
4098     *           The height of the `Component`s computed style.
4099     */
4100
4101
4102    Component.prototype.currentHeight = function currentHeight() {
4103      return this.currentDimension('height');
4104    };
4105
4106    /**
4107     * Set the focus to this component
4108     */
4109
4110
4111    Component.prototype.focus = function focus() {
4112      this.el_.focus();
4113    };
4114
4115    /**
4116     * Remove the focus from this component
4117     */
4118
4119
4120    Component.prototype.blur = function blur() {
4121      this.el_.blur();
4122    };
4123
4124    /**
4125     * Emit a 'tap' events when touch event support gets detected. This gets used to
4126     * support toggling the controls through a tap on the video. They get enabled
4127     * because every sub-component would have extra overhead otherwise.
4128     *
4129     * @private
4130     * @fires Component#tap
4131     * @listens Component#touchstart
4132     * @listens Component#touchmove
4133     * @listens Component#touchleave
4134     * @listens Component#touchcancel
4135     * @listens Component#touchend
4136      */
4137
4138
4139    Component.prototype.emitTapEvents = function emitTapEvents() {
4140      // Track the start time so we can determine how long the touch lasted
4141      var touchStart = 0;
4142      var firstTouch = null;
4143
4144      // Maximum movement allowed during a touch event to still be considered a tap
4145      // Other popular libs use anywhere from 2 (hammer.js) to 15,
4146      // so 10 seems like a nice, round number.
4147      var tapMovementThreshold = 10;
4148
4149      // The maximum length a touch can be while still being considered a tap
4150      var touchTimeThreshold = 200;
4151
4152      var couldBeTap = void 0;
4153
4154      this.on('touchstart', function (event) {
4155        // If more than one finger, don't consider treating this as a click
4156        if (event.touches.length === 1) {
4157          // Copy pageX/pageY from the object
4158          firstTouch = {
4159            pageX: event.touches[0].pageX,
4160            pageY: event.touches[0].pageY
4161          };
4162          // Record start time so we can detect a tap vs. "touch and hold"
4163          touchStart = new Date().getTime();
4164          // Reset couldBeTap tracking
4165          couldBeTap = true;
4166        }
4167      });
4168
4169      this.on('touchmove', function (event) {
4170        // If more than one finger, don't consider treating this as a click
4171        if (event.touches.length > 1) {
4172          couldBeTap = false;
4173        } else if (firstTouch) {
4174          // Some devices will throw touchmoves for all but the slightest of taps.
4175          // So, if we moved only a small distance, this could still be a tap
4176          var xdiff = event.touches[0].pageX - firstTouch.pageX;
4177          var ydiff = event.touches[0].pageY - firstTouch.pageY;
4178          var touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff);
4179
4180          if (touchDistance > tapMovementThreshold) {
4181            couldBeTap = false;
4182          }
4183        }
4184      });
4185
4186      var noTap = function noTap() {
4187        couldBeTap = false;
4188      };
4189
4190      // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s
4191      this.on('touchleave', noTap);
4192      this.on('touchcancel', noTap);
4193
4194      // When the touch ends, measure how long it took and trigger the appropriate
4195      // event
4196      this.on('touchend', function (event) {
4197        firstTouch = null;
4198        // Proceed only if the touchmove/leave/cancel event didn't happen
4199        if (couldBeTap === true) {
4200          // Measure how long the touch lasted
4201          var touchTime = new Date().getTime() - touchStart;
4202
4203          // Make sure the touch was less than the threshold to be considered a tap
4204          if (touchTime < touchTimeThreshold) {
4205            // Don't let browser turn this into a click
4206            event.preventDefault();
4207            /**
4208             * Triggered when a `Component` is tapped.
4209             *
4210             * @event Component#tap
4211             * @type {EventTarget~Event}
4212             */
4213            this.trigger('tap');
4214            // It may be good to copy the touchend event object and change the
4215            // type to tap, if the other event properties aren't exact after
4216            // Events.fixEvent runs (e.g. event.target)
4217          }
4218        }
4219      });
4220    };
4221
4222    /**
4223     * This function reports user activity whenever touch events happen. This can get
4224     * turned off by any sub-components that wants touch events to act another way.
4225     *
4226     * Report user touch activity when touch events occur. User activity gets used to
4227     * determine when controls should show/hide. It is simple when it comes to mouse
4228     * events, because any mouse event should show the controls. So we capture mouse
4229     * events that bubble up to the player and report activity when that happens.
4230     * With touch events it isn't as easy as `touchstart` and `touchend` toggle player
4231     * controls. So touch events can't help us at the player level either.
4232     *
4233     * User activity gets checked asynchronously. So what could happen is a tap event
4234     * on the video turns the controls off. Then the `touchend` event bubbles up to
4235     * the player. Which, if it reported user activity, would turn the controls right
4236     * back on. We also don't want to completely block touch events from bubbling up.
4237     * Furthermore a `touchmove` event and anything other than a tap, should not turn
4238     * controls back on.
4239     *
4240     * @listens Component#touchstart
4241     * @listens Component#touchmove
4242     * @listens Component#touchend
4243     * @listens Component#touchcancel
4244     */
4245
4246
4247    Component.prototype.enableTouchActivity = function enableTouchActivity() {
4248      // Don't continue if the root player doesn't support reporting user activity
4249      if (!this.player() || !this.player().reportUserActivity) {
4250        return;
4251      }
4252
4253      // listener for reporting that the user is active
4254      var report = bind(this.player(), this.player().reportUserActivity);
4255
4256      var touchHolding = void 0;
4257
4258      this.on('touchstart', function () {
4259        report();
4260        // For as long as the they are touching the device or have their mouse down,
4261        // we consider them active even if they're not moving their finger or mouse.
4262        // So we want to continue to update that they are active
4263        this.clearInterval(touchHolding);
4264        // report at the same interval as activityCheck
4265        touchHolding = this.setInterval(report, 250);
4266      });
4267
4268      var touchEnd = function touchEnd(event) {
4269        report();
4270        // stop the interval that maintains activity if the touch is holding
4271        this.clearInterval(touchHolding);
4272      };
4273
4274      this.on('touchmove', report);
4275      this.on('touchend', touchEnd);
4276      this.on('touchcancel', touchEnd);
4277    };
4278
4279    /**
4280     * A callback that has no parameters and is bound into `Component`s context.
4281     *
4282     * @callback Component~GenericCallback
4283     * @this Component
4284     */
4285
4286    /**
4287     * Creates a function that runs after an `x` millisecond timeout. This function is a
4288     * wrapper around `window.setTimeout`. There are a few reasons to use this one
4289     * instead though:
4290     * 1. It gets cleared via  {@link Component#clearTimeout} when
4291     *    {@link Component#dispose} gets called.
4292     * 2. The function callback will gets turned into a {@link Component~GenericCallback}
4293     *
4294     * > Note: You can't use `window.clearTimeout` on the id returned by this function. This
4295     *         will cause its dispose listener not to get cleaned up! Please use
4296     *         {@link Component#clearTimeout} or {@link Component#dispose} instead.
4297     *
4298     * @param {Component~GenericCallback} fn
4299     *        The function that will be run after `timeout`.
4300     *
4301     * @param {number} timeout
4302     *        Timeout in milliseconds to delay before executing the specified function.
4303     *
4304     * @return {number}
4305     *         Returns a timeout ID that gets used to identify the timeout. It can also
4306     *         get used in {@link Component#clearTimeout} to clear the timeout that
4307     *         was set.
4308     *
4309     * @listens Component#dispose
4310     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout}
4311     */
4312
4313
4314    Component.prototype.setTimeout = function setTimeout(fn, timeout) {
4315      var _this2 = this;
4316
4317      fn = bind(this, fn);
4318
4319      var timeoutId = window_1.setTimeout(fn, timeout);
4320      var disposeFn = function disposeFn() {
4321        return _this2.clearTimeout(timeoutId);
4322      };
4323
4324      disposeFn.guid = 'vjs-timeout-' + timeoutId;
4325
4326      this.on('dispose', disposeFn);
4327
4328      return timeoutId;
4329    };
4330
4331    /**
4332     * Clears a timeout that gets created via `window.setTimeout` or
4333     * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout}
4334     * use this function instead of `window.clearTimout`. If you don't your dispose
4335     * listener will not get cleaned up until {@link Component#dispose}!
4336     *
4337     * @param {number} timeoutId
4338     *        The id of the timeout to clear. The return value of
4339     *        {@link Component#setTimeout} or `window.setTimeout`.
4340     *
4341     * @return {number}
4342     *         Returns the timeout id that was cleared.
4343     *
4344     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout}
4345     */
4346
4347
4348    Component.prototype.clearTimeout = function clearTimeout(timeoutId) {
4349      window_1.clearTimeout(timeoutId);
4350
4351      var disposeFn = function disposeFn() {};
4352
4353      disposeFn.guid = 'vjs-timeout-' + timeoutId;
4354
4355      this.off('dispose', disposeFn);
4356
4357      return timeoutId;
4358    };
4359
4360    /**
4361     * Creates a function that gets run every `x` milliseconds. This function is a wrapper
4362     * around `window.setInterval`. There are a few reasons to use this one instead though.
4363     * 1. It gets cleared via  {@link Component#clearInterval} when
4364     *    {@link Component#dispose} gets called.
4365     * 2. The function callback will be a {@link Component~GenericCallback}
4366     *
4367     * @param {Component~GenericCallback} fn
4368     *        The function to run every `x` seconds.
4369     *
4370     * @param {number} interval
4371     *        Execute the specified function every `x` milliseconds.
4372     *
4373     * @return {number}
4374     *         Returns an id that can be used to identify the interval. It can also be be used in
4375     *         {@link Component#clearInterval} to clear the interval.
4376     *
4377     * @listens Component#dispose
4378     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval}
4379     */
4380
4381
4382    Component.prototype.setInterval = function setInterval(fn, interval) {
4383      var _this3 = this;
4384
4385      fn = bind(this, fn);
4386
4387      var intervalId = window_1.setInterval(fn, interval);
4388
4389      var disposeFn = function disposeFn() {
4390        return _this3.clearInterval(intervalId);
4391      };
4392
4393      disposeFn.guid = 'vjs-interval-' + intervalId;
4394
4395      this.on('dispose', disposeFn);
4396
4397      return intervalId;
4398    };
4399
4400    /**
4401     * Clears an interval that gets created via `window.setInterval` or
4402     * {@link Component#setInterval}. If you set an inteval via {@link Component#setInterval}
4403     * use this function instead of `window.clearInterval`. If you don't your dispose
4404     * listener will not get cleaned up until {@link Component#dispose}!
4405     *
4406     * @param {number} intervalId
4407     *        The id of the interval to clear. The return value of
4408     *        {@link Component#setInterval} or `window.setInterval`.
4409     *
4410     * @return {number}
4411     *         Returns the interval id that was cleared.
4412     *
4413     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval}
4414     */
4415
4416
4417    Component.prototype.clearInterval = function clearInterval(intervalId) {
4418      window_1.clearInterval(intervalId);
4419
4420      var disposeFn = function disposeFn() {};
4421
4422      disposeFn.guid = 'vjs-interval-' + intervalId;
4423
4424      this.off('dispose', disposeFn);
4425
4426      return intervalId;
4427    };
4428
4429    /**
4430     * Queues up a callback to be passed to requestAnimationFrame (rAF), but
4431     * with a few extra bonuses:
4432     *
4433     * - Supports browsers that do not support rAF by falling back to
4434     *   {@link Component#setTimeout}.
4435     *
4436     * - The callback is turned into a {@link Component~GenericCallback} (i.e.
4437     *   bound to the component).
4438     *
4439     * - Automatic cancellation of the rAF callback is handled if the component
4440     *   is disposed before it is called.
4441     *
4442     * @param  {Component~GenericCallback} fn
4443     *         A function that will be bound to this component and executed just
4444     *         before the browser's next repaint.
4445     *
4446     * @return {number}
4447     *         Returns an rAF ID that gets used to identify the timeout. It can
4448     *         also be used in {@link Component#cancelAnimationFrame} to cancel
4449     *         the animation frame callback.
4450     *
4451     * @listens Component#dispose
4452     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame}
4453     */
4454
4455
4456    Component.prototype.requestAnimationFrame = function requestAnimationFrame(fn) {
4457      var _this4 = this;
4458
4459      if (this.supportsRaf_) {
4460        fn = bind(this, fn);
4461
4462        var id = window_1.requestAnimationFrame(fn);
4463        var disposeFn = function disposeFn() {
4464          return _this4.cancelAnimationFrame(id);
4465        };
4466
4467        disposeFn.guid = 'vjs-raf-' + id;
4468        this.on('dispose', disposeFn);
4469
4470        return id;
4471      }
4472
4473      // Fall back to using a timer.
4474      return this.setTimeout(fn, 1000 / 60);
4475    };
4476
4477    /**
4478     * Cancels a queued callback passed to {@link Component#requestAnimationFrame}
4479     * (rAF).
4480     *
4481     * If you queue an rAF callback via {@link Component#requestAnimationFrame},
4482     * use this function instead of `window.cancelAnimationFrame`. If you don't,
4483     * your dispose listener will not get cleaned up until {@link Component#dispose}!
4484     *
4485     * @param {number} id
4486     *        The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}.
4487     *
4488     * @return {number}
4489     *         Returns the rAF ID that was cleared.
4490     *
4491     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame}
4492     */
4493
4494
4495    Component.prototype.cancelAnimationFrame = function cancelAnimationFrame(id) {
4496      if (this.supportsRaf_) {
4497        window_1.cancelAnimationFrame(id);
4498
4499        var disposeFn = function disposeFn() {};
4500
4501        disposeFn.guid = 'vjs-raf-' + id;
4502
4503        this.off('dispose', disposeFn);
4504
4505        return id;
4506      }
4507
4508      // Fall back to using a timer.
4509      return this.clearTimeout(id);
4510    };
4511
4512    /**
4513     * Register a `Component` with `videojs` given the name and the component.
4514     *
4515     * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s
4516     *         should be registered using {@link Tech.registerTech} or
vendor: 13,279 bytes, lines 4517-4957
4517     *         {@link videojs:videojs.registerTech}.
4518     *
4519     * > NOTE: This function can also be seen on videojs as
4520     *         {@link videojs:videojs.registerComponent}.
4521     *
4522     * @param {string} name
4523     *        The name of the `Component` to register.
4524     *
4525     * @param {Component} ComponentToRegister
4526     *        The `Component` class to register.
4527     *
4528     * @return {Component}
4529     *         The `Component` that was registered.
4530     */
4531
4532
4533    Component.registerComponent = function registerComponent(name, ComponentToRegister) {
4534      if (typeof name !== 'string' || !name) {
4535        throw new Error('Illegal component name, "' + name + '"; must be a non-empty string.');
4536      }
4537
4538      var Tech = Component.getComponent('Tech');
4539
4540      // We need to make sure this check is only done if Tech has been registered.
4541      var isTech = Tech && Tech.isTech(ComponentToRegister);
4542      var isComp = Component === ComponentToRegister || Component.prototype.isPrototypeOf(ComponentToRegister.prototype);
4543
4544      if (isTech || !isComp) {
4545        var reason = void 0;
4546
4547        if (isTech) {
4548          reason = 'techs must be registered using Tech.registerTech()';
4549        } else {
4550          reason = 'must be a Component subclass';
4551        }
4552
4553        throw new Error('Illegal component, "' + name + '"; ' + reason + '.');
4554      }
4555
4556      name = toTitleCase(name);
4557
4558      if (!Component.components_) {
4559        Component.components_ = {};
4560      }
4561
4562      var Player = Component.getComponent('Player');
4563
4564      if (name === 'Player' && Player && Player.players) {
4565        var players = Player.players;
4566        var playerNames = Object.keys(players);
4567
4568        // If we have players that were disposed, then their name will still be
4569        // in Players.players. So, we must loop through and verify that the value
4570        // for each item is not null. This allows registration of the Player component
4571        // after all players have been disposed or before any were created.
4572        if (players && playerNames.length > 0 && playerNames.map(function (pname) {
4573          return players[pname];
4574        }).every(Boolean)) {
4575          throw new Error('Can not register Player component after player has been created.');
4576        }
4577      }
4578
4579      Component.components_[name] = ComponentToRegister;
4580
4581      return ComponentToRegister;
4582    };
4583
4584    /**
4585     * Get a `Component` based on the name it was registered with.
4586     *
4587     * @param {string} name
4588     *        The Name of the component to get.
4589     *
4590     * @return {Component}
4591     *         The `Component` that got registered under the given name.
4592     *
4593     * @deprecated In `videojs` 6 this will not return `Component`s that were not
4594     *             registered using {@link Component.registerComponent}. Currently we
4595     *             check the global `videojs` object for a `Component` name and
4596     *             return that if it exists.
4597     */
4598
4599
4600    Component.getComponent = function getComponent(name) {
4601      if (!name) {
4602        return;
4603      }
4604
4605      name = toTitleCase(name);
4606
4607      if (Component.components_ && Component.components_[name]) {
4608        return Component.components_[name];
4609      }
4610    };
4611
4612    return Component;
4613  }();
4614
4615  /**
4616   * Whether or not this component supports `requestAnimationFrame`.
4617   *
4618   * This is exposed primarily for testing purposes.
4619   *
4620   * @private
4621   * @type {Boolean}
4622   */
4623
4624
4625  Component.prototype.supportsRaf_ = typeof window_1.requestAnimationFrame === 'function' && typeof window_1.cancelAnimationFrame === 'function';
4626
4627  Component.registerComponent('Component', Component);
4628
4629  /**
4630   * @file browser.js
4631   * @module browser
4632   */
4633
4634  var USER_AGENT = window_1.navigator && window_1.navigator.userAgent || '';
4635  var webkitVersionMap = /AppleWebKit\/([\d.]+)/i.exec(USER_AGENT);
4636  var appleWebkitVersion = webkitVersionMap ? parseFloat(webkitVersionMap.pop()) : null;
4637
4638  /*
4639   * Device is an iPhone
4640   *
4641   * @type {Boolean}
4642   * @constant
4643   * @private
4644   */
4645  var IS_IPAD = /iPad/i.test(USER_AGENT);
4646
4647  // The Facebook app's UIWebView identifies as both an iPhone and iPad, so
4648  // to identify iPhones, we need to exclude iPads.
4649  // http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/
4650  var IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD;
4651  var IS_IPOD = /iPod/i.test(USER_AGENT);
4652  var IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD;
4653
4654  var IOS_VERSION = function () {
4655    var match = USER_AGENT.match(/OS (\d+)_/i);
4656
4657    if (match && match[1]) {
4658      return match[1];
4659    }
4660    return null;
4661  }();
4662
4663  var IS_ANDROID = /Android/i.test(USER_AGENT);
4664  var ANDROID_VERSION = function () {
4665    // This matches Android Major.Minor.Patch versions
4666    // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned
4667    var match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i);
4668
4669    if (!match) {
4670      return null;
4671    }
4672
4673    var major = match[1] && parseFloat(match[1]);
4674    var minor = match[2] && parseFloat(match[2]);
4675
4676    if (major && minor) {
4677      return parseFloat(match[1] + '.' + match[2]);
4678    } else if (major) {
4679      return major;
4680    }
4681    return null;
4682  }();
4683
4684  var IS_NATIVE_ANDROID = IS_ANDROID && ANDROID_VERSION < 5 && appleWebkitVersion < 537;
4685
4686  var IS_FIREFOX = /Firefox/i.test(USER_AGENT);
4687  var IS_EDGE = /Edge/i.test(USER_AGENT);
4688  var IS_CHROME = !IS_EDGE && /Chrome/i.test(USER_AGENT);
4689  var CHROME_VERSION = function () {
4690    var match = USER_AGENT.match(/Chrome\/(\d+)/);
4691
4692    if (match && match[1]) {
4693      return parseFloat(match[1]);
4694    }
4695    return null;
4696  }();
4697  var IE_VERSION = function () {
4698    var result = /MSIE\s(\d+)\.\d/.exec(USER_AGENT);
4699    var version = result && parseFloat(result[1]);
4700
4701    if (!version && /Trident\/7.0/i.test(USER_AGENT) && /rv:11.0/.test(USER_AGENT)) {
4702      // IE 11 has a different user agent string than other IE versions
4703      version = 11.0;
4704    }
4705
4706    return version;
4707  }();
4708
4709  var IS_SAFARI = /Safari/i.test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE;
4710  var IS_ANY_SAFARI = IS_SAFARI || IS_IOS;
4711
4712  var TOUCH_ENABLED = isReal() && ('ontouchstart' in window_1 || window_1.DocumentTouch && window_1.document instanceof window_1.DocumentTouch);
4713
4714  var browser = /*#__PURE__*/Object.freeze({
4715    IS_IPAD: IS_IPAD,
4716    IS_IPHONE: IS_IPHONE,
4717    IS_IPOD: IS_IPOD,
4718    IS_IOS: IS_IOS,
4719    IOS_VERSION: IOS_VERSION,
4720    IS_ANDROID: IS_ANDROID,
4721    ANDROID_VERSION: ANDROID_VERSION,
4722    IS_NATIVE_ANDROID: IS_NATIVE_ANDROID,
4723    IS_FIREFOX: IS_FIREFOX,
4724    IS_EDGE: IS_EDGE,
4725    IS_CHROME: IS_CHROME,
4726    CHROME_VERSION: CHROME_VERSION,
4727    IE_VERSION: IE_VERSION,
4728    IS_SAFARI: IS_SAFARI,
4729    IS_ANY_SAFARI: IS_ANY_SAFARI,
4730    TOUCH_ENABLED: TOUCH_ENABLED
4731  });
4732
4733  /**
4734   * @file time-ranges.js
4735   * @module time-ranges
4736   */
4737
4738  /**
4739   * Returns the time for the specified index at the start or end
4740   * of a TimeRange object.
4741   *
4742   * @function time-ranges:indexFunction
4743   *
4744   * @param {number} [index=0]
4745   *        The range number to return the time for.
4746   *
4747   * @return {number}
4748   *         The time that offset at the specified index.
4749   *
4750   * @depricated index must be set to a value, in the future this will throw an error.
4751   */
4752
4753  /**
4754   * An object that contains ranges of time for various reasons.
4755   *
4756   * @typedef {Object} TimeRange
4757   *
4758   * @property {number} length
4759   *           The number of time ranges represented by this Object
4760   *
4761   * @property {time-ranges:indexFunction} start
4762   *           Returns the time offset at which a specified time range begins.
4763   *
4764   * @property {time-ranges:indexFunction} end
4765   *           Returns the time offset at which a specified time range ends.
4766   *
4767   * @see https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges
4768   */
4769
4770  /**
4771   * Check if any of the time ranges are over the maximum index.
4772   *
4773   * @param {string} fnName
4774   *        The function name to use for logging
4775   *
4776   * @param {number} index
4777   *        The index to check
4778   *
4779   * @param {number} maxIndex
4780   *        The maximum possible index
4781   *
4782   * @throws {Error} if the timeRanges provided are over the maxIndex
4783   */
4784  function rangeCheck(fnName, index, maxIndex) {
4785    if (typeof index !== 'number' || index < 0 || index > maxIndex) {
4786      throw new Error('Failed to execute \'' + fnName + '\' on \'TimeRanges\': The index provided (' + index + ') is non-numeric or out of bounds (0-' + maxIndex + ').');
4787    }
4788  }
4789
4790  /**
4791   * Get the time for the specified index at the start or end
4792   * of a TimeRange object.
4793   *
4794   * @param {string} fnName
4795   *        The function name to use for logging
4796   *
4797   * @param {string} valueIndex
4798   *        The property that should be used to get the time. should be 'start' or 'end'
4799   *
4800   * @param {Array} ranges
4801   *        An array of time ranges
4802   *
4803   * @param {Array} [rangeIndex=0]
4804   *        The index to start the search at
4805   *
4806   * @return {number}
4807   *         The time that offset at the specified index.
4808   *
4809   *
4810   * @depricated rangeIndex must be set to a value, in the future this will throw an error.
4811   * @throws {Error} if rangeIndex is more than the length of ranges
4812   */
4813  function getRange(fnName, valueIndex, ranges, rangeIndex) {
4814    rangeCheck(fnName, rangeIndex, ranges.length - 1);
4815    return ranges[rangeIndex][valueIndex];
4816  }
4817
4818  /**
4819   * Create a time range object given ranges of time.
4820   *
4821   * @param {Array} [ranges]
4822   *        An array of time ranges.
4823   */
4824  function createTimeRangesObj(ranges) {
4825    if (ranges === undefined || ranges.length === 0) {
4826      return {
4827        length: 0,
4828        start: function start() {
4829          throw new Error('This TimeRanges object is empty');
4830        },
4831        end: function end() {
4832          throw new Error('This TimeRanges object is empty');
4833        }
4834      };
4835    }
4836    return {
4837      length: ranges.length,
4838      start: getRange.bind(null, 'start', 0, ranges),
4839      end: getRange.bind(null, 'end', 1, ranges)
4840    };
4841  }
4842
4843  /**
4844   * Should create a fake `TimeRange` object which mimics an HTML5 time range instance.
4845   *
4846   * @param {number|Array} start
4847   *        The start of a single range or an array of ranges
4848   *
4849   * @param {number} end
4850   *        The end of a single range.
4851   *
4852   * @private
4853   */
4854  function createTimeRanges(start, end) {
4855    if (Array.isArray(start)) {
4856      return createTimeRangesObj(start);
4857    } else if (start === undefined || end === undefined) {
4858      return createTimeRangesObj();
4859    }
4860    return createTimeRangesObj([[start, end]]);
4861  }
4862
4863  /**
4864   * @file buffer.js
4865   * @module buffer
4866   */
4867
4868  /**
4869   * Compute the percentage of the media that has been buffered.
4870   *
4871   * @param {TimeRange} buffered
4872   *        The current `TimeRange` object representing buffered time ranges
4873   *
4874   * @param {number} duration
4875   *        Total duration of the media
4876   *
4877   * @return {number}
4878   *         Percent buffered of the total duration in decimal form.
4879   */
4880  function bufferedPercent(buffered, duration) {
4881    var bufferedDuration = 0;
4882    var start = void 0;
4883    var end = void 0;
4884
4885    if (!duration) {
4886      return 0;
4887    }
4888
4889    if (!buffered || !buffered.length) {
4890      buffered = createTimeRanges(0, 0);
4891    }
4892
4893    for (var i = 0; i < buffered.length; i++) {
4894      start = buffered.start(i);
4895      end = buffered.end(i);
4896
4897      // buffered end can be bigger than duration by a very small fraction
4898      if (end > duration) {
4899        end = duration;
4900      }
4901
4902      bufferedDuration += end - start;
4903    }
4904
4905    return bufferedDuration / duration;
4906  }
4907
4908  /**
4909   * @file fullscreen-api.js
4910   * @module fullscreen-api
4911   * @private
4912   */
4913
4914  /**
4915   * Store the browser-specific methods for the fullscreen API.
4916   *
4917   * @type {Object}
4918   * @see [Specification]{@link https://fullscreen.spec.whatwg.org}
4919   * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js}
4920   */
4921  var FullscreenApi = {};
4922
4923  // browser API methods
4924  var apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror'],
4925  // WebKit
4926  ['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror'],
4927  // Old WebKit (Safari 5.1)
4928  ['webkitRequestFullScreen', 'webkitCancelFullScreen', 'webkitCurrentFullScreenElement', 'webkitCancelFullScreen', 'webkitfullscreenchange', 'webkitfullscreenerror'],
4929  // Mozilla
4930  ['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror'],
4931  // Microsoft
4932  ['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError']];
4933
4934  var specApi = apiMap[0];
4935  var browserApi = void 0;
4936
4937  // determine the supported set of functions
4938  for (var i = 0; i < apiMap.length; i++) {
4939    // check for exitFullscreen function
4940    if (apiMap[i][1] in document_1) {
4941      browserApi = apiMap[i];
4942      break;
4943    }
4944  }
4945
4946  // map the browser API names to the spec API names
4947  if (browserApi) {
4948    for (var _i = 0; _i < browserApi.length; _i++) {
4949      FullscreenApi[specApi[_i]] = browserApi[_i];
4950    }
4951  }
4952
4953  /**
4954   * @file media-error.js
4955   */
4956
4957  /**
4958   * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class.
4959   *
4960   * @param {number|string|Object|MediaError} value
4961   *        This can be of multiple types:
4962   *        - number: should be a standard error code
4963   *        - string: an error message (the code will be 0)
4964   *        - Object: arbitrary properties
4965   *        - `MediaError` (native): used to populate a video.js `MediaError` object
4966   *        - `MediaError` (video.js): will return itself if it's already a
4967   *          video.js `MediaError` object.
4968   *
4969   * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror}
4970   * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes}
4971   *
4972   * @class MediaError
4973   */
4974  function MediaError(value) {
4975
4976    // Allow redundant calls to this constructor to avoid having `instanceof`
4977    // checks peppered around the code.
4978    if (value instanceof MediaError) {
4979      return value;
4980    }
4981
4982    if (typeof value === 'number') {
4983      this.code = value;
4984    } else if (typeof value === 'string') {
vendor: 49,953 bytes, lines 4985-6820
4985      // default code is zero, so this is a custom error
4986      this.message = value;
4987    } else if (isObject(value)) {
4988
4989      // We assign the `code` property manually because native `MediaError` objects
4990      // do not expose it as an own/enumerable property of the object.
4991      if (typeof value.code === 'number') {
4992        this.code = value.code;
4993      }
4994
4995      assign(this, value);
4996    }
4997
4998    if (!this.message) {
4999      this.message = MediaError.defaultMessages[this.code] || '';
5000    }
5001  }
5002
5003  /**
5004   * The error code that refers two one of the defined `MediaError` types
5005   *
5006   * @type {Number}
5007   */
5008  MediaError.prototype.code = 0;
5009
5010  /**
5011   * An optional message that to show with the error. Message is not part of the HTML5
5012   * video spec but allows for more informative custom errors.
5013   *
5014   * @type {String}
5015   */
5016  MediaError.prototype.message = '';
5017
5018  /**
5019   * An optional status code that can be set by plugins to allow even more detail about
5020   * the error. For example a plugin might provide a specific HTTP status code and an
5021   * error message for that code. Then when the plugin gets that error this class will
5022   * know how to display an error message for it. This allows a custom message to show
5023   * up on the `Player` error overlay.
5024   *
5025   * @type {Array}
5026   */
5027  MediaError.prototype.status = null;
5028
5029  /**
5030   * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the
5031   * specification listed under {@link MediaError} for more information.
5032   *
5033   * @enum {array}
5034   * @readonly
5035   * @property {string} 0 - MEDIA_ERR_CUSTOM
5036   * @property {string} 1 - MEDIA_ERR_CUSTOM
5037   * @property {string} 2 - MEDIA_ERR_ABORTED
5038   * @property {string} 3 - MEDIA_ERR_NETWORK
5039   * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED
5040   * @property {string} 5 - MEDIA_ERR_ENCRYPTED
5041   */
5042  MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED'];
5043
5044  /**
5045   * The default `MediaError` messages based on the {@link MediaError.errorTypes}.
5046   *
5047   * @type {Array}
5048   * @constant
5049   */
5050  MediaError.defaultMessages = {
5051    1: 'You aborted the media playback',
5052    2: 'A network error caused the media download to fail part-way.',
5053    3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.',
5054    4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.',
5055    5: 'The media is encrypted and we do not have the keys to decrypt it.'
5056  };
5057
5058  // Add types as properties on MediaError
5059  // e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
5060  for (var errNum = 0; errNum < MediaError.errorTypes.length; errNum++) {
5061    MediaError[MediaError.errorTypes[errNum]] = errNum;
5062    // values should be accessible on both the class and instance
5063    MediaError.prototype[MediaError.errorTypes[errNum]] = errNum;
5064  }
5065
5066  var tuple = SafeParseTuple;
5067
5068  function SafeParseTuple(obj, reviver) {
5069      var json;
5070      var error = null;
5071
5072      try {
5073          json = JSON.parse(obj, reviver);
5074      } catch (err) {
5075          error = err;
5076      }
5077
5078      return [error, json];
5079  }
5080
5081  /**
5082   * Returns whether an object is `Promise`-like (i.e. has a `then` method).
5083   *
5084   * @param  {Object}  value
5085   *         An object that may or may not be `Promise`-like.
5086   *
5087   * @return {Boolean}
5088   *         Whether or not the object is `Promise`-like.
5089   */
5090  function isPromise(value) {
5091    return value !== undefined && value !== null && typeof value.then === 'function';
5092  }
5093
5094  /**
5095   * Silence a Promise-like object.
5096   *
5097   * This is useful for avoiding non-harmful, but potentially confusing "uncaught
5098   * play promise" rejection error messages.
5099   *
5100   * @param  {Object} value
5101   *         An object that may or may not be `Promise`-like.
5102   */
5103  function silencePromise(value) {
5104    if (isPromise(value)) {
5105      value.then(null, function (e) {});
5106    }
5107  }
5108
5109  /**
5110   * @file text-track-list-converter.js Utilities for capturing text track state and
5111   * re-creating tracks based on a capture.
5112   *
5113   * @module text-track-list-converter
5114   */
5115
5116  /**
5117   * Examine a single {@link TextTrack} and return a JSON-compatible javascript object that
5118   * represents the {@link TextTrack}'s state.
5119   *
5120   * @param {TextTrack} track
5121   *        The text track to query.
5122   *
5123   * @return {Object}
5124   *         A serializable javascript representation of the TextTrack.
5125   * @private
5126   */
5127  var trackToJson_ = function trackToJson_(track) {
5128    var ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce(function (acc, prop, i) {
5129
5130      if (track[prop]) {
5131        acc[prop] = track[prop];
5132      }
5133
5134      return acc;
5135    }, {
5136      cues: track.cues && Array.prototype.map.call(track.cues, function (cue) {
5137        return {
5138          startTime: cue.startTime,
5139          endTime: cue.endTime,
5140          text: cue.text,
5141          id: cue.id
5142        };
5143      })
5144    });
5145
5146    return ret;
5147  };
5148
5149  /**
5150   * Examine a {@link Tech} and return a JSON-compatible javascript array that represents the
5151   * state of all {@link TextTrack}s currently configured. The return array is compatible with
5152   * {@link text-track-list-converter:jsonToTextTracks}.
5153   *
5154   * @param {Tech} tech
5155   *        The tech object to query
5156   *
5157   * @return {Array}
5158   *         A serializable javascript representation of the {@link Tech}s
5159   *         {@link TextTrackList}.
5160   */
5161  var textTracksToJson = function textTracksToJson(tech) {
5162
5163    var trackEls = tech.$$('track');
5164
5165    var trackObjs = Array.prototype.map.call(trackEls, function (t) {
5166      return t.track;
5167    });
5168    var tracks = Array.prototype.map.call(trackEls, function (trackEl) {
5169      var json = trackToJson_(trackEl.track);
5170
5171      if (trackEl.src) {
5172        json.src = trackEl.src;
5173      }
5174      return json;
5175    });
5176
5177    return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) {
5178      return trackObjs.indexOf(track) === -1;
5179    }).map(trackToJson_));
5180  };
5181
5182  /**
5183   * Create a set of remote {@link TextTrack}s on a {@link Tech} based on an array of javascript
5184   * object {@link TextTrack} representations.
5185   *
5186   * @param {Array} json
5187   *        An array of `TextTrack` representation objects, like those that would be
5188   *        produced by `textTracksToJson`.
5189   *
5190   * @param {Tech} tech
5191   *        The `Tech` to create the `TextTrack`s on.
5192   */
5193  var jsonToTextTracks = function jsonToTextTracks(json, tech) {
5194    json.forEach(function (track) {
5195      var addedTrack = tech.addRemoteTextTrack(track).track;
5196
5197      if (!track.src && track.cues) {
5198        track.cues.forEach(function (cue) {
5199          return addedTrack.addCue(cue);
5200        });
5201      }
5202    });
5203
5204    return tech.textTracks();
5205  };
5206
5207  var textTrackConverter = { textTracksToJson: textTracksToJson, jsonToTextTracks: jsonToTextTracks, trackToJson_: trackToJson_ };
5208
5209  /**
5210   * @file modal-dialog.js
5211   */
5212
5213  var MODAL_CLASS_NAME = 'vjs-modal-dialog';
5214  var ESC = 27;
5215
5216  /**
5217   * The `ModalDialog` displays over the video and its controls, which blocks
5218   * interaction with the player until it is closed.
5219   *
5220   * Modal dialogs include a "Close" button and will close when that button
5221   * is activated - or when ESC is pressed anywhere.
5222   *
5223   * @extends Component
5224   */
5225
5226  var ModalDialog = function (_Component) {
5227    inherits(ModalDialog, _Component);
5228
5229    /**
5230     * Create an instance of this class.
5231     *
5232     * @param {Player} player
5233     *        The `Player` that this class should be attached to.
5234     *
5235     * @param {Object} [options]
5236     *        The key/value store of player options.
5237     *
5238     * @param {Mixed} [options.content=undefined]
5239     *        Provide customized content for this modal.
5240     *
5241     * @param {string} [options.description]
5242     *        A text description for the modal, primarily for accessibility.
5243     *
5244     * @param {boolean} [options.fillAlways=false]
5245     *        Normally, modals are automatically filled only the first time
5246     *        they open. This tells the modal to refresh its content
5247     *        every time it opens.
5248     *
5249     * @param {string} [options.label]
5250     *        A text label for the modal, primarily for accessibility.
5251     *
5252     * @param {boolean} [options.temporary=true]
5253     *        If `true`, the modal can only be opened once; it will be
5254     *        disposed as soon as it's closed.
5255     *
5256     * @param {boolean} [options.uncloseable=false]
5257     *        If `true`, the user will not be able to close the modal
5258     *        through the UI in the normal ways. Programmatic closing is
5259     *        still possible.
5260     */
5261    function ModalDialog(player, options) {
5262      classCallCheck(this, ModalDialog);
5263
5264      var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
5265
5266      _this.opened_ = _this.hasBeenOpened_ = _this.hasBeenFilled_ = false;
5267
5268      _this.closeable(!_this.options_.uncloseable);
5269      _this.content(_this.options_.content);
5270
5271      // Make sure the contentEl is defined AFTER any children are initialized
5272      // because we only want the contents of the modal in the contentEl
5273      // (not the UI elements like the close button).
5274      _this.contentEl_ = createEl('div', {
5275        className: MODAL_CLASS_NAME + '-content'
5276      }, {
5277        role: 'document'
5278      });
5279
5280      _this.descEl_ = createEl('p', {
5281        className: MODAL_CLASS_NAME + '-description vjs-control-text',
5282        id: _this.el().getAttribute('aria-describedby')
5283      });
5284
5285      textContent(_this.descEl_, _this.description());
5286      _this.el_.appendChild(_this.descEl_);
5287      _this.el_.appendChild(_this.contentEl_);
5288      return _this;
5289    }
5290
5291    /**
5292     * Create the `ModalDialog`'s DOM element
5293     *
5294     * @return {Element}
5295     *         The DOM element that gets created.
5296     */
5297
5298
5299    ModalDialog.prototype.createEl = function createEl$$1() {
5300      return _Component.prototype.createEl.call(this, 'div', {
5301        className: this.buildCSSClass(),
5302        tabIndex: -1
5303      }, {
5304        'aria-describedby': this.id() + '_description',
5305        'aria-hidden': 'true',
5306        'aria-label': this.label(),
5307        'role': 'dialog'
5308      });
5309    };
5310
5311    ModalDialog.prototype.dispose = function dispose() {
5312      this.contentEl_ = null;
5313      this.descEl_ = null;
5314      this.previouslyActiveEl_ = null;
5315
5316      _Component.prototype.dispose.call(this);
5317    };
5318
5319    /**
5320     * Builds the default DOM `className`.
5321     *
5322     * @return {string}
5323     *         The DOM `className` for this object.
5324     */
5325
5326
5327    ModalDialog.prototype.buildCSSClass = function buildCSSClass() {
5328      return MODAL_CLASS_NAME + ' vjs-hidden ' + _Component.prototype.buildCSSClass.call(this);
5329    };
5330
5331    /**
5332     * Handles `keydown` events on the document, looking for ESC, which closes
5333     * the modal.
5334     *
5335     * @param {EventTarget~Event} e
5336     *        The keypress that triggered this event.
5337     *
5338     * @listens keydown
5339     */
5340
5341
5342    ModalDialog.prototype.handleKeyPress = function handleKeyPress(e) {
5343      if (e.which === ESC && this.closeable()) {
5344        this.close();
5345      }
5346    };
5347
5348    /**
5349     * Returns the label string for this modal. Primarily used for accessibility.
5350     *
5351     * @return {string}
5352     *         the localized or raw label of this modal.
5353     */
5354
5355
5356    ModalDialog.prototype.label = function label() {
5357      return this.localize(this.options_.label || 'Modal Window');
5358    };
5359
5360    /**
5361     * Returns the description string for this modal. Primarily used for
5362     * accessibility.
5363     *
5364     * @return {string}
5365     *         The localized or raw description of this modal.
5366     */
5367
5368
5369    ModalDialog.prototype.description = function description() {
5370      var desc = this.options_.description || this.localize('This is a modal window.');
5371
5372      // Append a universal closeability message if the modal is closeable.
5373      if (this.closeable()) {
5374        desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.');
5375      }
5376
5377      return desc;
5378    };
5379
5380    /**
5381     * Opens the modal.
5382     *
5383     * @fires ModalDialog#beforemodalopen
5384     * @fires ModalDialog#modalopen
5385     */
5386
5387
5388    ModalDialog.prototype.open = function open() {
5389      if (!this.opened_) {
5390        var player = this.player();
5391
5392        /**
5393          * Fired just before a `ModalDialog` is opened.
5394          *
5395          * @event ModalDialog#beforemodalopen
5396          * @type {EventTarget~Event}
5397          */
5398        this.trigger('beforemodalopen');
5399        this.opened_ = true;
5400
5401        // Fill content if the modal has never opened before and
5402        // never been filled.
5403        if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) {
5404          this.fill();
5405        }
5406
5407        // If the player was playing, pause it and take note of its previously
5408        // playing state.
5409        this.wasPlaying_ = !player.paused();
5410
5411        if (this.options_.pauseOnOpen && this.wasPlaying_) {
5412          player.pause();
5413        }
5414
5415        if (this.closeable()) {
5416          this.on(this.el_.ownerDocument, 'keydown', bind(this, this.handleKeyPress));
5417        }
5418
5419        // Hide controls and note if they were enabled.
5420        this.hadControls_ = player.controls();
5421        player.controls(false);
5422
5423        this.show();
5424        this.conditionalFocus_();
5425        this.el().setAttribute('aria-hidden', 'false');
5426
5427        /**
5428          * Fired just after a `ModalDialog` is opened.
5429          *
5430          * @event ModalDialog#modalopen
5431          * @type {EventTarget~Event}
5432          */
5433        this.trigger('modalopen');
5434        this.hasBeenOpened_ = true;
5435      }
5436    };
5437
5438    /**
5439     * If the `ModalDialog` is currently open or closed.
5440     *
5441     * @param  {boolean} [value]
5442     *         If given, it will open (`true`) or close (`false`) the modal.
5443     *
5444     * @return {boolean}
5445     *         the current open state of the modaldialog
5446     */
5447
5448
5449    ModalDialog.prototype.opened = function opened(value) {
5450      if (typeof value === 'boolean') {
5451        this[value ? 'open' : 'close']();
5452      }
5453      return this.opened_;
5454    };
5455
5456    /**
5457     * Closes the modal, does nothing if the `ModalDialog` is
5458     * not open.
5459     *
5460     * @fires ModalDialog#beforemodalclose
5461     * @fires ModalDialog#modalclose
5462     */
5463
5464
5465    ModalDialog.prototype.close = function close() {
5466      if (!this.opened_) {
5467        return;
5468      }
5469      var player = this.player();
5470
5471      /**
5472        * Fired just before a `ModalDialog` is closed.
5473        *
5474        * @event ModalDialog#beforemodalclose
5475        * @type {EventTarget~Event}
5476        */
5477      this.trigger('beforemodalclose');
5478      this.opened_ = false;
5479
5480      if (this.wasPlaying_ && this.options_.pauseOnOpen) {
5481        player.play();
5482      }
5483
5484      if (this.closeable()) {
5485        this.off(this.el_.ownerDocument, 'keydown', bind(this, this.handleKeyPress));
5486      }
5487
5488      if (this.hadControls_) {
5489        player.controls(true);
5490      }
5491
5492      this.hide();
5493      this.el().setAttribute('aria-hidden', 'true');
5494
5495      /**
5496        * Fired just after a `ModalDialog` is closed.
5497        *
5498        * @event ModalDialog#modalclose
5499        * @type {EventTarget~Event}
5500        */
5501      this.trigger('modalclose');
5502      this.conditionalBlur_();
5503
5504      if (this.options_.temporary) {
5505        this.dispose();
5506      }
5507    };
5508
5509    /**
5510     * Check to see if the `ModalDialog` is closeable via the UI.
5511     *
5512     * @param  {boolean} [value]
5513     *         If given as a boolean, it will set the `closeable` option.
5514     *
5515     * @return {boolean}
5516     *         Returns the final value of the closable option.
5517     */
5518
5519
5520    ModalDialog.prototype.closeable = function closeable(value) {
5521      if (typeof value === 'boolean') {
5522        var closeable = this.closeable_ = !!value;
5523        var close = this.getChild('closeButton');
5524
5525        // If this is being made closeable and has no close button, add one.
5526        if (closeable && !close) {
5527
5528          // The close button should be a child of the modal - not its
5529          // content element, so temporarily change the content element.
5530          var temp = this.contentEl_;
5531
5532          this.contentEl_ = this.el_;
5533          close = this.addChild('closeButton', { controlText: 'Close Modal Dialog' });
5534          this.contentEl_ = temp;
5535          this.on(close, 'close', this.close);
5536        }
5537
5538        // If this is being made uncloseable and has a close button, remove it.
5539        if (!closeable && close) {
5540          this.off(close, 'close', this.close);
5541          this.removeChild(close);
5542          close.dispose();
5543        }
5544      }
5545      return this.closeable_;
5546    };
5547
5548    /**
5549     * Fill the modal's content element with the modal's "content" option.
5550     * The content element will be emptied before this change takes place.
5551     */
5552
5553
5554    ModalDialog.prototype.fill = function fill() {
5555      this.fillWith(this.content());
5556    };
5557
5558    /**
5559     * Fill the modal's content element with arbitrary content.
5560     * The content element will be emptied before this change takes place.
5561     *
5562     * @fires ModalDialog#beforemodalfill
5563     * @fires ModalDialog#modalfill
5564     *
5565     * @param {Mixed} [content]
5566     *        The same rules apply to this as apply to the `content` option.
5567     */
5568
5569
5570    ModalDialog.prototype.fillWith = function fillWith(content) {
5571      var contentEl = this.contentEl();
5572      var parentEl = contentEl.parentNode;
5573      var nextSiblingEl = contentEl.nextSibling;
5574
5575      /**
5576       * Fired just before a `ModalDialog` is filled with content.
5577       *
5578       * @event ModalDialog#beforemodalfill
5579       * @type {EventTarget~Event}
5580       */
5581      this.trigger('beforemodalfill');
5582      this.hasBeenFilled_ = true;
5583
5584      // Detach the content element from the DOM before performing
5585      // manipulation to avoid modifying the live DOM multiple times.
5586      parentEl.removeChild(contentEl);
5587      this.empty();
5588      insertContent(contentEl, content);
5589      /**
5590       * Fired just after a `ModalDialog` is filled with content.
5591       *
5592       * @event ModalDialog#modalfill
5593       * @type {EventTarget~Event}
5594       */
5595      this.trigger('modalfill');
5596
5597      // Re-inject the re-filled content element.
5598      if (nextSiblingEl) {
5599        parentEl.insertBefore(contentEl, nextSiblingEl);
5600      } else {
5601        parentEl.appendChild(contentEl);
5602      }
5603
5604      // make sure that the close button is last in the dialog DOM
5605      var closeButton = this.getChild('closeButton');
5606
5607      if (closeButton) {
5608        parentEl.appendChild(closeButton.el_);
5609      }
5610    };
5611
5612    /**
5613     * Empties the content element. This happens anytime the modal is filled.
5614     *
5615     * @fires ModalDialog#beforemodalempty
5616     * @fires ModalDialog#modalempty
5617     */
5618
5619
5620    ModalDialog.prototype.empty = function empty() {
5621      /**
5622       * Fired just before a `ModalDialog` is emptied.
5623       *
5624       * @event ModalDialog#beforemodalempty
5625       * @type {EventTarget~Event}
5626       */
5627      this.trigger('beforemodalempty');
5628      emptyEl(this.contentEl());
5629
5630      /**
5631       * Fired just after a `ModalDialog` is emptied.
5632       *
5633       * @event ModalDialog#modalempty
5634       * @type {EventTarget~Event}
5635       */
5636      this.trigger('modalempty');
5637    };
5638
5639    /**
5640     * Gets or sets the modal content, which gets normalized before being
5641     * rendered into the DOM.
5642     *
5643     * This does not update the DOM or fill the modal, but it is called during
5644     * that process.
5645     *
5646     * @param  {Mixed} [value]
5647     *         If defined, sets the internal content value to be used on the
5648     *         next call(s) to `fill`. This value is normalized before being
5649     *         inserted. To "clear" the internal content value, pass `null`.
5650     *
5651     * @return {Mixed}
5652     *         The current content of the modal dialog
5653     */
5654
5655
5656    ModalDialog.prototype.content = function content(value) {
5657      if (typeof value !== 'undefined') {
5658        this.content_ = value;
5659      }
5660      return this.content_;
5661    };
5662
5663    /**
5664     * conditionally focus the modal dialog if focus was previously on the player.
5665     *
5666     * @private
5667     */
5668
5669
5670    ModalDialog.prototype.conditionalFocus_ = function conditionalFocus_() {
5671      var activeEl = document_1.activeElement;
5672      var playerEl = this.player_.el_;
5673
5674      this.previouslyActiveEl_ = null;
5675
5676      if (playerEl.contains(activeEl) || playerEl === activeEl) {
5677        this.previouslyActiveEl_ = activeEl;
5678
5679        this.focus();
5680
5681        this.on(document_1, 'keydown', this.handleKeyDown);
5682      }
5683    };
5684
5685    /**
5686     * conditionally blur the element and refocus the last focused element
5687     *
5688     * @private
5689     */
5690
5691
5692    ModalDialog.prototype.conditionalBlur_ = function conditionalBlur_() {
5693      if (this.previouslyActiveEl_) {
5694        this.previouslyActiveEl_.focus();
5695        this.previouslyActiveEl_ = null;
5696      }
5697
5698      this.off(document_1, 'keydown', this.handleKeyDown);
5699    };
5700
5701    /**
5702     * Keydown handler. Attached when modal is focused.
5703     *
5704     * @listens keydown
5705     */
5706
5707
5708    ModalDialog.prototype.handleKeyDown = function handleKeyDown(event) {
5709      // exit early if it isn't a tab key
5710      if (event.which !== 9) {
5711        return;
5712      }
5713
5714      var focusableEls = this.focusableEls_();
5715      var activeEl = this.el_.querySelector(':focus');
5716      var focusIndex = void 0;
5717
5718      for (var i = 0; i < focusableEls.length; i++) {
5719        if (activeEl === focusableEls[i]) {
5720          focusIndex = i;
5721          break;
5722        }
5723      }
5724
5725      if (document_1.activeElement === this.el_) {
5726        focusIndex = 0;
5727      }
5728
5729      if (event.shiftKey && focusIndex === 0) {
5730        focusableEls[focusableEls.length - 1].focus();
5731        event.preventDefault();
5732      } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) {
5733        focusableEls[0].focus();
5734        event.preventDefault();
5735      }
5736    };
5737
5738    /**
5739     * get all focusable elements
5740     *
5741     * @private
5742     */
5743
5744
5745    ModalDialog.prototype.focusableEls_ = function focusableEls_() {
5746      var allChildren = this.el_.querySelectorAll('*');
5747
5748      return Array.prototype.filter.call(allChildren, function (child) {
5749        return (child instanceof window_1.HTMLAnchorElement || child instanceof window_1.HTMLAreaElement) && child.hasAttribute('href') || (child instanceof window_1.HTMLInputElement || child instanceof window_1.HTMLSelectElement || child instanceof window_1.HTMLTextAreaElement || child instanceof window_1.HTMLButtonElement) && !child.hasAttribute('disabled') || child instanceof window_1.HTMLIFrameElement || child instanceof window_1.HTMLObjectElement || child instanceof window_1.HTMLEmbedElement || child.hasAttribute('tabindex') && child.getAttribute('tabindex') !== -1 || child.hasAttribute('contenteditable');
5750      });
5751    };
5752
5753    return ModalDialog;
5754  }(Component);
5755
5756  /**
5757   * Default options for `ModalDialog` default options.
5758   *
5759   * @type {Object}
5760   * @private
5761   */
5762
5763
5764  ModalDialog.prototype.options_ = {
5765    pauseOnOpen: true,
5766    temporary: true
5767  };
5768
5769  Component.registerComponent('ModalDialog', ModalDialog);
5770
5771  /**
5772   * @file track-list.js
5773   */
5774
5775  /**
5776   * Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and
5777   * {@link VideoTrackList}
5778   *
5779   * @extends EventTarget
5780   */
5781
5782  var TrackList = function (_EventTarget) {
5783    inherits(TrackList, _EventTarget);
5784
5785    /**
5786     * Create an instance of this class
5787     *
5788     * @param {Track[]} tracks
5789     *        A list of tracks to initialize the list with.
5790     *
5791     * @abstract
5792     */
5793    function TrackList() {
5794      var tracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
5795      classCallCheck(this, TrackList);
5796
5797      var _this = possibleConstructorReturn(this, _EventTarget.call(this));
5798
5799      _this.tracks_ = [];
5800
5801      /**
5802       * @memberof TrackList
5803       * @member {number} length
5804       *         The current number of `Track`s in the this Trackist.
5805       * @instance
5806       */
5807      Object.defineProperty(_this, 'length', {
5808        get: function get$$1() {
5809          return this.tracks_.length;
5810        }
5811      });
5812
5813      for (var i = 0; i < tracks.length; i++) {
5814        _this.addTrack(tracks[i]);
5815      }
5816      return _this;
5817    }
5818
5819    /**
5820     * Add a {@link Track} to the `TrackList`
5821     *
5822     * @param {Track} track
5823     *        The audio, video, or text track to add to the list.
5824     *
5825     * @fires TrackList#addtrack
5826     */
5827
5828
5829    TrackList.prototype.addTrack = function addTrack(track) {
5830      var index = this.tracks_.length;
5831
5832      if (!('' + index in this)) {
5833        Object.defineProperty(this, index, {
5834          get: function get$$1() {
5835            return this.tracks_[index];
5836          }
5837        });
5838      }
5839
5840      // Do not add duplicate tracks
5841      if (this.tracks_.indexOf(track) === -1) {
5842        this.tracks_.push(track);
5843        /**
5844         * Triggered when a track is added to a track list.
5845         *
5846         * @event TrackList#addtrack
5847         * @type {EventTarget~Event}
5848         * @property {Track} track
5849         *           A reference to track that was added.
5850         */
5851        this.trigger({
5852          track: track,
5853          type: 'addtrack'
5854        });
5855      }
5856    };
5857
5858    /**
5859     * Remove a {@link Track} from the `TrackList`
5860     *
5861     * @param {Track} rtrack
5862     *        The audio, video, or text track to remove from the list.
5863     *
5864     * @fires TrackList#removetrack
5865     */
5866
5867
5868    TrackList.prototype.removeTrack = function removeTrack(rtrack) {
5869      var track = void 0;
5870
5871      for (var i = 0, l = this.length; i < l; i++) {
5872        if (this[i] === rtrack) {
5873          track = this[i];
5874          if (track.off) {
5875            track.off();
5876          }
5877
5878          this.tracks_.splice(i, 1);
5879
5880          break;
5881        }
5882      }
5883
5884      if (!track) {
5885        return;
5886      }
5887
5888      /**
5889       * Triggered when a track is removed from track list.
5890       *
5891       * @event TrackList#removetrack
5892       * @type {EventTarget~Event}
5893       * @property {Track} track
5894       *           A reference to track that was removed.
5895       */
5896      this.trigger({
5897        track: track,
5898        type: 'removetrack'
5899      });
5900    };
5901
5902    /**
5903     * Get a Track from the TrackList by a tracks id
5904     *
5905     * @param {String} id - the id of the track to get
5906     * @method getTrackById
5907     * @return {Track}
5908     * @private
5909     */
5910
5911
5912    TrackList.prototype.getTrackById = function getTrackById(id) {
5913      var result = null;
5914
5915      for (var i = 0, l = this.length; i < l; i++) {
5916        var track = this[i];
5917
5918        if (track.id === id) {
5919          result = track;
5920          break;
5921        }
5922      }
5923
5924      return result;
5925    };
5926
5927    return TrackList;
5928  }(EventTarget);
5929
5930  /**
5931   * Triggered when a different track is selected/enabled.
5932   *
5933   * @event TrackList#change
5934   * @type {EventTarget~Event}
5935   */
5936
5937  /**
5938   * Events that can be called with on + eventName. See {@link EventHandler}.
5939   *
5940   * @property {Object} TrackList#allowedEvents_
5941   * @private
5942   */
5943
5944
5945  TrackList.prototype.allowedEvents_ = {
5946    change: 'change',
5947    addtrack: 'addtrack',
5948    removetrack: 'removetrack'
5949  };
5950
5951  // emulate attribute EventHandler support to allow for feature detection
5952  for (var event in TrackList.prototype.allowedEvents_) {
5953    TrackList.prototype['on' + event] = null;
5954  }
5955
5956  /**
5957   * @file audio-track-list.js
5958   */
5959
5960  /**
5961   * Anywhere we call this function we diverge from the spec
5962   * as we only support one enabled audiotrack at a time
5963   *
5964   * @param {AudioTrackList} list
5965   *        list to work on
5966   *
5967   * @param {AudioTrack} track
5968   *        The track to skip
5969   *
5970   * @private
5971   */
5972  var disableOthers = function disableOthers(list, track) {
5973    for (var i = 0; i < list.length; i++) {
5974      if (!Object.keys(list[i]).length || track.id === list[i].id) {
5975        continue;
5976      }
5977      // another audio track is enabled, disable it
5978      list[i].enabled = false;
5979    }
5980  };
5981
5982  /**
5983   * The current list of {@link AudioTrack} for a media file.
5984   *
5985   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist}
5986   * @extends TrackList
5987   */
5988
5989  var AudioTrackList = function (_TrackList) {
5990    inherits(AudioTrackList, _TrackList);
5991
5992    /**
5993     * Create an instance of this class.
5994     *
5995     * @param {AudioTrack[]} [tracks=[]]
5996     *        A list of `AudioTrack` to instantiate the list with.
5997     */
5998    function AudioTrackList() {
5999      var tracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
6000      classCallCheck(this, AudioTrackList);
6001
6002      // make sure only 1 track is enabled
6003      // sorted from last index to first index
6004      for (var i = tracks.length - 1; i >= 0; i--) {
6005        if (tracks[i].enabled) {
6006          disableOthers(tracks, tracks[i]);
6007          break;
6008        }
6009      }
6010
6011      var _this = possibleConstructorReturn(this, _TrackList.call(this, tracks));
6012
6013      _this.changing_ = false;
6014      return _this;
6015    }
6016
6017    /**
6018     * Add an {@link AudioTrack} to the `AudioTrackList`.
6019     *
6020     * @param {AudioTrack} track
6021     *        The AudioTrack to add to the list
6022     *
6023     * @fires TrackList#addtrack
6024     */
6025
6026
6027    AudioTrackList.prototype.addTrack = function addTrack(track) {
6028      var _this2 = this;
6029
6030      if (track.enabled) {
6031        disableOthers(this, track);
6032      }
6033
6034      _TrackList.prototype.addTrack.call(this, track);
6035      // native tracks don't have this
6036      if (!track.addEventListener) {
6037        return;
6038      }
6039
6040      /**
6041       * @listens AudioTrack#enabledchange
6042       * @fires TrackList#change
6043       */
6044      track.addEventListener('enabledchange', function () {
6045        // when we are disabling other tracks (since we don't support
6046        // more than one track at a time) we will set changing_
6047        // to true so that we don't trigger additional change events
6048        if (_this2.changing_) {
6049          return;
6050        }
6051        _this2.changing_ = true;
6052        disableOthers(_this2, track);
6053        _this2.changing_ = false;
6054        _this2.trigger('change');
6055      });
6056    };
6057
6058    return AudioTrackList;
6059  }(TrackList);
6060
6061  /**
6062   * @file video-track-list.js
6063   */
6064
6065  /**
6066   * Un-select all other {@link VideoTrack}s that are selected.
6067   *
6068   * @param {VideoTrackList} list
6069   *        list to work on
6070   *
6071   * @param {VideoTrack} track
6072   *        The track to skip
6073   *
6074   * @private
6075   */
6076  var disableOthers$1 = function disableOthers(list, track) {
6077    for (var i = 0; i < list.length; i++) {
6078      if (!Object.keys(list[i]).length || track.id === list[i].id) {
6079        continue;
6080      }
6081      // another video track is enabled, disable it
6082      list[i].selected = false;
6083    }
6084  };
6085
6086  /**
6087   * The current list of {@link VideoTrack} for a video.
6088   *
6089   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist}
6090   * @extends TrackList
6091   */
6092
6093  var VideoTrackList = function (_TrackList) {
6094    inherits(VideoTrackList, _TrackList);
6095
6096    /**
6097     * Create an instance of this class.
6098     *
6099     * @param {VideoTrack[]} [tracks=[]]
6100     *        A list of `VideoTrack` to instantiate the list with.
6101     */
6102    function VideoTrackList() {
6103      var tracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
6104      classCallCheck(this, VideoTrackList);
6105
6106      // make sure only 1 track is enabled
6107      // sorted from last index to first index
6108      for (var i = tracks.length - 1; i >= 0; i--) {
6109        if (tracks[i].selected) {
6110          disableOthers$1(tracks, tracks[i]);
6111          break;
6112        }
6113      }
6114
6115      var _this = possibleConstructorReturn(this, _TrackList.call(this, tracks));
6116
6117      _this.changing_ = false;
6118
6119      /**
6120       * @member {number} VideoTrackList#selectedIndex
6121       *         The current index of the selected {@link VideoTrack`}.
6122       */
6123      Object.defineProperty(_this, 'selectedIndex', {
6124        get: function get$$1() {
6125          for (var _i = 0; _i < this.length; _i++) {
6126            if (this[_i].selected) {
6127              return _i;
6128            }
6129          }
6130          return -1;
6131        },
6132        set: function set$$1() {}
6133      });
6134      return _this;
6135    }
6136
6137    /**
6138     * Add a {@link VideoTrack} to the `VideoTrackList`.
6139     *
6140     * @param {VideoTrack} track
6141     *        The VideoTrack to add to the list
6142     *
6143     * @fires TrackList#addtrack
6144     */
6145
6146
6147    VideoTrackList.prototype.addTrack = function addTrack(track) {
6148      var _this2 = this;
6149
6150      if (track.selected) {
6151        disableOthers$1(this, track);
6152      }
6153
6154      _TrackList.prototype.addTrack.call(this, track);
6155      // native tracks don't have this
6156      if (!track.addEventListener) {
6157        return;
6158      }
6159
6160      /**
6161       * @listens VideoTrack#selectedchange
6162       * @fires TrackList#change
6163       */
6164      track.addEventListener('selectedchange', function () {
6165        if (_this2.changing_) {
6166          return;
6167        }
6168        _this2.changing_ = true;
6169        disableOthers$1(_this2, track);
6170        _this2.changing_ = false;
6171        _this2.trigger('change');
6172      });
6173    };
6174
6175    return VideoTrackList;
6176  }(TrackList);
6177
6178  /**
6179   * @file text-track-list.js
6180   */
6181
6182  /**
6183   * The current list of {@link TextTrack} for a media file.
6184   *
6185   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist}
6186   * @extends TrackList
6187   */
6188
6189  var TextTrackList = function (_TrackList) {
6190    inherits(TextTrackList, _TrackList);
6191
6192    function TextTrackList() {
6193      classCallCheck(this, TextTrackList);
6194      return possibleConstructorReturn(this, _TrackList.apply(this, arguments));
6195    }
6196
6197    /**
6198     * Add a {@link TextTrack} to the `TextTrackList`
6199     *
6200     * @param {TextTrack} track
6201     *        The text track to add to the list.
6202     *
6203     * @fires TrackList#addtrack
6204     */
6205    TextTrackList.prototype.addTrack = function addTrack(track) {
6206      _TrackList.prototype.addTrack.call(this, track);
6207
6208      /**
6209       * @listens TextTrack#modechange
6210       * @fires TrackList#change
6211       */
6212      track.addEventListener('modechange', bind(this, function () {
6213        this.trigger('change');
6214      }));
6215
6216      var nonLanguageTextTrackKind = ['metadata', 'chapters'];
6217
6218      if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) {
6219        track.addEventListener('modechange', bind(this, function () {
6220          this.trigger('selectedlanguagechange');
6221        }));
6222      }
6223    };
6224
6225    return TextTrackList;
6226  }(TrackList);
6227
6228  /**
6229   * @file html-track-element-list.js
6230   */
6231
6232  /**
6233   * The current list of {@link HtmlTrackElement}s.
6234   */
6235  var HtmlTrackElementList = function () {
6236
6237    /**
6238     * Create an instance of this class.
6239     *
6240     * @param {HtmlTrackElement[]} [tracks=[]]
6241     *        A list of `HtmlTrackElement` to instantiate the list with.
6242     */
6243    function HtmlTrackElementList() {
6244      var trackElements = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
6245      classCallCheck(this, HtmlTrackElementList);
6246
6247      this.trackElements_ = [];
6248
6249      /**
6250       * @memberof HtmlTrackElementList
6251       * @member {number} length
6252       *         The current number of `Track`s in the this Trackist.
6253       * @instance
6254       */
6255      Object.defineProperty(this, 'length', {
6256        get: function get$$1() {
6257          return this.trackElements_.length;
6258        }
6259      });
6260
6261      for (var i = 0, length = trackElements.length; i < length; i++) {
6262        this.addTrackElement_(trackElements[i]);
6263      }
6264    }
6265
6266    /**
6267     * Add an {@link HtmlTrackElement} to the `HtmlTrackElementList`
6268     *
6269     * @param {HtmlTrackElement} trackElement
6270     *        The track element to add to the list.
6271     *
6272     * @private
6273     */
6274
6275
6276    HtmlTrackElementList.prototype.addTrackElement_ = function addTrackElement_(trackElement) {
6277      var index = this.trackElements_.length;
6278
6279      if (!('' + index in this)) {
6280        Object.defineProperty(this, index, {
6281          get: function get$$1() {
6282            return this.trackElements_[index];
6283          }
6284        });
6285      }
6286
6287      // Do not add duplicate elements
6288      if (this.trackElements_.indexOf(trackElement) === -1) {
6289        this.trackElements_.push(trackElement);
6290      }
6291    };
6292
6293    /**
6294     * Get an {@link HtmlTrackElement} from the `HtmlTrackElementList` given an
6295     * {@link TextTrack}.
6296     *
6297     * @param {TextTrack} track
6298     *        The track associated with a track element.
6299     *
6300     * @return {HtmlTrackElement|undefined}
6301     *         The track element that was found or undefined.
6302     *
6303     * @private
6304     */
6305
6306
6307    HtmlTrackElementList.prototype.getTrackElementByTrack_ = function getTrackElementByTrack_(track) {
6308      var trackElement_ = void 0;
6309
6310      for (var i = 0, length = this.trackElements_.length; i < length; i++) {
6311        if (track === this.trackElements_[i].track) {
6312          trackElement_ = this.trackElements_[i];
6313
6314          break;
6315        }
6316      }
6317
6318      return trackElement_;
6319    };
6320
6321    /**
6322     * Remove a {@link HtmlTrackElement} from the `HtmlTrackElementList`
6323     *
6324     * @param {HtmlTrackElement} trackElement
6325     *        The track element to remove from the list.
6326     *
6327     * @private
6328     */
6329
6330
6331    HtmlTrackElementList.prototype.removeTrackElement_ = function removeTrackElement_(trackElement) {
6332      for (var i = 0, length = this.trackElements_.length; i < length; i++) {
6333        if (trackElement === this.trackElements_[i]) {
6334          this.trackElements_.splice(i, 1);
6335
6336          break;
6337        }
6338      }
6339    };
6340
6341    return HtmlTrackElementList;
6342  }();
6343
6344  /**
6345   * @file text-track-cue-list.js
6346   */
6347
6348  /**
6349   * @typedef {Object} TextTrackCueList~TextTrackCue
6350   *
6351   * @property {string} id
6352   *           The unique id for this text track cue
6353   *
6354   * @property {number} startTime
6355   *           The start time for this text track cue
6356   *
6357   * @property {number} endTime
6358   *           The end time for this text track cue
6359   *
6360   * @property {boolean} pauseOnExit
6361   *           Pause when the end time is reached if true.
6362   *
6363   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcue}
6364   */
6365
6366  /**
6367   * A List of TextTrackCues.
6368   *
6369   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist}
6370   */
6371  var TextTrackCueList = function () {
6372
6373    /**
6374     * Create an instance of this class..
6375     *
6376     * @param {Array} cues
6377     *        A list of cues to be initialized with
6378     */
6379    function TextTrackCueList(cues) {
6380      classCallCheck(this, TextTrackCueList);
6381
6382      TextTrackCueList.prototype.setCues_.call(this, cues);
6383
6384      /**
6385       * @memberof TextTrackCueList
6386       * @member {number} length
6387       *         The current number of `TextTrackCue`s in the TextTrackCueList.
6388       * @instance
6389       */
6390      Object.defineProperty(this, 'length', {
6391        get: function get$$1() {
6392          return this.length_;
6393        }
6394      });
6395    }
6396
6397    /**
6398     * A setter for cues in this list. Creates getters
6399     * an an index for the cues.
6400     *
6401     * @param {Array} cues
6402     *        An array of cues to set
6403     *
6404     * @private
6405     */
6406
6407
6408    TextTrackCueList.prototype.setCues_ = function setCues_(cues) {
6409      var oldLength = this.length || 0;
6410      var i = 0;
6411      var l = cues.length;
6412
6413      this.cues_ = cues;
6414      this.length_ = cues.length;
6415
6416      var defineProp = function defineProp(index) {
6417        if (!('' + index in this)) {
6418          Object.defineProperty(this, '' + index, {
6419            get: function get$$1() {
6420              return this.cues_[index];
6421            }
6422          });
6423        }
6424      };
6425
6426      if (oldLength < l) {
6427        i = oldLength;
6428
6429        for (; i < l; i++) {
6430          defineProp.call(this, i);
6431        }
6432      }
6433    };
6434
6435    /**
6436     * Get a `TextTrackCue` that is currently in the `TextTrackCueList` by id.
6437     *
6438     * @param {string} id
6439     *        The id of the cue that should be searched for.
6440     *
6441     * @return {TextTrackCueList~TextTrackCue|null}
6442     *         A single cue or null if none was found.
6443     */
6444
6445
6446    TextTrackCueList.prototype.getCueById = function getCueById(id) {
6447      var result = null;
6448
6449      for (var i = 0, l = this.length; i < l; i++) {
6450        var cue = this[i];
6451
6452        if (cue.id === id) {
6453          result = cue;
6454          break;
6455        }
6456      }
6457
6458      return result;
6459    };
6460
6461    return TextTrackCueList;
6462  }();
6463
6464  /**
6465   * @file track-kinds.js
6466   */
6467
6468  /**
6469   * All possible `VideoTrackKind`s
6470   *
6471   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind
6472   * @typedef VideoTrack~Kind
6473   * @enum
6474   */
6475  var VideoTrackKind = {
6476    alternative: 'alternative',
6477    captions: 'captions',
6478    main: 'main',
6479    sign: 'sign',
6480    subtitles: 'subtitles',
6481    commentary: 'commentary'
6482  };
6483
6484  /**
6485   * All possible `AudioTrackKind`s
6486   *
6487   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind
6488   * @typedef AudioTrack~Kind
6489   * @enum
6490   */
6491  var AudioTrackKind = {
6492    'alternative': 'alternative',
6493    'descriptions': 'descriptions',
6494    'main': 'main',
6495    'main-desc': 'main-desc',
6496    'translation': 'translation',
6497    'commentary': 'commentary'
6498  };
6499
6500  /**
6501   * All possible `TextTrackKind`s
6502   *
6503   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-texttrack-kind
6504   * @typedef TextTrack~Kind
6505   * @enum
6506   */
6507  var TextTrackKind = {
6508    subtitles: 'subtitles',
6509    captions: 'captions',
6510    descriptions: 'descriptions',
6511    chapters: 'chapters',
6512    metadata: 'metadata'
6513  };
6514
6515  /**
6516   * All possible `TextTrackMode`s
6517   *
6518   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode
6519   * @typedef TextTrack~Mode
6520   * @enum
6521   */
6522  var TextTrackMode = {
6523    disabled: 'disabled',
6524    hidden: 'hidden',
6525    showing: 'showing'
6526  };
6527
6528  /**
6529   * @file track.js
6530   */
6531
6532  /**
6533   * A Track class that contains all of the common functionality for {@link AudioTrack},
6534   * {@link VideoTrack}, and {@link TextTrack}.
6535   *
6536   * > Note: This class should not be used directly
6537   *
6538   * @see {@link https://html.spec.whatwg.org/multipage/embedded-content.html}
6539   * @extends EventTarget
6540   * @abstract
6541   */
6542
6543  var Track = function (_EventTarget) {
6544    inherits(Track, _EventTarget);
6545
6546    /**
6547     * Create an instance of this class.
6548     *
6549     * @param {Object} [options={}]
6550     *        Object of option names and values
6551     *
6552     * @param {string} [options.kind='']
6553     *        A valid kind for the track type you are creating.
6554     *
6555     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
6556     *        A unique id for this AudioTrack.
6557     *
6558     * @param {string} [options.label='']
6559     *        The menu label for this track.
6560     *
6561     * @param {string} [options.language='']
6562     *        A valid two character language code.
6563     *
6564     * @abstract
6565     */
6566    function Track() {
6567      var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
6568      classCallCheck(this, Track);
6569
6570      var _this = possibleConstructorReturn(this, _EventTarget.call(this));
6571
6572      var trackProps = {
6573        id: options.id || 'vjs_track_' + newGUID(),
6574        kind: options.kind || '',
6575        label: options.label || '',
6576        language: options.language || ''
6577      };
6578
6579      /**
6580       * @memberof Track
6581       * @member {string} id
6582       *         The id of this track. Cannot be changed after creation.
6583       * @instance
6584       *
6585       * @readonly
6586       */
6587
6588      /**
6589       * @memberof Track
6590       * @member {string} kind
6591       *         The kind of track that this is. Cannot be changed after creation.
6592       * @instance
6593       *
6594       * @readonly
6595       */
6596
6597      /**
6598       * @memberof Track
6599       * @member {string} label
6600       *         The label of this track. Cannot be changed after creation.
6601       * @instance
6602       *
6603       * @readonly
6604       */
6605
6606      /**
6607       * @memberof Track
6608       * @member {string} language
6609       *         The two letter language code for this track. Cannot be changed after
6610       *         creation.
6611       * @instance
6612       *
6613       * @readonly
6614       */
6615
6616      var _loop = function _loop(key) {
6617        Object.defineProperty(_this, key, {
6618          get: function get$$1() {
6619            return trackProps[key];
6620          },
6621          set: function set$$1() {}
6622        });
6623      };
6624
6625      for (var key in trackProps) {
6626        _loop(key);
6627      }
6628      return _this;
6629    }
6630
6631    return Track;
6632  }(EventTarget);
6633
6634  /**
6635   * @file url.js
6636   * @module url
6637   */
6638
6639  /**
6640   * @typedef {Object} url:URLObject
6641   *
6642   * @property {string} protocol
6643   *           The protocol of the url that was parsed.
6644   *
6645   * @property {string} hostname
6646   *           The hostname of the url that was parsed.
6647   *
6648   * @property {string} port
6649   *           The port of the url that was parsed.
6650   *
6651   * @property {string} pathname
6652   *           The pathname of the url that was parsed.
6653   *
6654   * @property {string} search
6655   *           The search query of the url that was parsed.
6656   *
6657   * @property {string} hash
6658   *           The hash of the url that was parsed.
6659   *
6660   * @property {string} host
6661   *           The host of the url that was parsed.
6662   */
6663
6664  /**
6665   * Resolve and parse the elements of a URL.
6666   *
6667   * @param  {String} url
6668   *         The url to parse
6669   *
6670   * @return {url:URLObject}
6671   *         An object of url details
6672   */
6673  var parseUrl = function parseUrl(url) {
6674    var props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host'];
6675
6676    // add the url to an anchor and let the browser parse the URL
6677    var a = document_1.createElement('a');
6678
6679    a.href = url;
6680
6681    // IE8 (and 9?) Fix
6682    // ie8 doesn't parse the URL correctly until the anchor is actually
6683    // added to the body, and an innerHTML is needed to trigger the parsing
6684    var addToBody = a.host === '' && a.protocol !== 'file:';
6685    var div = void 0;
6686
6687    if (addToBody) {
6688      div = document_1.createElement('div');
6689      div.innerHTML = '<a href="' + url + '"></a>';
6690      a = div.firstChild;
6691      // prevent the div from affecting layout
6692      div.setAttribute('style', 'display:none; position:absolute;');
6693      document_1.body.appendChild(div);
6694    }
6695
6696    // Copy the specific URL properties to a new object
6697    // This is also needed for IE8 because the anchor loses its
6698    // properties when it's removed from the dom
6699    var details = {};
6700
6701    for (var i = 0; i < props.length; i++) {
6702      details[props[i]] = a[props[i]];
6703    }
6704
6705    // IE9 adds the port to the host property unlike everyone else. If
6706    // a port identifier is added for standard ports, strip it.
6707    if (details.protocol === 'http:') {
6708      details.host = details.host.replace(/:80$/, '');
6709    }
6710
6711    if (details.protocol === 'https:') {
6712      details.host = details.host.replace(/:443$/, '');
6713    }
6714
6715    if (!details.protocol) {
6716      details.protocol = window_1.location.protocol;
6717    }
6718
6719    if (addToBody) {
6720      document_1.body.removeChild(div);
6721    }
6722
6723    return details;
6724  };
6725
6726  /**
6727   * Get absolute version of relative URL. Used to tell flash correct URL.
6728   *
6729   *
6730   * @param  {string} url
6731   *         URL to make absolute
6732   *
6733   * @return {string}
6734   *         Absolute URL
6735   *
6736   * @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
6737   */
6738  var getAbsoluteURL = function getAbsoluteURL(url) {
6739    // Check if absolute URL
6740    if (!url.match(/^https?:\/\//)) {
6741      // Convert to absolute URL. Flash hosted off-site needs an absolute URL.
6742      var div = document_1.createElement('div');
6743
6744      div.innerHTML = '<a href="' + url + '">x</a>';
6745      url = div.firstChild.href;
6746    }
6747
6748    return url;
6749  };
6750
6751  /**
6752   * Returns the extension of the passed file name. It will return an empty string
6753   * if passed an invalid path.
6754   *
6755   * @param {string} path
6756   *        The fileName path like '/path/to/file.mp4'
6757   *
6758   * @returns {string}
6759   *          The extension in lower case or an empty string if no
6760   *          extension could be found.
6761   */
6762  var getFileExtension = function getFileExtension(path) {
6763    if (typeof path === 'string') {
6764      var splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/i;
6765      var pathParts = splitPathRe.exec(path);
6766
6767      if (pathParts) {
6768        return pathParts.pop().toLowerCase();
6769      }
6770    }
6771
6772    return '';
6773  };
6774
6775  /**
6776   * Returns whether the url passed is a cross domain request or not.
6777   *
6778   * @param {string} url
6779   *        The url to check.
6780   *
6781   * @return {boolean}
6782   *         Whether it is a cross domain request or not.
6783   */
6784  var isCrossOrigin = function isCrossOrigin(url) {
6785    var winLoc = window_1.location;
6786    var urlInfo = parseUrl(url);
6787
6788    // IE8 protocol relative urls will return ':' for protocol
6789    var srcProtocol = urlInfo.protocol === ':' ? winLoc.protocol : urlInfo.protocol;
6790
6791    // Check if url is for another domain/origin
6792    // IE8 doesn't know location.origin, so we won't rely on it here
6793    var crossOrigin = srcProtocol + urlInfo.host !== winLoc.protocol + winLoc.host;
6794
6795    return crossOrigin;
6796  };
6797
6798  var Url = /*#__PURE__*/Object.freeze({
6799    parseUrl: parseUrl,
6800    getAbsoluteURL: getAbsoluteURL,
6801    getFileExtension: getFileExtension,
6802    isCrossOrigin: isCrossOrigin
6803  });
6804
6805  var isFunction_1 = isFunction;
6806
6807  var toString$1 = Object.prototype.toString;
6808
6809  function isFunction(fn) {
6810    var string = toString$1.call(fn);
6811    return string === '[object Function]' || typeof fn === 'function' && string !== '[object RegExp]' || typeof window !== 'undefined' && (
6812    // IE8 and below
6813    fn === window.setTimeout || fn === window.alert || fn === window.confirm || fn === window.prompt);
6814  }
6815
6816  var isFunction$1 = /*#__PURE__*/Object.freeze({
6817    default: isFunction_1,
6818    __moduleExports: isFunction_1
6819  });
6820
6821  var trim_1 = createCommonjsModule(function (module, exports) {
6822    exports = module.exports = trim;
6823
6824    function trim(str) {
6825      return str.replace(/^\s*|\s*$/g, '');
6826    }
6827
6828    exports.left = function (str) {
6829      return str.replace(/^\s*/, '');
6830    };
6831
6832    exports.right = function (str) {
6833      return str.replace(/\s*$/, '');
6834    };
6835  });
6836  var trim_2 = trim_1.left;
6837  var trim_3 = trim_1.right;
6838
6839  var trim = /*#__PURE__*/Object.freeze({
6840    default: trim_1,
6841    __moduleExports: trim_1,
6842    left: trim_2,
6843    right: trim_3
6844  });
6845
6846  var isFunction$2 = ( isFunction$1 && isFunction_1 ) || isFunction$1;
6847
6848  var forEach_1 = forEach;
6849
6850  var toString$2 = Object.prototype.toString;
6851  var hasOwnProperty = Object.prototype.hasOwnProperty;
6852
6853  function forEach(list, iterator, context) {
6854      if (!isFunction$2(iterator)) {
6855          throw new TypeError('iterator must be a function');
6856      }
6857
6858      if (arguments.length < 3) {
6859          context = this;
6860      }
6861
6862      if (toString$2.call(list) === '[object Array]') forEachArray(list, iterator, context);else if (typeof list === 'string') forEachString(list, iterator, context);else forEachObject(list, iterator, context);
6863  }
6864
6865  function forEachArray(array, iterator, context) {
6866      for (var i = 0, len = array.length; i < len; i++) {
6867          if (hasOwnProperty.call(array, i)) {
6868              iterator.call(context, array[i], i, array);
6869          }
6870      }
6871  }
6872
6873  function forEachString(string, iterator, context) {
6874      for (var i = 0, len = string.length; i < len; i++) {
6875          // no such thing as a sparse string.
6876          iterator.call(context, string.charAt(i), i, string);
6877      }
6878  }
6879
6880  function forEachObject(object, iterator, context) {
6881      for (var k in object) {
6882          if (hasOwnProperty.call(object, k)) {
6883              iterator.call(context, object[k], k, object);
6884          }
6885      }
6886  }
6887
6888  var forEach$1 = /*#__PURE__*/Object.freeze({
6889    default: forEach_1,
6890    __moduleExports: forEach_1
6891  });
6892
6893  var trim$1 = ( trim && trim_1 ) || trim;
6894
6895  var forEach$2 = ( forEach$1 && forEach_1 ) || forEach$1;
6896
6897  var isArray = function isArray(arg) {
6898    return Object.prototype.toString.call(arg) === '[object Array]';
6899  };
6900
6901  var parseHeaders = function parseHeaders(headers) {
6902    if (!headers) return {};
6903
6904    var result = {};
6905
6906    forEach$2(trim$1(headers).split('\n'), function (row) {
6907      var index = row.indexOf(':'),
6908          key = trim$1(row.slice(0, index)).toLowerCase(),
6909          value = trim$1(row.slice(index + 1));
6910
6911      if (typeof result[key] === 'undefined') {
6912        result[key] = value;
6913      } else if (isArray(result[key])) {
6914        result[key].push(value);
6915      } else {
6916        result[key] = [result[key], value];
6917      }
6918    });
6919
6920    return result;
6921  };
6922
6923  var parseHeaders$1 = /*#__PURE__*/Object.freeze({
6924    default: parseHeaders,
6925    __moduleExports: parseHeaders
6926  });
6927
6928  var immutable = extend;
6929
6930  var hasOwnProperty$1 = Object.prototype.hasOwnProperty;
6931
6932  function extend() {
6933      var target = {};
6934
6935      for (var i = 0; i < arguments.length; i++) {
6936          var source = arguments[i];
6937
6938          for (var key in source) {
6939              if (hasOwnProperty$1.call(source, key)) {
6940                  target[key] = source[key];
6941              }
6942          }
6943      }
6944
6945      return target;
6946  }
6947
6948  var immutable$1 = /*#__PURE__*/Object.freeze({
6949    default: immutable,
6950    __moduleExports: immutable
6951  });
6952
6953  var parseHeaders$2 = ( parseHeaders$1 && parseHeaders ) || parseHeaders$1;
6954
6955  var xtend = ( immutable$1 && immutable ) || immutable$1;
6956
6957  var xhr = createXHR;
6958  createXHR.XMLHttpRequest = window_1.XMLHttpRequest || noop;
6959  createXHR.XDomainRequest = "withCredentials" in new createXHR.XMLHttpRequest() ? createXHR.XMLHttpRequest : window_1.XDomainRequest;
6960
6961  forEachArray$1(["get", "put", "post", "patch", "head", "delete"], function (method) {
6962      createXHR[method === "delete" ? "del" : method] = function (uri, options, callback) {
6963          options = initParams(uri, options, callback);
6964          options.method = method.toUpperCase();
6965          return _createXHR(options);
6966      };
6967  });
6968
6969  function forEachArray$1(array, iterator) {
6970      for (var i = 0; i < array.length; i++) {
6971          iterator(array[i]);
6972      }
6973  }
6974
6975  function isEmpty(obj) {
6976      for (var i in obj) {
6977          if (obj.hasOwnProperty(i)) return false;
vendor: 10,824 bytes, lines 6978-7336
6978      }
6979      return true;
6980  }
6981
6982  function initParams(uri, options, callback) {
6983      var params = uri;
6984
6985      if (isFunction$2(options)) {
6986          callback = options;
6987          if (typeof uri === "string") {
6988              params = { uri: uri };
6989          }
6990      } else {
6991          params = xtend(options, { uri: uri });
6992      }
6993
6994      params.callback = callback;
6995      return params;
6996  }
6997
6998  function createXHR(uri, options, callback) {
6999      options = initParams(uri, options, callback);
7000      return _createXHR(options);
7001  }
7002
7003  function _createXHR(options) {
7004      if (typeof options.callback === "undefined") {
7005          throw new Error("callback argument missing");
7006      }
7007
7008      var called = false;
7009      var callback = function cbOnce(err, response, body) {
7010          if (!called) {
7011              called = true;
7012              options.callback(err, response, body);
7013          }
7014      };
7015
7016      function readystatechange() {
7017          if (xhr.readyState === 4) {
7018              setTimeout(loadFunc, 0);
7019          }
7020      }
7021
7022      function getBody() {
7023          // Chrome with requestType=blob throws errors arround when even testing access to responseText
7024          var body = undefined;
7025
7026          if (xhr.response) {
7027              body = xhr.response;
7028          } else {
7029              body = xhr.responseText || getXml(xhr);
7030          }
7031
7032          if (isJson) {
7033              try {
7034                  body = JSON.parse(body);
7035              } catch (e) {}
7036          }
7037
7038          return body;
7039      }
7040
7041      function errorFunc(evt) {
7042          clearTimeout(timeoutTimer);
7043          if (!(evt instanceof Error)) {
7044              evt = new Error("" + (evt || "Unknown XMLHttpRequest Error"));
7045          }
7046          evt.statusCode = 0;
7047          return callback(evt, failureResponse);
7048      }
7049
7050      // will load the data & process the response in a special response object
7051      function loadFunc() {
7052          if (aborted) return;
7053          var status;
7054          clearTimeout(timeoutTimer);
7055          if (options.useXDR && xhr.status === undefined) {
7056              //IE8 CORS GET successful response doesn't have a status field, but body is fine
7057              status = 200;
7058          } else {
7059              status = xhr.status === 1223 ? 204 : xhr.status;
7060          }
7061          var response = failureResponse;
7062          var err = null;
7063
7064          if (status !== 0) {
7065              response = {
7066                  body: getBody(),
7067                  statusCode: status,
7068                  method: method,
7069                  headers: {},
7070                  url: uri,
7071                  rawRequest: xhr
7072              };
7073              if (xhr.getAllResponseHeaders) {
7074                  //remember xhr can in fact be XDR for CORS in IE
7075                  response.headers = parseHeaders$2(xhr.getAllResponseHeaders());
7076              }
7077          } else {
7078              err = new Error("Internal XMLHttpRequest Error");
7079          }
7080          return callback(err, response, response.body);
7081      }
7082
7083      var xhr = options.xhr || null;
7084
7085      if (!xhr) {
7086          if (options.cors || options.useXDR) {
7087              xhr = new createXHR.XDomainRequest();
7088          } else {
7089              xhr = new createXHR.XMLHttpRequest();
7090          }
7091      }
7092
7093      var key;
7094      var aborted;
7095      var uri = xhr.url = options.uri || options.url;
7096      var method = xhr.method = options.method || "GET";
7097      var body = options.body || options.data;
7098      var headers = xhr.headers = options.headers || {};
7099      var sync = !!options.sync;
7100      var isJson = false;
7101      var timeoutTimer;
7102      var failureResponse = {
7103          body: undefined,
7104          headers: {},
7105          statusCode: 0,
7106          method: method,
7107          url: uri,
7108          rawRequest: xhr
7109      };
7110
7111      if ("json" in options && options.json !== false) {
7112          isJson = true;
7113          headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json"); //Don't override existing accept header declared by user
7114          if (method !== "GET" && method !== "HEAD") {
7115              headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json"); //Don't override existing accept header declared by user
7116              body = JSON.stringify(options.json === true ? body : options.json);
7117          }
7118      }
7119
7120      xhr.onreadystatechange = readystatechange;
7121      xhr.onload = loadFunc;
7122      xhr.onerror = errorFunc;
7123      // IE9 must have onprogress be set to a unique function.
7124      xhr.onprogress = function () {
7125          // IE must die
7126      };
7127      xhr.onabort = function () {
7128          aborted = true;
7129      };
7130      xhr.ontimeout = errorFunc;
7131      xhr.open(method, uri, !sync, options.username, options.password);
7132      //has to be after open
7133      if (!sync) {
7134          xhr.withCredentials = !!options.withCredentials;
7135      }
7136      // Cannot set timeout with sync request
7137      // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly
7138      // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent
7139      if (!sync && options.timeout > 0) {
7140          timeoutTimer = setTimeout(function () {
7141              if (aborted) return;
7142              aborted = true; //IE9 may still call readystatechange
7143              xhr.abort("timeout");
7144              var e = new Error("XMLHttpRequest timeout");
7145              e.code = "ETIMEDOUT";
7146              errorFunc(e);
7147          }, options.timeout);
7148      }
7149
7150      if (xhr.setRequestHeader) {
7151          for (key in headers) {
7152              if (headers.hasOwnProperty(key)) {
7153                  xhr.setRequestHeader(key, headers[key]);
7154              }
7155          }
7156      } else if (options.headers && !isEmpty(options.headers)) {
7157          throw new Error("Headers cannot be set on an XDomainRequest object");
7158      }
7159
7160      if ("responseType" in options) {
7161          xhr.responseType = options.responseType;
7162      }
7163
7164      if ("beforeSend" in options && typeof options.beforeSend === "function") {
7165          options.beforeSend(xhr);
7166      }
7167
7168      // Microsoft Edge browser sends "undefined" when send is called with undefined value.
7169      // XMLHttpRequest spec says to pass null as body to indicate no body
7170      // See https://github.com/naugtur/xhr/issues/100.
7171      xhr.send(body || null);
7172
7173      return xhr;
7174  }
7175
7176  function getXml(xhr) {
7177      if (xhr.responseType === "document") {
7178          return xhr.responseXML;
7179      }
7180      var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror";
7181      if (xhr.responseType === "" && !firefoxBugTakenEffect) {
7182          return xhr.responseXML;
7183      }
7184
7185      return null;
7186  }
7187
7188  function noop() {}
7189
7190  /**
7191   * @file text-track.js
7192   */
7193
7194  /**
7195   * Takes a webvtt file contents and parses it into cues
7196   *
7197   * @param {string} srcContent
7198   *        webVTT file contents
7199   *
7200   * @param {TextTrack} track
7201   *        TextTrack to add cues to. Cues come from the srcContent.
7202   *
7203   * @private
7204   */
7205  var parseCues = function parseCues(srcContent, track) {
7206    var parser = new window_1.WebVTT.Parser(window_1, window_1.vttjs, window_1.WebVTT.StringDecoder());
7207    var errors = [];
7208
7209    parser.oncue = function (cue) {
7210      track.addCue(cue);
7211    };
7212
7213    parser.onparsingerror = function (error) {
7214      errors.push(error);
7215    };
7216
7217    parser.onflush = function () {
7218      track.trigger({
7219        type: 'loadeddata',
7220        target: track
7221      });
7222    };
7223
7224    parser.parse(srcContent);
7225    if (errors.length > 0) {
7226      if (window_1.console && window_1.console.groupCollapsed) {
7227        window_1.console.groupCollapsed('Text Track parsing errors for ' + track.src);
7228      }
7229      errors.forEach(function (error) {
7230        return log$1.error(error);
7231      });
7232      if (window_1.console && window_1.console.groupEnd) {
7233        window_1.console.groupEnd();
7234      }
7235    }
7236
7237    parser.flush();
7238  };
7239
7240  /**
7241   * Load a `TextTrack` from a specified url.
7242   *
7243   * @param {string} src
7244   *        Url to load track from.
7245   *
7246   * @param {TextTrack} track
7247   *        Track to add cues to. Comes from the content at the end of `url`.
7248   *
7249   * @private
7250   */
7251  var loadTrack = function loadTrack(src, track) {
7252    var opts = {
7253      uri: src
7254    };
7255    var crossOrigin = isCrossOrigin(src);
7256
7257    if (crossOrigin) {
7258      opts.cors = crossOrigin;
7259    }
7260
7261    xhr(opts, bind(this, function (err, response, responseBody) {
7262      if (err) {
7263        return log$1.error(err, response);
7264      }
7265
7266      track.loaded_ = true;
7267
7268      // Make sure that vttjs has loaded, otherwise, wait till it finished loading
7269      // NOTE: this is only used for the alt/video.novtt.js build
7270      if (typeof window_1.WebVTT !== 'function') {
7271        if (track.tech_) {
7272          var loadHandler = function loadHandler() {
7273            return parseCues(responseBody, track);
7274          };
7275
7276          track.tech_.on('vttjsloaded', loadHandler);
7277          track.tech_.on('vttjserror', function () {
7278            log$1.error('vttjs failed to load, stopping trying to process ' + track.src);
7279            track.tech_.off('vttjsloaded', loadHandler);
7280          });
7281        }
7282      } else {
7283        parseCues(responseBody, track);
7284      }
7285    }));
7286  };
7287
7288  /**
7289   * A representation of a single `TextTrack`.
7290   *
7291   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack}
7292   * @extends Track
7293   */
7294
7295  var TextTrack = function (_Track) {
7296    inherits(TextTrack, _Track);
7297
7298    /**
7299     * Create an instance of this class.
7300     *
7301     * @param {Object} options={}
7302     *        Object of option names and values
7303     *
7304     * @param {Tech} options.tech
7305     *        A reference to the tech that owns this TextTrack.
7306     *
7307     * @param {TextTrack~Kind} [options.kind='subtitles']
7308     *        A valid text track kind.
7309     *
7310     * @param {TextTrack~Mode} [options.mode='disabled']
7311     *        A valid text track mode.
7312     *
7313     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
7314     *        A unique id for this TextTrack.
7315     *
7316     * @param {string} [options.label='']
7317     *        The menu label for this track.
7318     *
7319     * @param {string} [options.language='']
7320     *        A valid two character language code.
7321     *
7322     * @param {string} [options.srclang='']
7323     *        A valid two character language code. An alternative, but deprioritized
7324     *        version of `options.language`
7325     *
7326     * @param {string} [options.src]
7327     *        A url to TextTrack cues.
7328     *
7329     * @param {boolean} [options.default]
7330     *        If this track should default to on or off.
7331     */
7332    function TextTrack() {
7333      var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
7334      classCallCheck(this, TextTrack);
7335
7336      if (!options.tech) {
vendor: 17,280 bytes, lines 7337-7951
7337        throw new Error('A tech was not provided.');
7338      }
7339
7340      var settings = mergeOptions(options, {
7341        kind: TextTrackKind[options.kind] || 'subtitles',
7342        language: options.language || options.srclang || ''
7343      });
7344      var mode = TextTrackMode[settings.mode] || 'disabled';
7345      var default_ = settings.default;
7346
7347      if (settings.kind === 'metadata' || settings.kind === 'chapters') {
7348        mode = 'hidden';
7349      }
7350
7351      var _this = possibleConstructorReturn(this, _Track.call(this, settings));
7352
7353      _this.tech_ = settings.tech;
7354
7355      _this.cues_ = [];
7356      _this.activeCues_ = [];
7357
7358      var cues = new TextTrackCueList(_this.cues_);
7359      var activeCues = new TextTrackCueList(_this.activeCues_);
7360      var changed = false;
7361      var timeupdateHandler = bind(_this, function () {
7362
7363        // Accessing this.activeCues for the side-effects of updating itself
7364        // due to it's nature as a getter function. Do not remove or cues will
7365        // stop updating!
7366        /* eslint-disable no-unused-expressions */
7367        this.activeCues;
7368        /* eslint-enable no-unused-expressions */
7369        if (changed) {
7370          this.trigger('cuechange');
7371          changed = false;
7372        }
7373      });
7374
7375      if (mode !== 'disabled') {
7376        _this.tech_.ready(function () {
7377          _this.tech_.on('timeupdate', timeupdateHandler);
7378        }, true);
7379      }
7380
7381      Object.defineProperties(_this, {
7382        /**
7383         * @memberof TextTrack
7384         * @member {boolean} default
7385         *         If this track was set to be on or off by default. Cannot be changed after
7386         *         creation.
7387         * @instance
7388         *
7389         * @readonly
7390         */
7391        default: {
7392          get: function get$$1() {
7393            return default_;
7394          },
7395          set: function set$$1() {}
7396        },
7397
7398        /**
7399         * @memberof TextTrack
7400         * @member {string} mode
7401         *         Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will
7402         *         not be set if setting to an invalid mode.
7403         * @instance
7404         *
7405         * @fires TextTrack#modechange
7406         */
7407        mode: {
7408          get: function get$$1() {
7409            return mode;
7410          },
7411          set: function set$$1(newMode) {
7412            var _this2 = this;
7413
7414            if (!TextTrackMode[newMode]) {
7415              return;
7416            }
7417            mode = newMode;
7418            if (mode === 'showing') {
7419
7420              this.tech_.ready(function () {
7421                _this2.tech_.on('timeupdate', timeupdateHandler);
7422              }, true);
7423            }
7424            /**
7425             * An event that fires when mode changes on this track. This allows
7426             * the TextTrackList that holds this track to act accordingly.
7427             *
7428             * > Note: This is not part of the spec!
7429             *
7430             * @event TextTrack#modechange
7431             * @type {EventTarget~Event}
7432             */
7433            this.trigger('modechange');
7434          }
7435        },
7436
7437        /**
7438         * @memberof TextTrack
7439         * @member {TextTrackCueList} cues
7440         *         The text track cue list for this TextTrack.
7441         * @instance
7442         */
7443        cues: {
7444          get: function get$$1() {
7445            if (!this.loaded_) {
7446              return null;
7447            }
7448
7449            return cues;
7450          },
7451          set: function set$$1() {}
7452        },
7453
7454        /**
7455         * @memberof TextTrack
7456         * @member {TextTrackCueList} activeCues
7457         *         The list text track cues that are currently active for this TextTrack.
7458         * @instance
7459         */
7460        activeCues: {
7461          get: function get$$1() {
7462            if (!this.loaded_) {
7463              return null;
7464            }
7465
7466            // nothing to do
7467            if (this.cues.length === 0) {
7468              return activeCues;
7469            }
7470
7471            var ct = this.tech_.currentTime();
7472            var active = [];
7473
7474            for (var i = 0, l = this.cues.length; i < l; i++) {
7475              var cue = this.cues[i];
7476
7477              if (cue.startTime <= ct && cue.endTime >= ct) {
7478                active.push(cue);
7479              } else if (cue.startTime === cue.endTime && cue.startTime <= ct && cue.startTime + 0.5 >= ct) {
7480                active.push(cue);
7481              }
7482            }
7483
7484            changed = false;
7485
7486            if (active.length !== this.activeCues_.length) {
7487              changed = true;
7488            } else {
7489              for (var _i = 0; _i < active.length; _i++) {
7490                if (this.activeCues_.indexOf(active[_i]) === -1) {
7491                  changed = true;
7492                }
7493              }
7494            }
7495
7496            this.activeCues_ = active;
7497            activeCues.setCues_(this.activeCues_);
7498
7499            return activeCues;
7500          },
7501          set: function set$$1() {}
7502        }
7503      });
7504
7505      if (settings.src) {
7506        _this.src = settings.src;
7507        loadTrack(settings.src, _this);
7508      } else {
7509        _this.loaded_ = true;
7510      }
7511      return _this;
7512    }
7513
7514    /**
7515     * Add a cue to the internal list of cues.
7516     *
7517     * @param {TextTrack~Cue} cue
7518     *        The cue to add to our internal list
7519     */
7520
7521
7522    TextTrack.prototype.addCue = function addCue(originalCue) {
7523      var cue = originalCue;
7524
7525      if (window_1.vttjs && !(originalCue instanceof window_1.vttjs.VTTCue)) {
7526        cue = new window_1.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text);
7527
7528        for (var prop in originalCue) {
7529          if (!(prop in cue)) {
7530            cue[prop] = originalCue[prop];
7531          }
7532        }
7533
7534        // make sure that `id` is copied over
7535        cue.id = originalCue.id;
7536        cue.originalCue_ = originalCue;
7537      }
7538
7539      var tracks = this.tech_.textTracks();
7540
7541      for (var i = 0; i < tracks.length; i++) {
7542        if (tracks[i] !== this) {
7543          tracks[i].removeCue(cue);
7544        }
7545      }
7546
7547      this.cues_.push(cue);
7548      this.cues.setCues_(this.cues_);
7549    };
7550
7551    /**
7552     * Remove a cue from our internal list
7553     *
7554     * @param {TextTrack~Cue} removeCue
7555     *        The cue to remove from our internal list
7556     */
7557
7558
7559    TextTrack.prototype.removeCue = function removeCue(_removeCue) {
7560      var i = this.cues_.length;
7561
7562      while (i--) {
7563        var cue = this.cues_[i];
7564
7565        if (cue === _removeCue || cue.originalCue_ && cue.originalCue_ === _removeCue) {
7566          this.cues_.splice(i, 1);
7567          this.cues.setCues_(this.cues_);
7568          break;
7569        }
7570      }
7571    };
7572
7573    return TextTrack;
7574  }(Track);
7575
7576  /**
7577   * cuechange - One or more cues in the track have become active or stopped being active.
7578   */
7579
7580
7581  TextTrack.prototype.allowedEvents_ = {
7582    cuechange: 'cuechange'
7583  };
7584
7585  /**
7586   * A representation of a single `AudioTrack`. If it is part of an {@link AudioTrackList}
7587   * only one `AudioTrack` in the list will be enabled at a time.
7588   *
7589   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack}
7590   * @extends Track
7591   */
7592
7593  var AudioTrack = function (_Track) {
7594    inherits(AudioTrack, _Track);
7595
7596    /**
7597     * Create an instance of this class.
7598     *
7599     * @param {Object} [options={}]
7600     *        Object of option names and values
7601     *
7602     * @param {AudioTrack~Kind} [options.kind='']
7603     *        A valid audio track kind
7604     *
7605     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
7606     *        A unique id for this AudioTrack.
7607     *
7608     * @param {string} [options.label='']
7609     *        The menu label for this track.
7610     *
7611     * @param {string} [options.language='']
7612     *        A valid two character language code.
7613     *
7614     * @param {boolean} [options.enabled]
7615     *        If this track is the one that is currently playing. If this track is part of
7616     *        an {@link AudioTrackList}, only one {@link AudioTrack} will be enabled.
7617     */
7618    function AudioTrack() {
7619      var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
7620      classCallCheck(this, AudioTrack);
7621
7622      var settings = mergeOptions(options, {
7623        kind: AudioTrackKind[options.kind] || ''
7624      });
7625
7626      var _this = possibleConstructorReturn(this, _Track.call(this, settings));
7627
7628      var enabled = false;
7629
7630      /**
7631       * @memberof AudioTrack
7632       * @member {boolean} enabled
7633       *         If this `AudioTrack` is enabled or not. When setting this will
7634       *         fire {@link AudioTrack#enabledchange} if the state of enabled is changed.
7635       * @instance
7636       *
7637       * @fires VideoTrack#selectedchange
7638       */
7639      Object.defineProperty(_this, 'enabled', {
7640        get: function get$$1() {
7641          return enabled;
7642        },
7643        set: function set$$1(newEnabled) {
7644          // an invalid or unchanged value
7645          if (typeof newEnabled !== 'boolean' || newEnabled === enabled) {
7646            return;
7647          }
7648          enabled = newEnabled;
7649
7650          /**
7651           * An event that fires when enabled changes on this track. This allows
7652           * the AudioTrackList that holds this track to act accordingly.
7653           *
7654           * > Note: This is not part of the spec! Native tracks will do
7655           *         this internally without an event.
7656           *
7657           * @event AudioTrack#enabledchange
7658           * @type {EventTarget~Event}
7659           */
7660          this.trigger('enabledchange');
7661        }
7662      });
7663
7664      // if the user sets this track to selected then
7665      // set selected to that true value otherwise
7666      // we keep it false
7667      if (settings.enabled) {
7668        _this.enabled = settings.enabled;
7669      }
7670      _this.loaded_ = true;
7671      return _this;
7672    }
7673
7674    return AudioTrack;
7675  }(Track);
7676
7677  /**
7678   * A representation of a single `VideoTrack`.
7679   *
7680   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack}
7681   * @extends Track
7682   */
7683
7684  var VideoTrack = function (_Track) {
7685    inherits(VideoTrack, _Track);
7686
7687    /**
7688     * Create an instance of this class.
7689     *
7690     * @param {Object} [options={}]
7691     *        Object of option names and values
7692     *
7693     * @param {string} [options.kind='']
7694     *        A valid {@link VideoTrack~Kind}
7695     *
7696     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
7697     *        A unique id for this AudioTrack.
7698     *
7699     * @param {string} [options.label='']
7700     *        The menu label for this track.
7701     *
7702     * @param {string} [options.language='']
7703     *        A valid two character language code.
7704     *
7705     * @param {boolean} [options.selected]
7706     *        If this track is the one that is currently playing.
7707     */
7708    function VideoTrack() {
7709      var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
7710      classCallCheck(this, VideoTrack);
7711
7712      var settings = mergeOptions(options, {
7713        kind: VideoTrackKind[options.kind] || ''
7714      });
7715
7716      var _this = possibleConstructorReturn(this, _Track.call(this, settings));
7717
7718      var selected = false;
7719
7720      /**
7721       * @memberof VideoTrack
7722       * @member {boolean} selected
7723       *         If this `VideoTrack` is selected or not. When setting this will
7724       *         fire {@link VideoTrack#selectedchange} if the state of selected changed.
7725       * @instance
7726       *
7727       * @fires VideoTrack#selectedchange
7728       */
7729      Object.defineProperty(_this, 'selected', {
7730        get: function get$$1() {
7731          return selected;
7732        },
7733        set: function set$$1(newSelected) {
7734          // an invalid or unchanged value
7735          if (typeof newSelected !== 'boolean' || newSelected === selected) {
7736            return;
7737          }
7738          selected = newSelected;
7739
7740          /**
7741           * An event that fires when selected changes on this track. This allows
7742           * the VideoTrackList that holds this track to act accordingly.
7743           *
7744           * > Note: This is not part of the spec! Native tracks will do
7745           *         this internally without an event.
7746           *
7747           * @event VideoTrack#selectedchange
7748           * @type {EventTarget~Event}
7749           */
7750          this.trigger('selectedchange');
7751        }
7752      });
7753
7754      // if the user sets this track to selected then
7755      // set selected to that true value otherwise
7756      // we keep it false
7757      if (settings.selected) {
7758        _this.selected = settings.selected;
7759      }
7760      return _this;
7761    }
7762
7763    return VideoTrack;
7764  }(Track);
7765
7766  /**
7767   * @file html-track-element.js
7768   */
7769
7770  /**
7771   * @memberof HTMLTrackElement
7772   * @typedef {HTMLTrackElement~ReadyState}
7773   * @enum {number}
7774   */
7775  var NONE = 0;
7776  var LOADING = 1;
7777  var LOADED = 2;
7778  var ERROR = 3;
7779
7780  /**
7781   * A single track represented in the DOM.
7782   *
7783   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement}
7784   * @extends EventTarget
7785   */
7786
7787  var HTMLTrackElement = function (_EventTarget) {
7788    inherits(HTMLTrackElement, _EventTarget);
7789
7790    /**
7791     * Create an instance of this class.
7792     *
7793     * @param {Object} options={}
7794     *        Object of option names and values
7795     *
7796     * @param {Tech} options.tech
7797     *        A reference to the tech that owns this HTMLTrackElement.
7798     *
7799     * @param {TextTrack~Kind} [options.kind='subtitles']
7800     *        A valid text track kind.
7801     *
7802     * @param {TextTrack~Mode} [options.mode='disabled']
7803     *        A valid text track mode.
7804     *
7805     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
7806     *        A unique id for this TextTrack.
7807     *
7808     * @param {string} [options.label='']
7809     *        The menu label for this track.
7810     *
7811     * @param {string} [options.language='']
7812     *        A valid two character language code.
7813     *
7814     * @param {string} [options.srclang='']
7815     *        A valid two character language code. An alternative, but deprioritized
7816     *        vesion of `options.language`
7817     *
7818     * @param {string} [options.src]
7819     *        A url to TextTrack cues.
7820     *
7821     * @param {boolean} [options.default]
7822     *        If this track should default to on or off.
7823     */
7824    function HTMLTrackElement() {
7825      var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
7826      classCallCheck(this, HTMLTrackElement);
7827
7828      var _this = possibleConstructorReturn(this, _EventTarget.call(this));
7829
7830      var readyState = void 0;
7831
7832      var track = new TextTrack(options);
7833
7834      _this.kind = track.kind;
7835      _this.src = track.src;
7836      _this.srclang = track.language;
7837      _this.label = track.label;
7838      _this.default = track.default;
7839
7840      Object.defineProperties(_this, {
7841
7842        /**
7843         * @memberof HTMLTrackElement
7844         * @member {HTMLTrackElement~ReadyState} readyState
7845         *         The current ready state of the track element.
7846         * @instance
7847         */
7848        readyState: {
7849          get: function get$$1() {
7850            return readyState;
7851          }
7852        },
7853
7854        /**
7855         * @memberof HTMLTrackElement
7856         * @member {TextTrack} track
7857         *         The underlying TextTrack object.
7858         * @instance
7859         *
7860         */
7861        track: {
7862          get: function get$$1() {
7863            return track;
7864          }
7865        }
7866      });
7867
7868      readyState = NONE;
7869
7870      /**
7871       * @listens TextTrack#loadeddata
7872       * @fires HTMLTrackElement#load
7873       */
7874      track.addEventListener('loadeddata', function () {
7875        readyState = LOADED;
7876
7877        _this.trigger({
7878          type: 'load',
7879          target: _this
7880        });
7881      });
7882      return _this;
7883    }
7884
7885    return HTMLTrackElement;
7886  }(EventTarget);
7887
7888  HTMLTrackElement.prototype.allowedEvents_ = {
7889    load: 'load'
7890  };
7891
7892  HTMLTrackElement.NONE = NONE;
7893  HTMLTrackElement.LOADING = LOADING;
7894  HTMLTrackElement.LOADED = LOADED;
7895  HTMLTrackElement.ERROR = ERROR;
7896
7897  /*
7898   * This file contains all track properties that are used in
7899   * player.js, tech.js, html5.js and possibly other techs in the future.
7900   */
7901
7902  var NORMAL = {
7903    audio: {
7904      ListClass: AudioTrackList,
7905      TrackClass: AudioTrack,
7906      capitalName: 'Audio'
7907    },
7908    video: {
7909      ListClass: VideoTrackList,
7910      TrackClass: VideoTrack,
7911      capitalName: 'Video'
7912    },
7913    text: {
7914      ListClass: TextTrackList,
7915      TrackClass: TextTrack,
7916      capitalName: 'Text'
7917    }
7918  };
7919
7920  Object.keys(NORMAL).forEach(function (type) {
7921    NORMAL[type].getterName = type + 'Tracks';
7922    NORMAL[type].privateName = type + 'Tracks_';
7923  });
7924
7925  var REMOTE = {
7926    remoteText: {
7927      ListClass: TextTrackList,
7928      TrackClass: TextTrack,
7929      capitalName: 'RemoteText',
7930      getterName: 'remoteTextTracks',
7931      privateName: 'remoteTextTracks_'
7932    },
7933    remoteTextEl: {
7934      ListClass: HtmlTrackElementList,
7935      TrackClass: HTMLTrackElement,
7936      capitalName: 'RemoteTextTrackEls',
7937      getterName: 'remoteTextTrackEls',
7938      privateName: 'remoteTextTrackEls_'
7939    }
7940  };
7941
7942  var ALL = mergeOptions(NORMAL, REMOTE);
7943
7944  REMOTE.names = Object.keys(REMOTE);
7945  NORMAL.names = Object.keys(NORMAL);
7946  ALL.names = [].concat(REMOTE.names).concat(NORMAL.names);
7947
7948  /**
7949   * Copyright 2013 vtt.js Contributors
7950   *
7951   * Licensed under the Apache License, Version 2.0 (the "License");
7952   * you may not use this file except in compliance with the License.
7953   * You may obtain a copy of the License at
7954   *
7955   *   http://www.apache.org/licenses/LICENSE-2.0
7956   *
7957   * Unless required by applicable law or agreed to in writing, software
7958   * distributed under the License is distributed on an "AS IS" BASIS,
7959   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7960   * See the License for the specific language governing permissions and
7961   * limitations under the License.
7962   */
7963
7964  /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
7965  /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
7966  var _objCreate = Object.create || function () {
7967    function F() {}
7968    return function (o) {
7969      if (arguments.length !== 1) {
7970        throw new Error('Object.create shim only accepts one parameter.');
7971      }
7972      F.prototype = o;
7973      return new F();
7974    };
7975  }();
7976
7977  // Creates a new ParserError object from an errorData object. The errorData
7978  // object should have default code and message properties. The default message
7979  // property can be overriden by passing in a message parameter.
7980  // See ParsingError.Errors below for acceptable errors.
7981  function ParsingError(errorData, message) {
7982    this.name = "ParsingError";
7983    this.code = errorData.code;
7984    this.message = message || errorData.message;
7985  }
7986  ParsingError.prototype = _objCreate(Error.prototype);
7987  ParsingError.prototype.constructor = ParsingError;
7988
7989  // ParsingError metadata for acceptable ParsingErrors.
7990  ParsingError.Errors = {
7991    BadSignature: {
7992      code: 0,
7993      message: "Malformed WebVTT signature."
7994    },
7995    BadTimeStamp: {
7996      code: 1,
7997      message: "Malformed time stamp."
7998    }
7999  };
8000
8001  // Try to parse input as a time stamp.
8002  function parseTimeStamp(input) {
8003
8004    function computeSeconds(h, m, s, f) {
8005      return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000;
8006    }
8007
8008    var m = input.match(/^(\d+):(\d{2})(:\d{2})?\.(\d{3})/);
8009    if (!m) {
8010      return null;
8011    }
8012
8013    if (m[3]) {
8014      // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds]
8015      return computeSeconds(m[1], m[2], m[3].replace(":", ""), m[4]);
8016    } else if (m[1] > 59) {
8017      // Timestamp takes the form of [hours]:[minutes].[milliseconds]
8018      // First position is hours as it's over 59.
8019      return computeSeconds(m[1], m[2], 0, m[4]);
8020    } else {
8021      // Timestamp takes the form of [minutes]:[seconds].[milliseconds]
8022      return computeSeconds(0, m[1], m[2], m[4]);
8023    }
8024  }
8025
8026  // A settings object holds key/value pairs and will ignore anything but the first
8027  // assignment to a specific key.
8028  function Settings() {
8029    this.values = _objCreate(null);
8030  }
8031
8032  Settings.prototype = {
8033    // Only accept the first assignment to any key.
8034    set: function set(k, v) {
8035      if (!this.get(k) && v !== "") {
8036        this.values[k] = v;
8037      }
8038    },
8039    // Return the value for a key, or a default value.
8040    // If 'defaultKey' is passed then 'dflt' is assumed to be an object with
8041    // a number of possible default values as properties where 'defaultKey' is
8042    // the key of the property that will be chosen; otherwise it's assumed to be
8043    // a single value.
8044    get: function get(k, dflt, defaultKey) {
8045      if (defaultKey) {
8046        return this.has(k) ? this.values[k] : dflt[defaultKey];
8047      }
8048      return this.has(k) ? this.values[k] : dflt;
8049    },
8050    // Check whether we have a value for a key.
8051    has: function has(k) {
8052      return k in this.values;
8053    },
8054    // Accept a setting if its one of the given alternatives.
8055    alt: function alt(k, v, a) {
8056      for (var n = 0; n < a.length; ++n) {
8057        if (v === a[n]) {
8058          this.set(k, v);
8059          break;
8060        }
8061      }
8062    },
8063    // Accept a setting if its a valid (signed) integer.
8064    integer: function integer(k, v) {
8065      if (/^-?\d+$/.test(v)) {
8066        // integer
8067        this.set(k, parseInt(v, 10));
8068      }
8069    },
8070    // Accept a setting if its a valid percentage.
8071    percent: function percent(k, v) {
8072      var m;
8073      if (m = v.match(/^([\d]{1,3})(\.[\d]*)?%$/)) {
8074        v = parseFloat(v);
8075        if (v >= 0 && v <= 100) {
8076          this.set(k, v);
8077          return true;
8078        }
8079      }
8080      return false;
vendor: 9,025 bytes, lines 8081-8347
8081    }
8082  };
8083
8084  // Helper function to parse input into groups separated by 'groupDelim', and
8085  // interprete each group as a key/value pair separated by 'keyValueDelim'.
8086  function parseOptions(input, callback, keyValueDelim, groupDelim) {
8087    var groups = groupDelim ? input.split(groupDelim) : [input];
8088    for (var i in groups) {
8089      if (typeof groups[i] !== "string") {
8090        continue;
8091      }
8092      var kv = groups[i].split(keyValueDelim);
8093      if (kv.length !== 2) {
8094        continue;
8095      }
8096      var k = kv[0];
8097      var v = kv[1];
8098      callback(k, v);
8099    }
8100  }
8101
8102  function parseCue(input, cue, regionList) {
8103    // Remember the original input if we need to throw an error.
8104    var oInput = input;
8105    // 4.1 WebVTT timestamp
8106    function consumeTimeStamp() {
8107      var ts = parseTimeStamp(input);
8108      if (ts === null) {
8109        throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed timestamp: " + oInput);
8110      }
8111      // Remove time stamp from input.
8112      input = input.replace(/^[^\sa-zA-Z-]+/, "");
8113      return ts;
8114    }
8115
8116    // 4.4.2 WebVTT cue settings
8117    function consumeCueSettings(input, cue) {
8118      var settings = new Settings();
8119
8120      parseOptions(input, function (k, v) {
8121        switch (k) {
8122          case "region":
8123            // Find the last region we parsed with the same region id.
8124            for (var i = regionList.length - 1; i >= 0; i--) {
8125              if (regionList[i].id === v) {
8126                settings.set(k, regionList[i].region);
8127                break;
8128              }
8129            }
8130            break;
8131          case "vertical":
8132            settings.alt(k, v, ["rl", "lr"]);
8133            break;
8134          case "line":
8135            var vals = v.split(","),
8136                vals0 = vals[0];
8137            settings.integer(k, vals0);
8138            settings.percent(k, vals0) ? settings.set("snapToLines", false) : null;
8139            settings.alt(k, vals0, ["auto"]);
8140            if (vals.length === 2) {
8141              settings.alt("lineAlign", vals[1], ["start", "middle", "end"]);
8142            }
8143            break;
8144          case "position":
8145            vals = v.split(",");
8146            settings.percent(k, vals[0]);
8147            if (vals.length === 2) {
8148              settings.alt("positionAlign", vals[1], ["start", "middle", "end"]);
8149            }
8150            break;
8151          case "size":
8152            settings.percent(k, v);
8153            break;
8154          case "align":
8155            settings.alt(k, v, ["start", "middle", "end", "left", "right"]);
8156            break;
8157        }
8158      }, /:/, /\s/);
8159
8160      // Apply default values for any missing fields.
8161      cue.region = settings.get("region", null);
8162      cue.vertical = settings.get("vertical", "");
8163      cue.line = settings.get("line", "auto");
8164      cue.lineAlign = settings.get("lineAlign", "start");
8165      cue.snapToLines = settings.get("snapToLines", true);
8166      cue.size = settings.get("size", 100);
8167      cue.align = settings.get("align", "middle");
8168      cue.position = settings.get("position", {
8169        start: 0,
8170        left: 0,
8171        middle: 50,
8172        end: 100,
8173        right: 100
8174      }, cue.align);
8175      cue.positionAlign = settings.get("positionAlign", {
8176        start: "start",
8177        left: "start",
8178        middle: "middle",
8179        end: "end",
8180        right: "end"
8181      }, cue.align);
8182    }
8183
8184    function skipWhitespace() {
8185      input = input.replace(/^\s+/, "");
8186    }
8187
8188    // 4.1 WebVTT cue timings.
8189    skipWhitespace();
8190    cue.startTime = consumeTimeStamp(); // (1) collect cue start time
8191    skipWhitespace();
8192    if (input.substr(0, 3) !== "-->") {
8193      // (3) next characters must match "-->"
8194      throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed time stamp (time stamps must be separated by '-->'): " + oInput);
8195    }
8196    input = input.substr(3);
8197    skipWhitespace();
8198    cue.endTime = consumeTimeStamp(); // (5) collect cue end time
8199
8200    // 4.1 WebVTT cue settings list.
8201    skipWhitespace();
8202    consumeCueSettings(input, cue);
8203  }
8204
8205  var ESCAPE = {
8206    "&amp;": "&",
8207    "&lt;": "<",
8208    "&gt;": ">",
8209    "&lrm;": "\u200E",
8210    "&rlm;": "\u200F",
8211    "&nbsp;": "\xA0"
8212  };
8213
8214  var TAG_NAME = {
8215    c: "span",
8216    i: "i",
8217    b: "b",
8218    u: "u",
8219    ruby: "ruby",
8220    rt: "rt",
8221    v: "span",
8222    lang: "span"
8223  };
8224
8225  var TAG_ANNOTATION = {
8226    v: "title",
8227    lang: "lang"
8228  };
8229
8230  var NEEDS_PARENT = {
8231    rt: "ruby"
8232  };
8233
8234  // Parse content into a document fragment.
8235  function parseContent(window, input) {
8236    function nextToken() {
8237      // Check for end-of-string.
8238      if (!input) {
8239        return null;
8240      }
8241
8242      // Consume 'n' characters from the input.
8243      function consume(result) {
8244        input = input.substr(result.length);
8245        return result;
8246      }
8247
8248      var m = input.match(/^([^<]*)(<[^>]*>?)?/);
8249      // If there is some text before the next tag, return it, otherwise return
8250      // the tag.
8251      return consume(m[1] ? m[1] : m[2]);
8252    }
8253
8254    // Unescape a string 's'.
8255    function unescape1(e) {
8256      return ESCAPE[e];
8257    }
8258    function unescape(s) {
8259      while (m = s.match(/&(amp|lt|gt|lrm|rlm|nbsp);/)) {
8260        s = s.replace(m[0], unescape1);
8261      }
8262      return s;
8263    }
8264
8265    function shouldAdd(current, element) {
8266      return !NEEDS_PARENT[element.localName] || NEEDS_PARENT[element.localName] === current.localName;
8267    }
8268
8269    // Create an element for this tag.
8270    function createElement(type, annotation) {
8271      var tagName = TAG_NAME[type];
8272      if (!tagName) {
8273        return null;
8274      }
8275      var element = window.document.createElement(tagName);
8276      element.localName = tagName;
8277      var name = TAG_ANNOTATION[type];
8278      if (name && annotation) {
8279        element[name] = annotation.trim();
8280      }
8281      return element;
8282    }
8283
8284    var rootDiv = window.document.createElement("div"),
8285        current = rootDiv,
8286        t,
8287        tagStack = [];
8288
8289    while ((t = nextToken()) !== null) {
8290      if (t[0] === '<') {
8291        if (t[1] === "/") {
8292          // If the closing tag matches, move back up to the parent node.
8293          if (tagStack.length && tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) {
8294            tagStack.pop();
8295            current = current.parentNode;
8296          }
8297          // Otherwise just ignore the end tag.
8298          continue;
8299        }
8300        var ts = parseTimeStamp(t.substr(1, t.length - 2));
8301        var node;
8302        if (ts) {
8303          // Timestamps are lead nodes as well.
8304          node = window.document.createProcessingInstruction("timestamp", ts);
8305          current.appendChild(node);
8306          continue;
8307        }
8308        var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/);
8309        // If we can't parse the tag, skip to the next tag.
8310        if (!m) {
8311          continue;
8312        }
8313        // Try to construct an element, and ignore the tag if we couldn't.
8314        node = createElement(m[1], m[3]);
8315        if (!node) {
8316          continue;
8317        }
8318        // Determine if the tag should be added based on the context of where it
8319        // is placed in the cuetext.
8320        if (!shouldAdd(current, node)) {
8321          continue;
8322        }
8323        // Set the class list (as a list of classes, separated by space).
8324        if (m[2]) {
8325          node.className = m[2].substr(1).replace('.', ' ');
8326        }
8327        // Append the node to the current node, and enter the scope of the new
8328        // node.
8329        tagStack.push(m[1]);
8330        current.appendChild(node);
8331        current = node;
8332        continue;
8333      }
8334
8335      // Text nodes are leaf nodes.
8336      current.appendChild(window.document.createTextNode(unescape(t)));
8337    }
8338
8339    return rootDiv;
8340  }
8341
8342  // This is a list of all the Unicode characters that have a strong
8343  // right-to-left category. What this means is that these characters are
8344  // written right-to-left for sure. It was generated by pulling all the strong
8345  // right-to-left characters out of the Unicode data table. That table can
8346  // found at: http://www.unicode.org/Public/UNIDATA/UnicodeData.txt
8347  var strongRTLRanges = [[0x5be, 0x5be], [0x5c0, 0x5c0], [0x5c3, 0x5c3], [0x5c6, 0x5c6], [0x5d0, 0x5ea], [0x5f0, 0x5f4], [0x608, 0x608], [0x60b, 0x60b], [0x60d, 0x60d], [0x61b, 0x61b], [0x61e, 0x64a], [0x66d, 0x66f], [0x671, 0x6d5], [0x6e5, 0x6e6], [0x6ee, 0x6ef], [0x6fa, 0x70d], [0x70f, 0x710], [0x712, 0x72f], [0x74d, 0x7a5], [0x7b1, 0x7b1], [0x7c0, 0x7ea], [0x7f4, 0x7f5], [0x7fa, 0x7fa], [0x800, 0x815], [0x81a, 0x81a], [0x824, 0x824], [0x828, 0x828], [0x830, 0x83e], [0x840, 0x858], [0x85e, 0x85e], [0x8a0, 0x8a0], [0x8a2, 0x8ac], [0x200f, 0x200f], [0xfb1d, 0xfb1d], [0xfb1f, 0xfb28], [0xfb2a, 0xfb36], [0xfb38, 0xfb3c], [0xfb3e, 0xfb3e], [0xfb40, 0xfb41], [0xfb43, 0xfb44], [0xfb46, 0xfbc1], [0xfbd3, 0xfd3d], [0xfd50, 0xfd8f], [0xfd92, 0xfdc7], [0xfdf0, 0xfdfc], [0xfe70, 0xfe74], [0xfe76, 0xfefc], [0x10800, 0x10805], [0x10808, 0x10808], [0x1080a, 0x10835], [0x10837, 0x10838], [0x1083c, 0x1083c], [0x1083f, 0x10855], [0x10857, 0x1085f], [0x10900, 0x1091b], [0x10920, 0x10939], [0x1093f, 
vendor: 18,697 bytes, lines 8347-8862
83470x1093f], [0x10980, 0x109b7], [0x109be, 0x109bf], [0x10a00, 0x10a00], [0x10a10, 0x10a13], [0x10a15, 0x10a17], [0x10a19, 0x10a33], [0x10a40, 0x10a47], [0x10a50, 0x10a58], [0x10a60, 0x10a7f], [0x10b00, 0x10b35], [0x10b40, 0x10b55], [0x10b58, 0x10b72], [0x10b78, 0x10b7f], [0x10c00, 0x10c48], [0x1ee00, 0x1ee03], [0x1ee05, 0x1ee1f], [0x1ee21, 0x1ee22], [0x1ee24, 0x1ee24], [0x1ee27, 0x1ee27], [0x1ee29, 0x1ee32], [0x1ee34, 0x1ee37], [0x1ee39, 0x1ee39], [0x1ee3b, 0x1ee3b], [0x1ee42, 0x1ee42], [0x1ee47, 0x1ee47], [0x1ee49, 0x1ee49], [0x1ee4b, 0x1ee4b], [0x1ee4d, 0x1ee4f], [0x1ee51, 0x1ee52], [0x1ee54, 0x1ee54], [0x1ee57, 0x1ee57], [0x1ee59, 0x1ee59], [0x1ee5b, 0x1ee5b], [0x1ee5d, 0x1ee5d], [0x1ee5f, 0x1ee5f], [0x1ee61, 0x1ee62], [0x1ee64, 0x1ee64], [0x1ee67, 0x1ee6a], [0x1ee6c, 0x1ee72], [0x1ee74, 0x1ee77], [0x1ee79, 0x1ee7c], [0x1ee7e, 0x1ee7e], [0x1ee80, 0x1ee89], [0x1ee8b, 0x1ee9b], [0x1eea1, 0x1eea3], [0x1eea5, 0x1eea9], [0x1eeab, 0x1eebb], [0x10fffd, 0x10fffd]];
8348
8349  function isStrongRTLChar(charCode) {
8350    for (var i = 0; i < strongRTLRanges.length; i++) {
8351      var currentRange = strongRTLRanges[i];
8352      if (charCode >= currentRange[0] && charCode <= currentRange[1]) {
8353        return true;
8354      }
8355    }
8356
8357    return false;
8358  }
8359
8360  function determineBidi(cueDiv) {
8361    var nodeStack = [],
8362        text = "",
8363        charCode;
8364
8365    if (!cueDiv || !cueDiv.childNodes) {
8366      return "ltr";
8367    }
8368
8369    function pushNodes(nodeStack, node) {
8370      for (var i = node.childNodes.length - 1; i >= 0; i--) {
8371        nodeStack.push(node.childNodes[i]);
8372      }
8373    }
8374
8375    function nextTextNode(nodeStack) {
8376      if (!nodeStack || !nodeStack.length) {
8377        return null;
8378      }
8379
8380      var node = nodeStack.pop(),
8381          text = node.textContent || node.innerText;
8382      if (text) {
8383        // TODO: This should match all unicode type B characters (paragraph
8384        // separator characters). See issue #115.
8385        var m = text.match(/^.*(\n|\r)/);
8386        if (m) {
8387          nodeStack.length = 0;
8388          return m[0];
8389        }
8390        return text;
8391      }
8392      if (node.tagName === "ruby") {
8393        return nextTextNode(nodeStack);
8394      }
8395      if (node.childNodes) {
8396        pushNodes(nodeStack, node);
8397        return nextTextNode(nodeStack);
8398      }
8399    }
8400
8401    pushNodes(nodeStack, cueDiv);
8402    while (text = nextTextNode(nodeStack)) {
8403      for (var i = 0; i < text.length; i++) {
8404        charCode = text.charCodeAt(i);
8405        if (isStrongRTLChar(charCode)) {
8406          return "rtl";
8407        }
8408      }
8409    }
8410    return "ltr";
8411  }
8412
8413  function computeLinePos(cue) {
8414    if (typeof cue.line === "number" && (cue.snapToLines || cue.line >= 0 && cue.line <= 100)) {
8415      return cue.line;
8416    }
8417    if (!cue.track || !cue.track.textTrackList || !cue.track.textTrackList.mediaElement) {
8418      return -1;
8419    }
8420    var track = cue.track,
8421        trackList = track.textTrackList,
8422        count = 0;
8423    for (var i = 0; i < trackList.length && trackList[i] !== track; i++) {
8424      if (trackList[i].mode === "showing") {
8425        count++;
8426      }
8427    }
8428    return ++count * -1;
8429  }
8430
8431  function StyleBox() {}
8432
8433  // Apply styles to a div. If there is no div passed then it defaults to the
8434  // div on 'this'.
8435  StyleBox.prototype.applyStyles = function (styles, div) {
8436    div = div || this.div;
8437    for (var prop in styles) {
8438      if (styles.hasOwnProperty(prop)) {
8439        div.style[prop] = styles[prop];
8440      }
8441    }
8442  };
8443
8444  StyleBox.prototype.formatStyle = function (val, unit) {
8445    return val === 0 ? 0 : val + unit;
8446  };
8447
8448  // Constructs the computed display state of the cue (a div). Places the div
8449  // into the overlay which should be a block level element (usually a div).
8450  function CueStyleBox(window, cue, styleOptions) {
8451    StyleBox.call(this);
8452    this.cue = cue;
8453
8454    // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will
8455    // have inline positioning and will function as the cue background box.
8456    this.cueDiv = parseContent(window, cue.text);
8457    var styles = {
8458      color: "rgba(255, 255, 255, 1)",
8459      backgroundColor: "rgba(0, 0, 0, 0.8)",
8460      position: "relative",
8461      left: 0,
8462      right: 0,
8463      top: 0,
8464      bottom: 0,
8465      display: "inline",
8466      writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl",
8467      unicodeBidi: "plaintext"
8468    };
8469
8470    this.applyStyles(styles, this.cueDiv);
8471
8472    // Create an absolutely positioned div that will be used to position the cue
8473    // div. Note, all WebVTT cue-setting alignments are equivalent to the CSS
8474    // mirrors of them except "middle" which is "center" in CSS.
8475    this.div = window.document.createElement("div");
8476    styles = {
8477      direction: determineBidi(this.cueDiv),
8478      writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl",
8479      unicodeBidi: "plaintext",
8480      textAlign: cue.align === "middle" ? "center" : cue.align,
8481      font: styleOptions.font,
8482      whiteSpace: "pre-line",
8483      position: "absolute"
8484    };
8485
8486    this.applyStyles(styles);
8487    this.div.appendChild(this.cueDiv);
8488
8489    // Calculate the distance from the reference edge of the viewport to the text
8490    // position of the cue box. The reference edge will be resolved later when
8491    // the box orientation styles are applied.
8492    var textPos = 0;
8493    switch (cue.positionAlign) {
8494      case "start":
8495        textPos = cue.position;
8496        break;
8497      case "middle":
8498        textPos = cue.position - cue.size / 2;
8499        break;
8500      case "end":
8501        textPos = cue.position - cue.size;
8502        break;
8503    }
8504
8505    // Horizontal box orientation; textPos is the distance from the left edge of the
8506    // area to the left edge of the box and cue.size is the distance extending to
8507    // the right from there.
8508    if (cue.vertical === "") {
8509      this.applyStyles({
8510        left: this.formatStyle(textPos, "%"),
8511        width: this.formatStyle(cue.size, "%")
8512      });
8513      // Vertical box orientation; textPos is the distance from the top edge of the
8514      // area to the top edge of the box and cue.size is the height extending
8515      // downwards from there.
8516    } else {
8517      this.applyStyles({
8518        top: this.formatStyle(textPos, "%"),
8519        height: this.formatStyle(cue.size, "%")
8520      });
8521    }
8522
8523    this.move = function (box) {
8524      this.applyStyles({
8525        top: this.formatStyle(box.top, "px"),
8526        bottom: this.formatStyle(box.bottom, "px"),
8527        left: this.formatStyle(box.left, "px"),
8528        right: this.formatStyle(box.right, "px"),
8529        height: this.formatStyle(box.height, "px"),
8530        width: this.formatStyle(box.width, "px")
8531      });
8532    };
8533  }
8534  CueStyleBox.prototype = _objCreate(StyleBox.prototype);
8535  CueStyleBox.prototype.constructor = CueStyleBox;
8536
8537  // Represents the co-ordinates of an Element in a way that we can easily
8538  // compute things with such as if it overlaps or intersects with another Element.
8539  // Can initialize it with either a StyleBox or another BoxPosition.
8540  function BoxPosition(obj) {
8541    // Either a BoxPosition was passed in and we need to copy it, or a StyleBox
8542    // was passed in and we need to copy the results of 'getBoundingClientRect'
8543    // as the object returned is readonly. All co-ordinate values are in reference
8544    // to the viewport origin (top left).
8545    var lh, height, width, top;
8546    if (obj.div) {
8547      height = obj.div.offsetHeight;
8548      width = obj.div.offsetWidth;
8549      top = obj.div.offsetTop;
8550
8551      var rects = (rects = obj.div.childNodes) && (rects = rects[0]) && rects.getClientRects && rects.getClientRects();
8552      obj = obj.div.getBoundingClientRect();
8553      // In certain cases the outter div will be slightly larger then the sum of
8554      // the inner div's lines. This could be due to bold text, etc, on some platforms.
8555      // In this case we should get the average line height and use that. This will
8556      // result in the desired behaviour.
8557      lh = rects ? Math.max(rects[0] && rects[0].height || 0, obj.height / rects.length) : 0;
8558    }
8559    this.left = obj.left;
8560    this.right = obj.right;
8561    this.top = obj.top || top;
8562    this.height = obj.height || height;
8563    this.bottom = obj.bottom || top + (obj.height || height);
8564    this.width = obj.width || width;
8565    this.lineHeight = lh !== undefined ? lh : obj.lineHeight;
8566  }
8567
8568  // Move the box along a particular axis. Optionally pass in an amount to move
8569  // the box. If no amount is passed then the default is the line height of the
8570  // box.
8571  BoxPosition.prototype.move = function (axis, toMove) {
8572    toMove = toMove !== undefined ? toMove : this.lineHeight;
8573    switch (axis) {
8574      case "+x":
8575        this.left += toMove;
8576        this.right += toMove;
8577        break;
8578      case "-x":
8579        this.left -= toMove;
8580        this.right -= toMove;
8581        break;
8582      case "+y":
8583        this.top += toMove;
8584        this.bottom += toMove;
8585        break;
8586      case "-y":
8587        this.top -= toMove;
8588        this.bottom -= toMove;
8589        break;
8590    }
8591  };
8592
8593  // Check if this box overlaps another box, b2.
8594  BoxPosition.prototype.overlaps = function (b2) {
8595    return this.left < b2.right && this.right > b2.left && this.top < b2.bottom && this.bottom > b2.top;
8596  };
8597
8598  // Check if this box overlaps any other boxes in boxes.
8599  BoxPosition.prototype.overlapsAny = function (boxes) {
8600    for (var i = 0; i < boxes.length; i++) {
8601      if (this.overlaps(boxes[i])) {
8602        return true;
8603      }
8604    }
8605    return false;
8606  };
8607
8608  // Check if this box is within another box.
8609  BoxPosition.prototype.within = function (container) {
8610    return this.top >= container.top && this.bottom <= container.bottom && this.left >= container.left && this.right <= container.right;
8611  };
8612
8613  // Check if this box is entirely within the container or it is overlapping
8614  // on the edge opposite of the axis direction passed. For example, if "+x" is
8615  // passed and the box is overlapping on the left edge of the container, then
8616  // return true.
8617  BoxPosition.prototype.overlapsOppositeAxis = function (container, axis) {
8618    switch (axis) {
8619      case "+x":
8620        return this.left < container.left;
8621      case "-x":
8622        return this.right > container.right;
8623      case "+y":
8624        return this.top < container.top;
8625      case "-y":
8626        return this.bottom > container.bottom;
8627    }
8628  };
8629
8630  // Find the percentage of the area that this box is overlapping with another
8631  // box.
8632  BoxPosition.prototype.intersectPercentage = function (b2) {
8633    var x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)),
8634        y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)),
8635        intersectArea = x * y;
8636    return intersectArea / (this.height * this.width);
8637  };
8638
8639  // Convert the positions from this box to CSS compatible positions using
8640  // the reference container's positions. This has to be done because this
8641  // box's positions are in reference to the viewport origin, whereas, CSS
8642  // values are in referecne to their respective edges.
8643  BoxPosition.prototype.toCSSCompatValues = function (reference) {
8644    return {
8645      top: this.top - reference.top,
8646      bottom: reference.bottom - this.bottom,
8647      left: this.left - reference.left,
8648      right: reference.right - this.right,
8649      height: this.height,
8650      width: this.width
8651    };
8652  };
8653
8654  // Get an object that represents the box's position without anything extra.
8655  // Can pass a StyleBox, HTMLElement, or another BoxPositon.
8656  BoxPosition.getSimpleBoxPosition = function (obj) {
8657    var height = obj.div ? obj.div.offsetHeight : obj.tagName ? obj.offsetHeight : 0;
8658    var width = obj.div ? obj.div.offsetWidth : obj.tagName ? obj.offsetWidth : 0;
8659    var top = obj.div ? obj.div.offsetTop : obj.tagName ? obj.offsetTop : 0;
8660
8661    obj = obj.div ? obj.div.getBoundingClientRect() : obj.tagName ? obj.getBoundingClientRect() : obj;
8662    var ret = {
8663      left: obj.left,
8664      right: obj.right,
8665      top: obj.top || top,
8666      height: obj.height || height,
8667      bottom: obj.bottom || top + (obj.height || height),
8668      width: obj.width || width
8669    };
8670    return ret;
8671  };
8672
8673  // Move a StyleBox to its specified, or next best, position. The containerBox
8674  // is the box that contains the StyleBox, such as a div. boxPositions are
8675  // a list of other boxes that the styleBox can't overlap with.
8676  function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) {
8677
8678    // Find the best position for a cue box, b, on the video. The axis parameter
8679    // is a list of axis, the order of which, it will move the box along. For example:
8680    // Passing ["+x", "-x"] will move the box first along the x axis in the positive
8681    // direction. If it doesn't find a good position for it there it will then move
8682    // it along the x axis in the negative direction.
8683    function findBestPosition(b, axis) {
8684      var bestPosition,
8685          specifiedPosition = new BoxPosition(b),
8686          percentage = 1; // Highest possible so the first thing we get is better.
8687
8688      for (var i = 0; i < axis.length; i++) {
8689        while (b.overlapsOppositeAxis(containerBox, axis[i]) || b.within(containerBox) && b.overlapsAny(boxPositions)) {
8690          b.move(axis[i]);
8691        }
8692        // We found a spot where we aren't overlapping anything. This is our
8693        // best position.
8694        if (b.within(containerBox)) {
8695          return b;
8696        }
8697        var p = b.intersectPercentage(containerBox);
8698        // If we're outside the container box less then we were on our last try
8699        // then remember this position as the best position.
8700        if (percentage > p) {
8701          bestPosition = new BoxPosition(b);
8702          percentage = p;
8703        }
8704        // Reset the box position to the specified position.
8705        b = new BoxPosition(specifiedPosition);
8706      }
8707      return bestPosition || specifiedPosition;
8708    }
8709
8710    var boxPosition = new BoxPosition(styleBox),
8711        cue = styleBox.cue,
8712        linePos = computeLinePos(cue),
8713        axis = [];
8714
8715    // If we have a line number to align the cue to.
8716    if (cue.snapToLines) {
8717      var size;
8718      switch (cue.vertical) {
8719        case "":
8720          axis = ["+y", "-y"];
8721          size = "height";
8722          break;
8723        case "rl":
8724          axis = ["+x", "-x"];
8725          size = "width";
8726          break;
8727        case "lr":
8728          axis = ["-x", "+x"];
8729          size = "width";
8730          break;
8731      }
8732
8733      var step = boxPosition.lineHeight,
8734          position = step * Math.round(linePos),
8735          maxPosition = containerBox[size] + step,
8736          initialAxis = axis[0];
8737
8738      // If the specified intial position is greater then the max position then
8739      // clamp the box to the amount of steps it would take for the box to
8740      // reach the max position.
8741      if (Math.abs(position) > maxPosition) {
8742        position = position < 0 ? -1 : 1;
8743        position *= Math.ceil(maxPosition / step) * step;
8744      }
8745
8746      // If computed line position returns negative then line numbers are
8747      // relative to the bottom of the video instead of the top. Therefore, we
8748      // need to increase our initial position by the length or width of the
8749      // video, depending on the writing direction, and reverse our axis directions.
8750      if (linePos < 0) {
8751        position += cue.vertical === "" ? containerBox.height : containerBox.width;
8752        axis = axis.reverse();
8753      }
8754
8755      // Move the box to the specified position. This may not be its best
8756      // position.
8757      boxPosition.move(initialAxis, position);
8758    } else {
8759      // If we have a percentage line value for the cue.
8760      var calculatedPercentage = boxPosition.lineHeight / containerBox.height * 100;
8761
8762      switch (cue.lineAlign) {
8763        case "middle":
8764          linePos -= calculatedPercentage / 2;
8765          break;
8766        case "end":
8767          linePos -= calculatedPercentage;
8768          break;
8769      }
8770
8771      // Apply initial line position to the cue box.
8772      switch (cue.vertical) {
8773        case "":
8774          styleBox.applyStyles({
8775            top: styleBox.formatStyle(linePos, "%")
8776          });
8777          break;
8778        case "rl":
8779          styleBox.applyStyles({
8780            left: styleBox.formatStyle(linePos, "%")
8781          });
8782          break;
8783        case "lr":
8784          styleBox.applyStyles({
8785            right: styleBox.formatStyle(linePos, "%")
8786          });
8787          break;
8788      }
8789
8790      axis = ["+y", "-x", "+x", "-y"];
8791
8792      // Get the box position again after we've applied the specified positioning
8793      // to it.
8794      boxPosition = new BoxPosition(styleBox);
8795    }
8796
8797    var bestPosition = findBestPosition(boxPosition, axis);
8798    styleBox.move(bestPosition.toCSSCompatValues(containerBox));
8799  }
8800
8801  function WebVTT$1() {}
8802  // Nothing
8803
8804
8805  // Helper to allow strings to be decoded instead of the default binary utf8 data.
8806  WebVTT$1.StringDecoder = function () {
8807    return {
8808      decode: function decode(data) {
8809        if (!data) {
8810          return "";
8811        }
8812        if (typeof data !== "string") {
8813          throw new Error("Error - expected string data.");
8814        }
8815        return decodeURIComponent(encodeURIComponent(data));
8816      }
8817    };
8818  };
8819
8820  WebVTT$1.convertCueToDOMTree = function (window, cuetext) {
8821    if (!window || !cuetext) {
8822      return null;
8823    }
8824    return parseContent(window, cuetext);
8825  };
8826
8827  var FONT_SIZE_PERCENT = 0.05;
8828  var FONT_STYLE = "sans-serif";
8829  var CUE_BACKGROUND_PADDING = "1.5%";
8830
8831  // Runs the processing model over the cues and regions passed to it.
8832  // @param overlay A block level element (usually a div) that the computed cues
8833  //                and regions will be placed into.
8834  WebVTT$1.processCues = function (window, cues, overlay) {
8835    if (!window || !cues || !overlay) {
8836      return null;
8837    }
8838
8839    // Remove all previous children.
8840    while (overlay.firstChild) {
8841      overlay.removeChild(overlay.firstChild);
8842    }
8843
8844    var paddedOverlay = window.document.createElement("div");
8845    paddedOverlay.style.position = "absolute";
8846    paddedOverlay.style.left = "0";
8847    paddedOverlay.style.right = "0";
8848    paddedOverlay.style.top = "0";
8849    paddedOverlay.style.bottom = "0";
8850    paddedOverlay.style.margin = CUE_BACKGROUND_PADDING;
8851    overlay.appendChild(paddedOverlay);
8852
8853    // Determine if we need to compute the display states of the cues. This could
8854    // be the case if a cue's state has been changed since the last computation or
8855    // if it has not been computed yet.
8856    function shouldCompute(cues) {
8857      for (var i = 0; i < cues.length; i++) {
8858        if (cues[i].hasBeenReset || !cues[i].displayState) {
8859          return true;
8860        }
8861      }
8862      return false;
vendor: 16,058 bytes, lines 8863-9396
8863    }
8864
8865    // We don't need to recompute the cues' display states. Just reuse them.
8866    if (!shouldCompute(cues)) {
8867      for (var i = 0; i < cues.length; i++) {
8868        paddedOverlay.appendChild(cues[i].displayState);
8869      }
8870      return;
8871    }
8872
8873    var boxPositions = [],
8874        containerBox = BoxPosition.getSimpleBoxPosition(paddedOverlay),
8875        fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100;
8876    var styleOptions = {
8877      font: fontSize + "px " + FONT_STYLE
8878    };
8879
8880    (function () {
8881      var styleBox, cue;
8882
8883      for (var i = 0; i < cues.length; i++) {
8884        cue = cues[i];
8885
8886        // Compute the intial position and styles of the cue div.
8887        styleBox = new CueStyleBox(window, cue, styleOptions);
8888        paddedOverlay.appendChild(styleBox.div);
8889
8890        // Move the cue div to it's correct line position.
8891        moveBoxToLinePosition(window, styleBox, containerBox, boxPositions);
8892
8893        // Remember the computed div so that we don't have to recompute it later
8894        // if we don't have too.
8895        cue.displayState = styleBox.div;
8896
8897        boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox));
8898      }
8899    })();
8900  };
8901
8902  WebVTT$1.Parser = function (window, vttjs, decoder) {
8903    if (!decoder) {
8904      decoder = vttjs;
8905      vttjs = {};
8906    }
8907    if (!vttjs) {
8908      vttjs = {};
8909    }
8910
8911    this.window = window;
8912    this.vttjs = vttjs;
8913    this.state = "INITIAL";
8914    this.buffer = "";
8915    this.decoder = decoder || new TextDecoder("utf8");
8916    this.regionList = [];
8917  };
8918
8919  WebVTT$1.Parser.prototype = {
8920    // If the error is a ParsingError then report it to the consumer if
8921    // possible. If it's not a ParsingError then throw it like normal.
8922    reportOrThrowError: function reportOrThrowError(e) {
8923      if (e instanceof ParsingError) {
8924        this.onparsingerror && this.onparsingerror(e);
8925      } else {
8926        throw e;
8927      }
8928    },
8929    parse: function parse(data) {
8930      var self = this;
8931
8932      // If there is no data then we won't decode it, but will just try to parse
8933      // whatever is in buffer already. This may occur in circumstances, for
8934      // example when flush() is called.
8935      if (data) {
8936        // Try to decode the data that we received.
8937        self.buffer += self.decoder.decode(data, { stream: true });
8938      }
8939
8940      function collectNextLine() {
8941        var buffer = self.buffer;
8942        var pos = 0;
8943        while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') {
8944          ++pos;
8945        }
8946        var line = buffer.substr(0, pos);
8947        // Advance the buffer early in case we fail below.
8948        if (buffer[pos] === '\r') {
8949          ++pos;
8950        }
8951        if (buffer[pos] === '\n') {
8952          ++pos;
8953        }
8954        self.buffer = buffer.substr(pos);
8955        return line;
8956      }
8957
8958      // 3.4 WebVTT region and WebVTT region settings syntax
8959      function parseRegion(input) {
8960        var settings = new Settings();
8961
8962        parseOptions(input, function (k, v) {
8963          switch (k) {
8964            case "id":
8965              settings.set(k, v);
8966              break;
8967            case "width":
8968              settings.percent(k, v);
8969              break;
8970            case "lines":
8971              settings.integer(k, v);
8972              break;
8973            case "regionanchor":
8974            case "viewportanchor":
8975              var xy = v.split(',');
8976              if (xy.length !== 2) {
8977                break;
8978              }
8979              // We have to make sure both x and y parse, so use a temporary
8980              // settings object here.
8981              var anchor = new Settings();
8982              anchor.percent("x", xy[0]);
8983              anchor.percent("y", xy[1]);
8984              if (!anchor.has("x") || !anchor.has("y")) {
8985                break;
8986              }
8987              settings.set(k + "X", anchor.get("x"));
8988              settings.set(k + "Y", anchor.get("y"));
8989              break;
8990            case "scroll":
8991              settings.alt(k, v, ["up"]);
8992              break;
8993          }
8994        }, /=/, /\s/);
8995
8996        // Create the region, using default values for any values that were not
8997        // specified.
8998        if (settings.has("id")) {
8999          var region = new (self.vttjs.VTTRegion || self.window.VTTRegion)();
9000          region.width = settings.get("width", 100);
9001          region.lines = settings.get("lines", 3);
9002          region.regionAnchorX = settings.get("regionanchorX", 0);
9003          region.regionAnchorY = settings.get("regionanchorY", 100);
9004          region.viewportAnchorX = settings.get("viewportanchorX", 0);
9005          region.viewportAnchorY = settings.get("viewportanchorY", 100);
9006          region.scroll = settings.get("scroll", "");
9007          // Register the region.
9008          self.onregion && self.onregion(region);
9009          // Remember the VTTRegion for later in case we parse any VTTCues that
9010          // reference it.
9011          self.regionList.push({
9012            id: settings.get("id"),
9013            region: region
9014          });
9015        }
9016      }
9017
9018      // draft-pantos-http-live-streaming-20
9019      // https://tools.ietf.org/html/draft-pantos-http-live-streaming-20#section-3.5
9020      // 3.5 WebVTT
9021      function parseTimestampMap(input) {
9022        var settings = new Settings();
9023
9024        parseOptions(input, function (k, v) {
9025          switch (k) {
9026            case "MPEGT":
9027              settings.integer(k + 'S', v);
9028              break;
9029            case "LOCA":
9030              settings.set(k + 'L', parseTimeStamp(v));
9031              break;
9032          }
9033        }, /[^\d]:/, /,/);
9034
9035        self.ontimestampmap && self.ontimestampmap({
9036          "MPEGTS": settings.get("MPEGTS"),
9037          "LOCAL": settings.get("LOCAL")
9038        });
9039      }
9040
9041      // 3.2 WebVTT metadata header syntax
9042      function parseHeader(input) {
9043        if (input.match(/X-TIMESTAMP-MAP/)) {
9044          // This line contains HLS X-TIMESTAMP-MAP metadata
9045          parseOptions(input, function (k, v) {
9046            switch (k) {
9047              case "X-TIMESTAMP-MAP":
9048                parseTimestampMap(v);
9049                break;
9050            }
9051          }, /=/);
9052        } else {
9053          parseOptions(input, function (k, v) {
9054            switch (k) {
9055              case "Region":
9056                // 3.3 WebVTT region metadata header syntax
9057                parseRegion(v);
9058                break;
9059            }
9060          }, /:/);
9061        }
9062      }
9063
9064      // 5.1 WebVTT file parsing.
9065      try {
9066        var line;
9067        if (self.state === "INITIAL") {
9068          // We can't start parsing until we have the first line.
9069          if (!/\r\n|\n/.test(self.buffer)) {
9070            return this;
9071          }
9072
9073          line = collectNextLine();
9074
9075          var m = line.match(/^WEBVTT([ \t].*)?$/);
9076          if (!m || !m[0]) {
9077            throw new ParsingError(ParsingError.Errors.BadSignature);
9078          }
9079
9080          self.state = "HEADER";
9081        }
9082
9083        var alreadyCollectedLine = false;
9084        while (self.buffer) {
9085          // We can't parse a line until we have the full line.
9086          if (!/\r\n|\n/.test(self.buffer)) {
9087            return this;
9088          }
9089
9090          if (!alreadyCollectedLine) {
9091            line = collectNextLine();
9092          } else {
9093            alreadyCollectedLine = false;
9094          }
9095
9096          switch (self.state) {
9097            case "HEADER":
9098              // 13-18 - Allow a header (metadata) under the WEBVTT line.
9099              if (/:/.test(line)) {
9100                parseHeader(line);
9101              } else if (!line) {
9102                // An empty line terminates the header and starts the body (cues).
9103                self.state = "ID";
9104              }
9105              continue;
9106            case "NOTE":
9107              // Ignore NOTE blocks.
9108              if (!line) {
9109                self.state = "ID";
9110              }
9111              continue;
9112            case "ID":
9113              // Check for the start of NOTE blocks.
9114              if (/^NOTE($|[ \t])/.test(line)) {
9115                self.state = "NOTE";
9116                break;
9117              }
9118              // 19-29 - Allow any number of line terminators, then initialize new cue values.
9119              if (!line) {
9120                continue;
9121              }
9122              self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, "");
9123              self.state = "CUE";
9124              // 30-39 - Check if self line contains an optional identifier or timing data.
9125              if (line.indexOf("-->") === -1) {
9126                self.cue.id = line;
9127                continue;
9128              }
9129            // Process line as start of a cue.
9130            /*falls through*/
9131            case "CUE":
9132              // 40 - Collect cue timings and settings.
9133              try {
9134                parseCue(line, self.cue, self.regionList);
9135              } catch (e) {
9136                self.reportOrThrowError(e);
9137                // In case of an error ignore rest of the cue.
9138                self.cue = null;
9139                self.state = "BADCUE";
9140                continue;
9141              }
9142              self.state = "CUETEXT";
9143              continue;
9144            case "CUETEXT":
9145              var hasSubstring = line.indexOf("-->") !== -1;
9146              // 34 - If we have an empty line then report the cue.
9147              // 35 - If we have the special substring '-->' then report the cue,
9148              // but do not collect the line as we need to process the current
9149              // one as a new cue.
9150              if (!line || hasSubstring && (alreadyCollectedLine = true)) {
9151                // We are done parsing self cue.
9152                self.oncue && self.oncue(self.cue);
9153                self.cue = null;
9154                self.state = "ID";
9155                continue;
9156              }
9157              if (self.cue.text) {
9158                self.cue.text += "\n";
9159              }
9160              self.cue.text += line;
9161              continue;
9162            case "BADCUE":
9163              // BADCUE
9164              // 54-62 - Collect and discard the remaining cue.
9165              if (!line) {
9166                self.state = "ID";
9167              }
9168              continue;
9169          }
9170        }
9171      } catch (e) {
9172        self.reportOrThrowError(e);
9173
9174        // If we are currently parsing a cue, report what we have.
9175        if (self.state === "CUETEXT" && self.cue && self.oncue) {
9176          self.oncue(self.cue);
9177        }
9178        self.cue = null;
9179        // Enter BADWEBVTT state if header was not parsed correctly otherwise
9180        // another exception occurred so enter BADCUE state.
9181        self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE";
9182      }
9183      return this;
9184    },
9185    flush: function flush() {
9186      var self = this;
9187      try {
9188        // Finish decoding the stream.
9189        self.buffer += self.decoder.decode();
9190        // Synthesize the end of the current cue or region.
9191        if (self.cue || self.state === "HEADER") {
9192          self.buffer += "\n\n";
9193          self.parse();
9194        }
9195        // If we've flushed, parsed, and we're still on the INITIAL state then
9196        // that means we don't have enough of the stream to parse the first
9197        // line.
9198        if (self.state === "INITIAL") {
9199          throw new ParsingError(ParsingError.Errors.BadSignature);
9200        }
9201      } catch (e) {
9202        self.reportOrThrowError(e);
9203      }
9204      self.onflush && self.onflush();
9205      return this;
9206    }
9207  };
9208
9209  var vtt = WebVTT$1;
9210
9211  var vtt$1 = /*#__PURE__*/Object.freeze({
9212    default: vtt,
9213    __moduleExports: vtt
9214  });
9215
9216  /**
9217   * Copyright 2013 vtt.js Contributors
9218   *
9219   * Licensed under the Apache License, Version 2.0 (the "License");
9220   * you may not use this file except in compliance with the License.
9221   * You may obtain a copy of the License at
9222   *
9223   *   http://www.apache.org/licenses/LICENSE-2.0
9224   *
9225   * Unless required by applicable law or agreed to in writing, software
9226   * distributed under the License is distributed on an "AS IS" BASIS,
9227   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9228   * See the License for the specific language governing permissions and
9229   * limitations under the License.
9230   */
9231
9232  var autoKeyword = "auto";
9233  var directionSetting = {
9234    "": 1,
9235    "lr": 1,
9236    "rl": 1
9237  };
9238  var alignSetting = {
9239    "start": 1,
9240    "middle": 1,
9241    "end": 1,
9242    "left": 1,
9243    "right": 1
9244  };
9245
9246  function findDirectionSetting(value) {
9247    if (typeof value !== "string") {
9248      return false;
9249    }
9250    var dir = directionSetting[value.toLowerCase()];
9251    return dir ? value.toLowerCase() : false;
9252  }
9253
9254  function findAlignSetting(value) {
9255    if (typeof value !== "string") {
9256      return false;
9257    }
9258    var align = alignSetting[value.toLowerCase()];
9259    return align ? value.toLowerCase() : false;
9260  }
9261
9262  function VTTCue(startTime, endTime, text) {
9263    /**
9264     * Shim implementation specific properties. These properties are not in
9265     * the spec.
9266     */
9267
9268    // Lets us know when the VTTCue's data has changed in such a way that we need
9269    // to recompute its display state. This lets us compute its display state
9270    // lazily.
9271    this.hasBeenReset = false;
9272
9273    /**
9274     * VTTCue and TextTrackCue properties
9275     * http://dev.w3.org/html5/webvtt/#vttcue-interface
9276     */
9277
9278    var _id = "";
9279    var _pauseOnExit = false;
9280    var _startTime = startTime;
9281    var _endTime = endTime;
9282    var _text = text;
9283    var _region = null;
9284    var _vertical = "";
9285    var _snapToLines = true;
9286    var _line = "auto";
9287    var _lineAlign = "start";
9288    var _position = 50;
9289    var _positionAlign = "middle";
9290    var _size = 50;
9291    var _align = "middle";
9292
9293    Object.defineProperties(this, {
9294      "id": {
9295        enumerable: true,
9296        get: function get() {
9297          return _id;
9298        },
9299        set: function set(value) {
9300          _id = "" + value;
9301        }
9302      },
9303
9304      "pauseOnExit": {
9305        enumerable: true,
9306        get: function get() {
9307          return _pauseOnExit;
9308        },
9309        set: function set(value) {
9310          _pauseOnExit = !!value;
9311        }
9312      },
9313
9314      "startTime": {
9315        enumerable: true,
9316        get: function get() {
9317          return _startTime;
9318        },
9319        set: function set(value) {
9320          if (typeof value !== "number") {
9321            throw new TypeError("Start time must be set to a number.");
9322          }
9323          _startTime = value;
9324          this.hasBeenReset = true;
9325        }
9326      },
9327
9328      "endTime": {
9329        enumerable: true,
9330        get: function get() {
9331          return _endTime;
9332        },
9333        set: function set(value) {
9334          if (typeof value !== "number") {
9335            throw new TypeError("End time must be set to a number.");
9336          }
9337          _endTime = value;
9338          this.hasBeenReset = true;
9339        }
9340      },
9341
9342      "text": {
9343        enumerable: true,
9344        get: function get() {
9345          return _text;
9346        },
9347        set: function set(value) {
9348          _text = "" + value;
9349          this.hasBeenReset = true;
9350        }
9351      },
9352
9353      "region": {
9354        enumerable: true,
9355        get: function get() {
9356          return _region;
9357        },
9358        set: function set(value) {
9359          _region = value;
9360          this.hasBeenReset = true;
9361        }
9362      },
9363
9364      "vertical": {
9365        enumerable: true,
9366        get: function get() {
9367          return _vertical;
9368        },
9369        set: function set(value) {
9370          var setting = findDirectionSetting(value);
9371          // Have to check for false because the setting an be an empty string.
9372          if (setting === false) {
9373            throw new SyntaxError("An invalid or illegal string was specified.");
9374          }
9375          _vertical = setting;
9376          this.hasBeenReset = true;
9377        }
9378      },
9379
9380      "snapToLines": {
9381        enumerable: true,
9382        get: function get() {
9383          return _snapToLines;
9384        },
9385        set: function set(value) {
9386          _snapToLines = !!value;
9387          this.hasBeenReset = true;
9388        }
9389      },
9390
9391      "line": {
9392        enumerable: true,
9393        get: function get() {
9394          return _line;
9395        },
9396        set: function set(value) {
vendor: 4,519 bytes, lines 9397-9564
9397          if (typeof value !== "number" && value !== autoKeyword) {
9398            throw new SyntaxError("An invalid number or illegal string was specified.");
9399          }
9400          _line = value;
9401          this.hasBeenReset = true;
9402        }
9403      },
9404
9405      "lineAlign": {
9406        enumerable: true,
9407        get: function get() {
9408          return _lineAlign;
9409        },
9410        set: function set(value) {
9411          var setting = findAlignSetting(value);
9412          if (!setting) {
9413            throw new SyntaxError("An invalid or illegal string was specified.");
9414          }
9415          _lineAlign = setting;
9416          this.hasBeenReset = true;
9417        }
9418      },
9419
9420      "position": {
9421        enumerable: true,
9422        get: function get() {
9423          return _position;
9424        },
9425        set: function set(value) {
9426          if (value < 0 || value > 100) {
9427            throw new Error("Position must be between 0 and 100.");
9428          }
9429          _position = value;
9430          this.hasBeenReset = true;
9431        }
9432      },
9433
9434      "positionAlign": {
9435        enumerable: true,
9436        get: function get() {
9437          return _positionAlign;
9438        },
9439        set: function set(value) {
9440          var setting = findAlignSetting(value);
9441          if (!setting) {
9442            throw new SyntaxError("An invalid or illegal string was specified.");
9443          }
9444          _positionAlign = setting;
9445          this.hasBeenReset = true;
9446        }
9447      },
9448
9449      "size": {
9450        enumerable: true,
9451        get: function get() {
9452          return _size;
9453        },
9454        set: function set(value) {
9455          if (value < 0 || value > 100) {
9456            throw new Error("Size must be between 0 and 100.");
9457          }
9458          _size = value;
9459          this.hasBeenReset = true;
9460        }
9461      },
9462
9463      "align": {
9464        enumerable: true,
9465        get: function get() {
9466          return _align;
9467        },
9468        set: function set(value) {
9469          var setting = findAlignSetting(value);
9470          if (!setting) {
9471            throw new SyntaxError("An invalid or illegal string was specified.");
9472          }
9473          _align = setting;
9474          this.hasBeenReset = true;
9475        }
9476      }
9477    });
9478
9479    /**
9480     * Other <track> spec defined properties
9481     */
9482
9483    // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state
9484    this.displayState = undefined;
9485  }
9486
9487  /**
9488   * VTTCue methods
9489   */
9490
9491  VTTCue.prototype.getCueAsHTML = function () {
9492    // Assume WebVTT.convertCueToDOMTree is on the global.
9493    return WebVTT.convertCueToDOMTree(window, this.text);
9494  };
9495
9496  var vttcue = VTTCue;
9497
9498  var vttcue$1 = /*#__PURE__*/Object.freeze({
9499    default: vttcue,
9500    __moduleExports: vttcue
9501  });
9502
9503  /**
9504   * Copyright 2013 vtt.js Contributors
9505   *
9506   * Licensed under the Apache License, Version 2.0 (the "License");
9507   * you may not use this file except in compliance with the License.
9508   * You may obtain a copy of the License at
9509   *
9510   *   http://www.apache.org/licenses/LICENSE-2.0
9511   *
9512   * Unless required by applicable law or agreed to in writing, software
9513   * distributed under the License is distributed on an "AS IS" BASIS,
9514   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9515   * See the License for the specific language governing permissions and
9516   * limitations under the License.
9517   */
9518
9519  var scrollSetting = {
9520    "": true,
9521    "up": true
9522  };
9523
9524  function findScrollSetting(value) {
9525    if (typeof value !== "string") {
9526      return false;
9527    }
9528    var scroll = scrollSetting[value.toLowerCase()];
9529    return scroll ? value.toLowerCase() : false;
9530  }
9531
9532  function isValidPercentValue(value) {
9533    return typeof value === "number" && value >= 0 && value <= 100;
9534  }
9535
9536  // VTTRegion shim http://dev.w3.org/html5/webvtt/#vttregion-interface
9537  function VTTRegion() {
9538    var _width = 100;
9539    var _lines = 3;
9540    var _regionAnchorX = 0;
9541    var _regionAnchorY = 100;
9542    var _viewportAnchorX = 0;
9543    var _viewportAnchorY = 100;
9544    var _scroll = "";
9545
9546    Object.defineProperties(this, {
9547      "width": {
9548        enumerable: true,
9549        get: function get() {
9550          return _width;
9551        },
9552        set: function set(value) {
9553          if (!isValidPercentValue(value)) {
9554            throw new Error("Width must be between 0 and 100.");
9555          }
9556          _width = value;
9557        }
9558      },
9559      "lines": {
9560        enumerable: true,
9561        get: function get() {
9562          return _lines;
9563        },
9564        set: function set(value) {
9565          if (typeof value !== "number") {
9566            throw new TypeError("Lines must be set to a number.");
9567          }
9568          _lines = value;
9569        }
9570      },
9571      "regionAnchorY": {
9572        enumerable: true,
9573        get: function get() {
9574          return _regionAnchorY;
9575        },
9576        set: function set(value) {
9577          if (!isValidPercentValue(value)) {
9578            throw new Error("RegionAnchorX must be between 0 and 100.");
9579          }
9580          _regionAnchorY = value;
9581        }
9582      },
9583      "regionAnchorX": {
9584        enumerable: true,
9585        get: function get() {
9586          return _regionAnchorX;
9587        },
9588        set: function set(value) {
9589          if (!isValidPercentValue(value)) {
9590            throw new Error("RegionAnchorY must be between 0 and 100.");
9591          }
9592          _regionAnchorX = value;
9593        }
9594      },
9595      "viewportAnchorY": {
9596        enumerable: true,
9597        get: function get() {
9598          return _viewportAnchorY;
9599        },
9600        set: function set(value) {
9601          if (!isValidPercentValue(value)) {
9602            throw new Error("ViewportAnchorY must be between 0 and 100.");
9603          }
9604          _viewportAnchorY = value;
9605        }
9606      },
9607      "viewportAnchorX": {
9608        enumerable: true,
9609        get: function get() {
9610          return _viewportAnchorX;
9611        },
9612        set: function set(value) {
9613          if (!isValidPercentValue(value)) {
9614            throw new Error("ViewportAnchorX must be between 0 and 100.");
9615          }
9616          _viewportAnchorX = value;
9617        }
9618      },
9619      "scroll": {
9620        enumerable: true,
9621        get: function get() {
9622          return _scroll;
9623        },
9624        set: function set(value) {
9625          var setting = findScrollSetting(value);
9626          // Have to check for false as an empty string is a legal value.
9627          if (setting === false) {
9628            throw new SyntaxError("An invalid or illegal string was specified.");
9629          }
9630          _scroll = setting;
9631        }
9632      }
9633    });
9634  }
9635
9636  var vttregion = VTTRegion;
9637
9638  var vttregion$1 = /*#__PURE__*/Object.freeze({
9639    default: vttregion,
9640    __moduleExports: vttregion
9641  });
9642
9643  var require$$0 = ( vtt$1 && vtt ) || vtt$1;
9644
9645  var require$$1 = ( vttcue$1 && vttcue ) || vttcue$1;
9646
9647  var require$$2 = ( vttregion$1 && vttregion ) || vttregion$1;
9648
9649  var browserIndex = createCommonjsModule(function (module) {
9650    /**
9651     * Copyright 2013 vtt.js Contributors
9652     *
9653     * Licensed under the Apache License, Version 2.0 (the "License");
9654     * you may not use this file except in compliance with the License.
9655     * You may obtain a copy of the License at
9656     *
9657     *   http://www.apache.org/licenses/LICENSE-2.0
9658     *
9659     * Unless required by applicable law or agreed to in writing, software
9660     * distributed under the License is distributed on an "AS IS" BASIS,
9661     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9662     * See the License for the specific language governing permissions and
9663     * limitations under the License.
9664     */
9665
9666    // Default exports for Node. Export the extended versions of VTTCue and
9667    // VTTRegion in Node since we likely want the capability to convert back and
9668    // forth between JSON. If we don't then it's not that big of a deal since we're
9669    // off browser.
9670
9671
9672    var vttjs = module.exports = {
9673      WebVTT: require$$0,
9674      VTTCue: require$$1,
9675      VTTRegion: require$$2
9676    };
9677
9678    window_1.vttjs = vttjs;
9679    window_1.WebVTT = vttjs.WebVTT;
9680
9681    var cueShim = vttjs.VTTCue;
9682    var regionShim = vttjs.VTTRegion;
9683    var nativeVTTCue = window_1.VTTCue;
9684    var nativeVTTRegion = window_1.VTTRegion;
9685
9686    vttjs.shim = function () {
9687      window_1.VTTCue = cueShim;
9688      window_1.VTTRegion = regionShim;
9689    };
9690
9691    vttjs.restore = function () {
9692      window_1.VTTCue = nativeVTTCue;
9693      window_1.VTTRegion = nativeVTTRegion;
9694    };
9695
9696    if (!window_1.VTTCue) {
9697      vttjs.shim();
9698    }
9699  });
9700  var browserIndex_1 = browserIndex.WebVTT;
9701  var browserIndex_2 = browserIndex.VTTCue;
9702  var browserIndex_3 = browserIndex.VTTRegion;
9703
9704  /**
9705   * @file tech.js
9706   */
9707
9708  /**
9709   * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string
9710   * that just contains the src url alone.
9711   * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};`
9712     * `var SourceString = 'http://example.com/some-video.mp4';`
9713   *
9714   * @typedef {Object|string} Tech~SourceObject
9715   *
9716   * @property {string} src
9717   *           The url to the source
9718   *
9719   * @property {string} type
9720   *           The mime type of the source
9721   */
9722
9723  /**
9724   * A function used by {@link Tech} to create a new {@link TextTrack}.
9725   *
9726   * @private
9727   *
9728   * @param {Tech} self
9729   *        An instance of the Tech class.
9730   *
9731   * @param {string} kind
9732   *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
9733   *
9734   * @param {string} [label]
9735   *        Label to identify the text track
9736   *
9737   * @param {string} [language]
9738   *        Two letter language abbreviation
9739   *
9740   * @param {Object} [options={}]
9741   *        An object with additional text track options
9742   *
9743   * @return {TextTrack}
9744   *          The text track that was created.
9745   */
9746  function createTrackHelper(self, kind, label, language) {
9747    var options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
9748
9749    var tracks = self.textTracks();
9750
9751    options.kind = kind;
9752
9753    if (label) {
9754      options.label = label;
9755    }
9756    if (language) {
9757      options.language = language;
9758    }
9759    options.tech = self;
9760
9761    var track = new ALL.text.TrackClass(options);
9762
9763    tracks.addTrack(track);
9764
9765    return track;
9766  }
9767
9768  /**
9769   * This is the base class for media playback technology controllers, such as
9770   * {@link Flash} and {@link HTML5}
9771   *
9772   * @extends Component
9773   */
9774
9775  var Tech = function (_Component) {
9776    inherits(Tech, _Component);
9777
9778    /**
9779     * Create an instance of this Tech.
9780     *
9781     * @param {Object} [options]
9782     *        The key/value store of player options.
9783     *
9784     * @param {Component~ReadyCallback} ready
9785     *        Callback function to call when the `HTML5` Tech is ready.
9786     */
9787    function Tech() {
9788      var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
9789      var ready = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {};
9790      classCallCheck(this, Tech);
9791
9792      // we don't want the tech to report user activity automatically.
vendor: 319,771 bytes, lines 9793-20909
9793      // This is done manually in addControlsListeners
9794      options.reportTouchActivity = false;
9795
9796      // keep track of whether the current source has played at all to
9797      // implement a very limited played()
9798      var _this = possibleConstructorReturn(this, _Component.call(this, null, options, ready));
9799
9800      _this.hasStarted_ = false;
9801      _this.on('playing', function () {
9802        this.hasStarted_ = true;
9803      });
9804      _this.on('loadstart', function () {
9805        this.hasStarted_ = false;
9806      });
9807
9808      ALL.names.forEach(function (name) {
9809        var props = ALL[name];
9810
9811        if (options && options[props.getterName]) {
9812          _this[props.privateName] = options[props.getterName];
9813        }
9814      });
9815
9816      // Manually track progress in cases where the browser/flash player doesn't report it.
9817      if (!_this.featuresProgressEvents) {
9818        _this.manualProgressOn();
9819      }
9820
9821      // Manually track timeupdates in cases where the browser/flash player doesn't report it.
9822      if (!_this.featuresTimeupdateEvents) {
9823        _this.manualTimeUpdatesOn();
9824      }
9825
9826      ['Text', 'Audio', 'Video'].forEach(function (track) {
9827        if (options['native' + track + 'Tracks'] === false) {
9828          _this['featuresNative' + track + 'Tracks'] = false;
9829        }
9830      });
9831
9832      if (options.nativeCaptions === false || options.nativeTextTracks === false) {
9833        _this.featuresNativeTextTracks = false;
9834      } else if (options.nativeCaptions === true || options.nativeTextTracks === true) {
9835        _this.featuresNativeTextTracks = true;
9836      }
9837
9838      if (!_this.featuresNativeTextTracks) {
9839        _this.emulateTextTracks();
9840      }
9841
9842      _this.autoRemoteTextTracks_ = new ALL.text.ListClass();
9843
9844      _this.initTrackListeners();
9845
9846      // Turn on component tap events only if not using native controls
9847      if (!options.nativeControlsForTouch) {
9848        _this.emitTapEvents();
9849      }
9850
9851      if (_this.constructor) {
9852        _this.name_ = _this.constructor.name || 'Unknown Tech';
9853      }
9854      return _this;
9855    }
9856
9857    /**
9858     * A special function to trigger source set in a way that will allow player
9859     * to re-trigger if the player or tech are not ready yet.
9860     *
9861     * @fires Tech#sourceset
9862     * @param {string} src The source string at the time of the source changing.
9863     */
9864
9865
9866    Tech.prototype.triggerSourceset = function triggerSourceset(src) {
9867      var _this2 = this;
9868
9869      if (!this.isReady_) {
9870        // on initial ready we have to trigger source set
9871        // 1ms after ready so that player can watch for it.
9872        this.one('ready', function () {
9873          return _this2.setTimeout(function () {
9874            return _this2.triggerSourceset(src);
9875          }, 1);
9876        });
9877      }
9878
9879      /**
9880       * Fired when the source is set on the tech causing the media element
9881       * to reload.
9882       *
9883       * @see {@link Player#event:sourceset}
9884       * @event Tech#sourceset
9885       * @type {EventTarget~Event}
9886       */
9887      this.trigger({
9888        src: src,
9889        type: 'sourceset'
9890      });
9891    };
9892
9893    /* Fallbacks for unsupported event types
9894    ================================================================================ */
9895
9896    /**
9897     * Polyfill the `progress` event for browsers that don't support it natively.
9898     *
9899     * @see {@link Tech#trackProgress}
9900     */
9901
9902
9903    Tech.prototype.manualProgressOn = function manualProgressOn() {
9904      this.on('durationchange', this.onDurationChange);
9905
9906      this.manualProgress = true;
9907
9908      // Trigger progress watching when a source begins loading
9909      this.one('ready', this.trackProgress);
9910    };
9911
9912    /**
9913     * Turn off the polyfill for `progress` events that was created in
9914     * {@link Tech#manualProgressOn}
9915     */
9916
9917
9918    Tech.prototype.manualProgressOff = function manualProgressOff() {
9919      this.manualProgress = false;
9920      this.stopTrackingProgress();
9921
9922      this.off('durationchange', this.onDurationChange);
9923    };
9924
9925    /**
9926     * This is used to trigger a `progress` event when the buffered percent changes. It
9927     * sets an interval function that will be called every 500 milliseconds to check if the
9928     * buffer end percent has changed.
9929     *
9930     * > This function is called by {@link Tech#manualProgressOn}
9931     *
9932     * @param {EventTarget~Event} event
9933     *        The `ready` event that caused this to run.
9934     *
9935     * @listens Tech#ready
9936     * @fires Tech#progress
9937     */
9938
9939
9940    Tech.prototype.trackProgress = function trackProgress(event) {
9941      this.stopTrackingProgress();
9942      this.progressInterval = this.setInterval(bind(this, function () {
9943        // Don't trigger unless buffered amount is greater than last time
9944
9945        var numBufferedPercent = this.bufferedPercent();
9946
9947        if (this.bufferedPercent_ !== numBufferedPercent) {
9948          /**
9949           * See {@link Player#progress}
9950           *
9951           * @event Tech#progress
9952           * @type {EventTarget~Event}
9953           */
9954          this.trigger('progress');
9955        }
9956
9957        this.bufferedPercent_ = numBufferedPercent;
9958
9959        if (numBufferedPercent === 1) {
9960          this.stopTrackingProgress();
9961        }
9962      }), 500);
9963    };
9964
9965    /**
9966     * Update our internal duration on a `durationchange` event by calling
9967     * {@link Tech#duration}.
9968     *
9969     * @param {EventTarget~Event} event
9970     *        The `durationchange` event that caused this to run.
9971     *
9972     * @listens Tech#durationchange
9973     */
9974
9975
9976    Tech.prototype.onDurationChange = function onDurationChange(event) {
9977      this.duration_ = this.duration();
9978    };
9979
9980    /**
9981     * Get and create a `TimeRange` object for buffering.
9982     *
9983     * @return {TimeRange}
9984     *         The time range object that was created.
9985     */
9986
9987
9988    Tech.prototype.buffered = function buffered() {
9989      return createTimeRanges(0, 0);
9990    };
9991
9992    /**
9993     * Get the percentage of the current video that is currently buffered.
9994     *
9995     * @return {number}
9996     *         A number from 0 to 1 that represents the decimal percentage of the
9997     *         video that is buffered.
9998     *
9999     */
10000
10001
10002    Tech.prototype.bufferedPercent = function bufferedPercent$$1() {
10003      return bufferedPercent(this.buffered(), this.duration_);
10004    };
10005
10006    /**
10007     * Turn off the polyfill for `progress` events that was created in
10008     * {@link Tech#manualProgressOn}
10009     * Stop manually tracking progress events by clearing the interval that was set in
10010     * {@link Tech#trackProgress}.
10011     */
10012
10013
10014    Tech.prototype.stopTrackingProgress = function stopTrackingProgress() {
10015      this.clearInterval(this.progressInterval);
10016    };
10017
10018    /**
10019     * Polyfill the `timeupdate` event for browsers that don't support it.
10020     *
10021     * @see {@link Tech#trackCurrentTime}
10022     */
10023
10024
10025    Tech.prototype.manualTimeUpdatesOn = function manualTimeUpdatesOn() {
10026      this.manualTimeUpdates = true;
10027
10028      this.on('play', this.trackCurrentTime);
10029      this.on('pause', this.stopTrackingCurrentTime);
10030    };
10031
10032    /**
10033     * Turn off the polyfill for `timeupdate` events that was created in
10034     * {@link Tech#manualTimeUpdatesOn}
10035     */
10036
10037
10038    Tech.prototype.manualTimeUpdatesOff = function manualTimeUpdatesOff() {
10039      this.manualTimeUpdates = false;
10040      this.stopTrackingCurrentTime();
10041      this.off('play', this.trackCurrentTime);
10042      this.off('pause', this.stopTrackingCurrentTime);
10043    };
10044
10045    /**
10046     * Sets up an interval function to track current time and trigger `timeupdate` every
10047     * 250 milliseconds.
10048     *
10049     * @listens Tech#play
10050     * @triggers Tech#timeupdate
10051     */
10052
10053
10054    Tech.prototype.trackCurrentTime = function trackCurrentTime() {
10055      if (this.currentTimeInterval) {
10056        this.stopTrackingCurrentTime();
10057      }
10058      this.currentTimeInterval = this.setInterval(function () {
10059        /**
10060         * Triggered at an interval of 250ms to indicated that time is passing in the video.
10061         *
10062         * @event Tech#timeupdate
10063         * @type {EventTarget~Event}
10064         */
10065        this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
10066
10067        // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
10068      }, 250);
10069    };
10070
10071    /**
10072     * Stop the interval function created in {@link Tech#trackCurrentTime} so that the
10073     * `timeupdate` event is no longer triggered.
10074     *
10075     * @listens {Tech#pause}
10076     */
10077
10078
10079    Tech.prototype.stopTrackingCurrentTime = function stopTrackingCurrentTime() {
10080      this.clearInterval(this.currentTimeInterval);
10081
10082      // #1002 - if the video ends right before the next timeupdate would happen,
10083      // the progress bar won't make it all the way to the end
10084      this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
10085    };
10086
10087    /**
10088     * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList},
10089     * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech.
10090     *
10091     * @fires Component#dispose
10092     */
10093
10094
10095    Tech.prototype.dispose = function dispose() {
10096
10097      // clear out all tracks because we can't reuse them between techs
10098      this.clearTracks(NORMAL.names);
10099
10100      // Turn off any manual progress or timeupdate tracking
10101      if (this.manualProgress) {
10102        this.manualProgressOff();
10103      }
10104
10105      if (this.manualTimeUpdates) {
10106        this.manualTimeUpdatesOff();
10107      }
10108
10109      _Component.prototype.dispose.call(this);
10110    };
10111
10112    /**
10113     * Clear out a single `TrackList` or an array of `TrackLists` given their names.
10114     *
10115     * > Note: Techs without source handlers should call this between sources for `video`
10116     *         & `audio` tracks. You don't want to use them between tracks!
10117     *
10118     * @param {string[]|string} types
10119     *        TrackList names to clear, valid names are `video`, `audio`, and
10120     *        `text`.
10121     */
10122
10123
10124    Tech.prototype.clearTracks = function clearTracks(types) {
10125      var _this3 = this;
10126
10127      types = [].concat(types);
10128      // clear out all tracks because we can't reuse them between techs
10129      types.forEach(function (type) {
10130        var list = _this3[type + 'Tracks']() || [];
10131        var i = list.length;
10132
10133        while (i--) {
10134          var track = list[i];
10135
10136          if (type === 'text') {
10137            _this3.removeRemoteTextTrack(track);
10138          }
10139          list.removeTrack(track);
10140        }
10141      });
10142    };
10143
10144    /**
10145     * Remove any TextTracks added via addRemoteTextTrack that are
10146     * flagged for automatic garbage collection
10147     */
10148
10149
10150    Tech.prototype.cleanupAutoTextTracks = function cleanupAutoTextTracks() {
10151      var list = this.autoRemoteTextTracks_ || [];
10152      var i = list.length;
10153
10154      while (i--) {
10155        var track = list[i];
10156
10157        this.removeRemoteTextTrack(track);
10158      }
10159    };
10160
10161    /**
10162     * Reset the tech, which will removes all sources and reset the internal readyState.
10163     *
10164     * @abstract
10165     */
10166
10167
10168    Tech.prototype.reset = function reset() {};
10169
10170    /**
10171     * Get or set an error on the Tech.
10172     *
10173     * @param {MediaError} [err]
10174     *        Error to set on the Tech
10175     *
10176     * @return {MediaError|null}
10177     *         The current error object on the tech, or null if there isn't one.
10178     */
10179
10180
10181    Tech.prototype.error = function error(err) {
10182      if (err !== undefined) {
10183        this.error_ = new MediaError(err);
10184        this.trigger('error');
10185      }
10186      return this.error_;
10187    };
10188
10189    /**
10190     * Returns the `TimeRange`s that have been played through for the current source.
10191     *
10192     * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`.
10193     *         It only checks whether the source has played at all or not.
10194     *
10195     * @return {TimeRange}
10196     *         - A single time range if this video has played
10197     *         - An empty set of ranges if not.
10198     */
10199
10200
10201    Tech.prototype.played = function played() {
10202      if (this.hasStarted_) {
10203        return createTimeRanges(0, 0);
10204      }
10205      return createTimeRanges();
10206    };
10207
10208    /**
10209     * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was
10210     * previously called.
10211     *
10212     * @fires Tech#timeupdate
10213     */
10214
10215
10216    Tech.prototype.setCurrentTime = function setCurrentTime() {
10217      // improve the accuracy of manual timeupdates
10218      if (this.manualTimeUpdates) {
10219        /**
10220         * A manual `timeupdate` event.
10221         *
10222         * @event Tech#timeupdate
10223         * @type {EventTarget~Event}
10224         */
10225        this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
10226      }
10227    };
10228
10229    /**
10230     * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and
10231     * {@link TextTrackList} events.
10232     *
10233     * This adds {@link EventTarget~EventListeners} for `addtrack`, and  `removetrack`.
10234     *
10235     * @fires Tech#audiotrackchange
10236     * @fires Tech#videotrackchange
10237     * @fires Tech#texttrackchange
10238     */
10239
10240
10241    Tech.prototype.initTrackListeners = function initTrackListeners() {
10242      var _this4 = this;
10243
10244      /**
10245       * Triggered when tracks are added or removed on the Tech {@link AudioTrackList}
10246       *
10247       * @event Tech#audiotrackchange
10248       * @type {EventTarget~Event}
10249       */
10250
10251      /**
10252       * Triggered when tracks are added or removed on the Tech {@link VideoTrackList}
10253       *
10254       * @event Tech#videotrackchange
10255       * @type {EventTarget~Event}
10256       */
10257
10258      /**
10259       * Triggered when tracks are added or removed on the Tech {@link TextTrackList}
10260       *
10261       * @event Tech#texttrackchange
10262       * @type {EventTarget~Event}
10263       */
10264      NORMAL.names.forEach(function (name) {
10265        var props = NORMAL[name];
10266        var trackListChanges = function trackListChanges() {
10267          _this4.trigger(name + 'trackchange');
10268        };
10269
10270        var tracks = _this4[props.getterName]();
10271
10272        tracks.addEventListener('removetrack', trackListChanges);
10273        tracks.addEventListener('addtrack', trackListChanges);
10274
10275        _this4.on('dispose', function () {
10276          tracks.removeEventListener('removetrack', trackListChanges);
10277          tracks.removeEventListener('addtrack', trackListChanges);
10278        });
10279      });
10280    };
10281
10282    /**
10283     * Emulate TextTracks using vtt.js if necessary
10284     *
10285     * @fires Tech#vttjsloaded
10286     * @fires Tech#vttjserror
10287     */
10288
10289
10290    Tech.prototype.addWebVttScript_ = function addWebVttScript_() {
10291      var _this5 = this;
10292
10293      if (window_1.WebVTT) {
10294        return;
10295      }
10296
10297      // Initially, Tech.el_ is a child of a dummy-div wait until the Component system
10298      // signals that the Tech is ready at which point Tech.el_ is part of the DOM
10299      // before inserting the WebVTT script
10300      if (document_1.body.contains(this.el())) {
10301
10302        // load via require if available and vtt.js script location was not passed in
10303        // as an option. novtt builds will turn the above require call into an empty object
10304        // which will cause this if check to always fail.
10305        if (!this.options_['vtt.js'] && isPlain(browserIndex) && Object.keys(browserIndex).length > 0) {
10306          this.trigger('vttjsloaded');
10307          return;
10308        }
10309
10310        // load vtt.js via the script location option or the cdn of no location was
10311        // passed in
10312        var script = document_1.createElement('script');
10313
10314        script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js';
10315        script.onload = function () {
10316          /**
10317           * Fired when vtt.js is loaded.
10318           *
10319           * @event Tech#vttjsloaded
10320           * @type {EventTarget~Event}
10321           */
10322          _this5.trigger('vttjsloaded');
10323        };
10324        script.onerror = function () {
10325          /**
10326           * Fired when vtt.js was not loaded due to an error
10327           *
10328           * @event Tech#vttjsloaded
10329           * @type {EventTarget~Event}
10330           */
10331          _this5.trigger('vttjserror');
10332        };
10333        this.on('dispose', function () {
10334          script.onload = null;
10335          script.onerror = null;
10336        });
10337        // but have not loaded yet and we set it to true before the inject so that
10338        // we don't overwrite the injected window.WebVTT if it loads right away
10339        window_1.WebVTT = true;
10340        this.el().parentNode.appendChild(script);
10341      } else {
10342        this.ready(this.addWebVttScript_);
10343      }
10344    };
10345
10346    /**
10347     * Emulate texttracks
10348     *
10349     */
10350
10351
10352    Tech.prototype.emulateTextTracks = function emulateTextTracks() {
10353      var _this6 = this;
10354
10355      var tracks = this.textTracks();
10356      var remoteTracks = this.remoteTextTracks();
10357      var handleAddTrack = function handleAddTrack(e) {
10358        return tracks.addTrack(e.track);
10359      };
10360      var handleRemoveTrack = function handleRemoveTrack(e) {
10361        return tracks.removeTrack(e.track);
10362      };
10363
10364      remoteTracks.on('addtrack', handleAddTrack);
10365      remoteTracks.on('removetrack', handleRemoveTrack);
10366
10367      this.addWebVttScript_();
10368
10369      var updateDisplay = function updateDisplay() {
10370        return _this6.trigger('texttrackchange');
10371      };
10372
10373      var textTracksChanges = function textTracksChanges() {
10374        updateDisplay();
10375
10376        for (var i = 0; i < tracks.length; i++) {
10377          var track = tracks[i];
10378
10379          track.removeEventListener('cuechange', updateDisplay);
10380          if (track.mode === 'showing') {
10381            track.addEventListener('cuechange', updateDisplay);
10382          }
10383        }
10384      };
10385
10386      textTracksChanges();
10387      tracks.addEventListener('change', textTracksChanges);
10388      tracks.addEventListener('addtrack', textTracksChanges);
10389      tracks.addEventListener('removetrack', textTracksChanges);
10390
10391      this.on('dispose', function () {
10392        remoteTracks.off('addtrack', handleAddTrack);
10393        remoteTracks.off('removetrack', handleRemoveTrack);
10394        tracks.removeEventListener('change', textTracksChanges);
10395        tracks.removeEventListener('addtrack', textTracksChanges);
10396        tracks.removeEventListener('removetrack', textTracksChanges);
10397
10398        for (var i = 0; i < tracks.length; i++) {
10399          var track = tracks[i];
10400
10401          track.removeEventListener('cuechange', updateDisplay);
10402        }
10403      });
10404    };
10405
10406    /**
10407     * Create and returns a remote {@link TextTrack} object.
10408     *
10409     * @param {string} kind
10410     *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
10411     *
10412     * @param {string} [label]
10413     *        Label to identify the text track
10414     *
10415     * @param {string} [language]
10416     *        Two letter language abbreviation
10417     *
10418     * @return {TextTrack}
10419     *         The TextTrack that gets created.
10420     */
10421
10422
10423    Tech.prototype.addTextTrack = function addTextTrack(kind, label, language) {
10424      if (!kind) {
10425        throw new Error('TextTrack kind is required but was not provided');
10426      }
10427
10428      return createTrackHelper(this, kind, label, language);
10429    };
10430
10431    /**
10432     * Create an emulated TextTrack for use by addRemoteTextTrack
10433     *
10434     * This is intended to be overridden by classes that inherit from
10435     * Tech in order to create native or custom TextTracks.
10436     *
10437     * @param {Object} options
10438     *        The object should contain the options to initialize the TextTrack with.
10439     *
10440     * @param {string} [options.kind]
10441     *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
10442     *
10443     * @param {string} [options.label].
10444     *        Label to identify the text track
10445     *
10446     * @param {string} [options.language]
10447     *        Two letter language abbreviation.
10448     *
10449     * @return {HTMLTrackElement}
10450     *         The track element that gets created.
10451     */
10452
10453
10454    Tech.prototype.createRemoteTextTrack = function createRemoteTextTrack(options) {
10455      var track = mergeOptions(options, {
10456        tech: this
10457      });
10458
10459      return new REMOTE.remoteTextEl.TrackClass(track);
10460    };
10461
10462    /**
10463     * Creates a remote text track object and returns an html track element.
10464     *
10465     * > Note: This can be an emulated {@link HTMLTrackElement} or a native one.
10466     *
10467     * @param {Object} options
10468     *        See {@link Tech#createRemoteTextTrack} for more detailed properties.
10469     *
10470     * @param {boolean} [manualCleanup=true]
10471     *        - When false: the TextTrack will be automatically removed from the video
10472     *          element whenever the source changes
10473     *        - When True: The TextTrack will have to be cleaned up manually
10474     *
10475     * @return {HTMLTrackElement}
10476     *         An Html Track Element.
10477     *
10478     * @deprecated The default functionality for this function will be equivalent
10479     *             to "manualCleanup=false" in the future. The manualCleanup parameter will
10480     *             also be removed.
10481     */
10482
10483
10484    Tech.prototype.addRemoteTextTrack = function addRemoteTextTrack() {
10485      var _this7 = this;
10486
10487      var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
10488      var manualCleanup = arguments[1];
10489
10490      var htmlTrackElement = this.createRemoteTextTrack(options);
10491
10492      if (manualCleanup !== true && manualCleanup !== false) {
10493        // deprecation warning
10494        log$1.warn('Calling addRemoteTextTrack without explicitly setting the "manualCleanup" parameter to `true` is deprecated and default to `false` in future version of video.js');
10495        manualCleanup = true;
10496      }
10497
10498      // store HTMLTrackElement and TextTrack to remote list
10499      this.remoteTextTrackEls().addTrackElement_(htmlTrackElement);
10500      this.remoteTextTracks().addTrack(htmlTrackElement.track);
10501
10502      if (manualCleanup !== true) {
10503        // create the TextTrackList if it doesn't exist
10504        this.ready(function () {
10505          return _this7.autoRemoteTextTracks_.addTrack(htmlTrackElement.track);
10506        });
10507      }
10508
10509      return htmlTrackElement;
10510    };
10511
10512    /**
10513     * Remove a remote text track from the remote `TextTrackList`.
10514     *
10515     * @param {TextTrack} track
10516     *        `TextTrack` to remove from the `TextTrackList`
10517     */
10518
10519
10520    Tech.prototype.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
10521      var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track);
10522
10523      // remove HTMLTrackElement and TextTrack from remote list
10524      this.remoteTextTrackEls().removeTrackElement_(trackElement);
10525      this.remoteTextTracks().removeTrack(track);
10526      this.autoRemoteTextTracks_.removeTrack(track);
10527    };
10528
10529    /**
10530     * Gets available media playback quality metrics as specified by the W3C's Media
10531     * Playback Quality API.
10532     *
10533     * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
10534     *
10535     * @return {Object}
10536     *         An object with supported media playback quality metrics
10537     *
10538     * @abstract
10539     */
10540
10541
10542    Tech.prototype.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
10543      return {};
10544    };
10545
10546    /**
10547     * A method to set a poster from a `Tech`.
10548     *
10549     * @abstract
10550     */
10551
10552
10553    Tech.prototype.setPoster = function setPoster() {};
10554
10555    /**
10556     * A method to check for the presence of the 'playsinline' <video> attribute.
10557     *
10558     * @abstract
10559     */
10560
10561
10562    Tech.prototype.playsinline = function playsinline() {};
10563
10564    /**
10565     * A method to set or unset the 'playsinline' <video> attribute.
10566     *
10567     * @abstract
10568     */
10569
10570
10571    Tech.prototype.setPlaysinline = function setPlaysinline() {};
10572
10573    /**
10574     * Attempt to force override of native audio tracks.
10575     *
10576     * @param {Boolean} override - If set to true native audio will be overridden,
10577     * otherwise native audio will potentially be used.
10578     *
10579     * @abstract
10580     */
10581
10582
10583    Tech.prototype.overrideNativeAudioTracks = function overrideNativeAudioTracks() {};
10584
10585    /**
10586     * Attempt to force override of native video tracks.
10587     *
10588     * @param {Boolean} override - If set to true native video will be overridden,
10589     * otherwise native video will potentially be used.
10590     *
10591     * @abstract
10592     */
10593
10594
10595    Tech.prototype.overrideNativeVideoTracks = function overrideNativeVideoTracks() {};
10596
10597    /*
10598     * Check if the tech can support the given mime-type.
10599     *
10600     * The base tech does not support any type, but source handlers might
10601     * overwrite this.
10602     *
10603     * @param  {string} type
10604     *         The mimetype to check for support
10605     *
10606     * @return {string}
10607     *         'probably', 'maybe', or empty string
10608     *
10609     * @see [Spec]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType}
10610     *
10611     * @abstract
10612     */
10613
10614
10615    Tech.prototype.canPlayType = function canPlayType() {
10616      return '';
10617    };
10618
10619    /**
10620     * Check if the type is supported by this tech.
10621     *
10622     * The base tech does not support any type, but source handlers might
10623     * overwrite this.
10624     *
10625     * @param {string} type
10626     *        The media type to check
10627     * @return {string} Returns the native video element's response
10628     */
10629
10630
10631    Tech.canPlayType = function canPlayType() {
10632      return '';
10633    };
10634
10635    /**
10636     * Check if the tech can support the given source
10637     * @param {Object} srcObj
10638     *        The source object
10639     * @param {Object} options
10640     *        The options passed to the tech
10641     * @return {string} 'probably', 'maybe', or '' (empty string)
10642     */
10643
10644
10645    Tech.canPlaySource = function canPlaySource(srcObj, options) {
10646      return Tech.canPlayType(srcObj.type);
10647    };
10648
10649    /*
10650     * Return whether the argument is a Tech or not.
10651     * Can be passed either a Class like `Html5` or a instance like `player.tech_`
10652     *
10653     * @param {Object} component
10654     *        The item to check
10655     *
10656     * @return {boolean}
10657     *         Whether it is a tech or not
10658     *         - True if it is a tech
10659     *         - False if it is not
10660     */
10661
10662
10663    Tech.isTech = function isTech(component) {
10664      return component.prototype instanceof Tech || component instanceof Tech || component === Tech;
10665    };
10666
10667    /**
10668     * Registers a `Tech` into a shared list for videojs.
10669     *
10670     * @param {string} name
10671     *        Name of the `Tech` to register.
10672     *
10673     * @param {Object} tech
10674     *        The `Tech` class to register.
10675     */
10676
10677
10678    Tech.registerTech = function registerTech(name, tech) {
10679      if (!Tech.techs_) {
10680        Tech.techs_ = {};
10681      }
10682
10683      if (!Tech.isTech(tech)) {
10684        throw new Error('Tech ' + name + ' must be a Tech');
10685      }
10686
10687      if (!Tech.canPlayType) {
10688        throw new Error('Techs must have a static canPlayType method on them');
10689      }
10690      if (!Tech.canPlaySource) {
10691        throw new Error('Techs must have a static canPlaySource method on them');
10692      }
10693
10694      name = toTitleCase(name);
10695
10696      Tech.techs_[name] = tech;
10697      if (name !== 'Tech') {
10698        // camel case the techName for use in techOrder
10699        Tech.defaultTechOrder_.push(name);
10700      }
10701      return tech;
10702    };
10703
10704    /**
10705     * Get a `Tech` from the shared list by name.
10706     *
10707     * @param {string} name
10708     *        `camelCase` or `TitleCase` name of the Tech to get
10709     *
10710     * @return {Tech|undefined}
10711     *         The `Tech` or undefined if there was no tech with the name requested.
10712     */
10713
10714
10715    Tech.getTech = function getTech(name) {
10716      if (!name) {
10717        return;
10718      }
10719
10720      name = toTitleCase(name);
10721
10722      if (Tech.techs_ && Tech.techs_[name]) {
10723        return Tech.techs_[name];
10724      }
10725
10726      if (window_1 && window_1.videojs && window_1.videojs[name]) {
10727        log$1.warn('The ' + name + ' tech was added to the videojs object when it should be registered using videojs.registerTech(name, tech)');
10728        return window_1.videojs[name];
10729      }
10730    };
10731
10732    return Tech;
10733  }(Component);
10734
10735  /**
10736   * Get the {@link VideoTrackList}
10737   *
10738   * @returns {VideoTrackList}
10739   * @method Tech.prototype.videoTracks
10740   */
10741
10742  /**
10743   * Get the {@link AudioTrackList}
10744   *
10745   * @returns {AudioTrackList}
10746   * @method Tech.prototype.audioTracks
10747   */
10748
10749  /**
10750   * Get the {@link TextTrackList}
10751   *
10752   * @returns {TextTrackList}
10753   * @method Tech.prototype.textTracks
10754   */
10755
10756  /**
10757   * Get the remote element {@link TextTrackList}
10758   *
10759   * @returns {TextTrackList}
10760   * @method Tech.prototype.remoteTextTracks
10761   */
10762
10763  /**
10764   * Get the remote element {@link HtmlTrackElementList}
10765   *
10766   * @returns {HtmlTrackElementList}
10767   * @method Tech.prototype.remoteTextTrackEls
10768   */
10769
10770  ALL.names.forEach(function (name) {
10771    var props = ALL[name];
10772
10773    Tech.prototype[props.getterName] = function () {
10774      this[props.privateName] = this[props.privateName] || new props.ListClass();
10775      return this[props.privateName];
10776    };
10777  });
10778
10779  /**
10780   * List of associated text tracks
10781   *
10782   * @type {TextTrackList}
10783   * @private
10784   * @property Tech#textTracks_
10785   */
10786
10787  /**
10788   * List of associated audio tracks.
10789   *
10790   * @type {AudioTrackList}
10791   * @private
10792   * @property Tech#audioTracks_
10793   */
10794
10795  /**
10796   * List of associated video tracks.
10797   *
10798   * @type {VideoTrackList}
10799   * @private
10800   * @property Tech#videoTracks_
10801   */
10802
10803  /**
10804   * Boolean indicating whether the `Tech` supports volume control.
10805   *
10806   * @type {boolean}
10807   * @default
10808   */
10809  Tech.prototype.featuresVolumeControl = true;
10810
10811  /**
10812   * Boolean indicating whether the `Tech` supports fullscreen resize control.
10813   * Resizing plugins using request fullscreen reloads the plugin
10814   *
10815   * @type {boolean}
10816   * @default
10817   */
10818  Tech.prototype.featuresFullscreenResize = false;
10819
10820  /**
10821   * Boolean indicating whether the `Tech` supports changing the speed at which the video
10822   * plays. Examples:
10823   *   - Set player to play 2x (twice) as fast
10824   *   - Set player to play 0.5x (half) as fast
10825   *
10826   * @type {boolean}
10827   * @default
10828   */
10829  Tech.prototype.featuresPlaybackRate = false;
10830
10831  /**
10832   * Boolean indicating whether the `Tech` supports the `progress` event. This is currently
10833   * not triggered by video-js-swf. This will be used to determine if
10834   * {@link Tech#manualProgressOn} should be called.
10835   *
10836   * @type {boolean}
10837   * @default
10838   */
10839  Tech.prototype.featuresProgressEvents = false;
10840
10841  /**
10842   * Boolean indicating whether the `Tech` supports the `sourceset` event.
10843   *
10844   * A tech should set this to `true` and then use {@link Tech#triggerSourceset}
10845   * to trigger a {@link Tech#event:sourceset} at the earliest time after getting
10846   * a new source.
10847   *
10848   * @type {boolean}
10849   * @default
10850   */
10851  Tech.prototype.featuresSourceset = false;
10852
10853  /**
10854   * Boolean indicating whether the `Tech` supports the `timeupdate` event. This is currently
10855   * not triggered by video-js-swf. This will be used to determine if
10856   * {@link Tech#manualTimeUpdates} should be called.
10857   *
10858   * @type {boolean}
10859   * @default
10860   */
10861  Tech.prototype.featuresTimeupdateEvents = false;
10862
10863  /**
10864   * Boolean indicating whether the `Tech` supports the native `TextTrack`s.
10865   * This will help us integrate with native `TextTrack`s if the browser supports them.
10866   *
10867   * @type {boolean}
10868   * @default
10869   */
10870  Tech.prototype.featuresNativeTextTracks = false;
10871
10872  /**
10873   * A functional mixin for techs that want to use the Source Handler pattern.
10874   * Source handlers are scripts for handling specific formats.
10875   * The source handler pattern is used for adaptive formats (HLS, DASH) that
10876   * manually load video data and feed it into a Source Buffer (Media Source Extensions)
10877   * Example: `Tech.withSourceHandlers.call(MyTech);`
10878   *
10879   * @param {Tech} _Tech
10880   *        The tech to add source handler functions to.
10881   *
10882   * @mixes Tech~SourceHandlerAdditions
10883   */
10884  Tech.withSourceHandlers = function (_Tech) {
10885
10886    /**
10887     * Register a source handler
10888     *
10889     * @param {Function} handler
10890     *        The source handler class
10891     *
10892     * @param {number} [index]
10893     *        Register it at the following index
10894     */
10895    _Tech.registerSourceHandler = function (handler, index) {
10896      var handlers = _Tech.sourceHandlers;
10897
10898      if (!handlers) {
10899        handlers = _Tech.sourceHandlers = [];
10900      }
10901
10902      if (index === undefined) {
10903        // add to the end of the list
10904        index = handlers.length;
10905      }
10906
10907      handlers.splice(index, 0, handler);
10908    };
10909
10910    /**
10911     * Check if the tech can support the given type. Also checks the
10912     * Techs sourceHandlers.
10913     *
10914     * @param {string} type
10915     *         The mimetype to check.
10916     *
10917     * @return {string}
10918     *         'probably', 'maybe', or '' (empty string)
10919     */
10920    _Tech.canPlayType = function (type) {
10921      var handlers = _Tech.sourceHandlers || [];
10922      var can = void 0;
10923
10924      for (var i = 0; i < handlers.length; i++) {
10925        can = handlers[i].canPlayType(type);
10926
10927        if (can) {
10928          return can;
10929        }
10930      }
10931
10932      return '';
10933    };
10934
10935    /**
10936     * Returns the first source handler that supports the source.
10937     *
10938     * TODO: Answer question: should 'probably' be prioritized over 'maybe'
10939     *
10940     * @param {Tech~SourceObject} source
10941     *        The source object
10942     *
10943     * @param {Object} options
10944     *        The options passed to the tech
10945     *
10946     * @return {SourceHandler|null}
10947     *          The first source handler that supports the source or null if
10948     *          no SourceHandler supports the source
10949     */
10950    _Tech.selectSourceHandler = function (source, options) {
10951      var handlers = _Tech.sourceHandlers || [];
10952      var can = void 0;
10953
10954      for (var i = 0; i < handlers.length; i++) {
10955        can = handlers[i].canHandleSource(source, options);
10956
10957        if (can) {
10958          return handlers[i];
10959        }
10960      }
10961
10962      return null;
10963    };
10964
10965    /**
10966     * Check if the tech can support the given source.
10967     *
10968     * @param {Tech~SourceObject} srcObj
10969     *        The source object
10970     *
10971     * @param {Object} options
10972     *        The options passed to the tech
10973     *
10974     * @return {string}
10975     *         'probably', 'maybe', or '' (empty string)
10976     */
10977    _Tech.canPlaySource = function (srcObj, options) {
10978      var sh = _Tech.selectSourceHandler(srcObj, options);
10979
10980      if (sh) {
10981        return sh.canHandleSource(srcObj, options);
10982      }
10983
10984      return '';
10985    };
10986
10987    /**
10988     * When using a source handler, prefer its implementation of
10989     * any function normally provided by the tech.
10990     */
10991    var deferrable = ['seekable', 'seeking', 'duration'];
10992
10993    /**
10994     * A wrapper around {@link Tech#seekable} that will call a `SourceHandler`s seekable
10995     * function if it exists, with a fallback to the Techs seekable function.
10996     *
10997     * @method _Tech.seekable
10998     */
10999
11000    /**
11001     * A wrapper around {@link Tech#duration} that will call a `SourceHandler`s duration
11002     * function if it exists, otherwise it will fallback to the techs duration function.
11003     *
11004     * @method _Tech.duration
11005     */
11006
11007    deferrable.forEach(function (fnName) {
11008      var originalFn = this[fnName];
11009
11010      if (typeof originalFn !== 'function') {
11011        return;
11012      }
11013
11014      this[fnName] = function () {
11015        if (this.sourceHandler_ && this.sourceHandler_[fnName]) {
11016          return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments);
11017        }
11018        return originalFn.apply(this, arguments);
11019      };
11020    }, _Tech.prototype);
11021
11022    /**
11023     * Create a function for setting the source using a source object
11024     * and source handlers.
11025     * Should never be called unless a source handler was found.
11026     *
11027     * @param {Tech~SourceObject} source
11028     *        A source object with src and type keys
11029     */
11030    _Tech.prototype.setSource = function (source) {
11031      var sh = _Tech.selectSourceHandler(source, this.options_);
11032
11033      if (!sh) {
11034        // Fall back to a native source hander when unsupported sources are
11035        // deliberately set
11036        if (_Tech.nativeSourceHandler) {
11037          sh = _Tech.nativeSourceHandler;
11038        } else {
11039          log$1.error('No source handler found for the current source.');
11040        }
11041      }
11042
11043      // Dispose any existing source handler
11044      this.disposeSourceHandler();
11045      this.off('dispose', this.disposeSourceHandler);
11046
11047      if (sh !== _Tech.nativeSourceHandler) {
11048        this.currentSource_ = source;
11049      }
11050
11051      this.sourceHandler_ = sh.handleSource(source, this, this.options_);
11052      this.on('dispose', this.disposeSourceHandler);
11053    };
11054
11055    /**
11056     * Clean up any existing SourceHandlers and listeners when the Tech is disposed.
11057     *
11058     * @listens Tech#dispose
11059     */
11060    _Tech.prototype.disposeSourceHandler = function () {
11061      // if we have a source and get another one
11062      // then we are loading something new
11063      // than clear all of our current tracks
11064      if (this.currentSource_) {
11065        this.clearTracks(['audio', 'video']);
11066        this.currentSource_ = null;
11067      }
11068
11069      // always clean up auto-text tracks
11070      this.cleanupAutoTextTracks();
11071
11072      if (this.sourceHandler_) {
11073
11074        if (this.sourceHandler_.dispose) {
11075          this.sourceHandler_.dispose();
11076        }
11077
11078        this.sourceHandler_ = null;
11079      }
11080    };
11081  };
11082
11083  // The base Tech class needs to be registered as a Component. It is the only
11084  // Tech that can be registered as a Component.
11085  Component.registerComponent('Tech', Tech);
11086  Tech.registerTech('Tech', Tech);
11087
11088  /**
11089   * A list of techs that should be added to techOrder on Players
11090   *
11091   * @private
11092   */
11093  Tech.defaultTechOrder_ = [];
11094
11095  var middlewares = {};
11096  var middlewareInstances = {};
11097
11098  var TERMINATOR = {};
11099
11100  function use(type, middleware) {
11101    middlewares[type] = middlewares[type] || [];
11102    middlewares[type].push(middleware);
11103  }
11104
11105  function setSource(player, src, next) {
11106    player.setTimeout(function () {
11107      return setSourceHelper(src, middlewares[src.type], next, player);
11108    }, 1);
11109  }
11110
11111  function setTech(middleware, tech) {
11112    middleware.forEach(function (mw) {
11113      return mw.setTech && mw.setTech(tech);
11114    });
11115  }
11116
11117  /**
11118   * Calls a getter on the tech first, through each middleware
11119   * from right to left to the player.
11120   */
11121  function get$1(middleware, tech, method) {
11122    return middleware.reduceRight(middlewareIterator(method), tech[method]());
11123  }
11124
11125  /**
11126   * Takes the argument given to the player and calls the setter method on each
11127   * middleware from left to right to the tech.
11128   */
11129  function set$1(middleware, tech, method, arg) {
11130    return tech[method](middleware.reduce(middlewareIterator(method), arg));
11131  }
11132
11133  /**
11134   * Takes the argument given to the player and calls the `call` version of the method
11135   * on each middleware from left to right.
11136   * Then, call the passed in method on the tech and return the result unchanged
11137   * back to the player, through middleware, this time from right to left.
11138   */
11139  function mediate(middleware, tech, method) {
11140    var arg = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
11141
11142    var callMethod = 'call' + toTitleCase(method);
11143    var middlewareValue = middleware.reduce(middlewareIterator(callMethod), arg);
11144    var terminated = middlewareValue === TERMINATOR;
11145    var returnValue = terminated ? null : tech[method](middlewareValue);
11146
11147    executeRight(middleware, method, returnValue, terminated);
11148
11149    return returnValue;
11150  }
11151
11152  var allowedGetters = {
11153    buffered: 1,
11154    currentTime: 1,
11155    duration: 1,
11156    seekable: 1,
11157    played: 1,
11158    paused: 1
11159  };
11160
11161  var allowedSetters = {
11162    setCurrentTime: 1
11163  };
11164
11165  var allowedMediators = {
11166    play: 1,
11167    pause: 1
11168  };
11169
11170  function middlewareIterator(method) {
11171    return function (value, mw) {
11172      // if the previous middleware terminated, pass along the termination
11173      if (value === TERMINATOR) {
11174        return TERMINATOR;
11175      }
11176
11177      if (mw[method]) {
11178        return mw[method](value);
11179      }
11180
11181      return value;
11182    };
11183  }
11184
11185  function executeRight(mws, method, value, terminated) {
11186    for (var i = mws.length - 1; i >= 0; i--) {
11187      var mw = mws[i];
11188
11189      if (mw[method]) {
11190        mw[method](terminated, value);
11191      }
11192    }
11193  }
11194
11195  function clearCacheForPlayer(player) {
11196    middlewareInstances[player.id()] = null;
11197  }
11198
11199  /**
11200   * {
11201   *  [playerId]: [[mwFactory, mwInstance], ...]
11202   * }
11203   */
11204  function getOrCreateFactory(player, mwFactory) {
11205    var mws = middlewareInstances[player.id()];
11206    var mw = null;
11207
11208    if (mws === undefined || mws === null) {
11209      mw = mwFactory(player);
11210      middlewareInstances[player.id()] = [[mwFactory, mw]];
11211      return mw;
11212    }
11213
11214    for (var i = 0; i < mws.length; i++) {
11215      var _mws$i = mws[i],
11216          mwf = _mws$i[0],
11217          mwi = _mws$i[1];
11218
11219
11220      if (mwf !== mwFactory) {
11221        continue;
11222      }
11223
11224      mw = mwi;
11225    }
11226
11227    if (mw === null) {
11228      mw = mwFactory(player);
11229      mws.push([mwFactory, mw]);
11230    }
11231
11232    return mw;
11233  }
11234
11235  function setSourceHelper() {
11236    var src = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
11237    var middleware = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
11238    var next = arguments[2];
11239    var player = arguments[3];
11240    var acc = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : [];
11241    var lastRun = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false;
11242    var mwFactory = middleware[0],
11243        mwrest = middleware.slice(1);
11244
11245    // if mwFactory is a string, then we're at a fork in the road
11246
11247    if (typeof mwFactory === 'string') {
11248      setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun);
11249
11250      // if we have an mwFactory, call it with the player to get the mw,
11251      // then call the mw's setSource method
11252    } else if (mwFactory) {
11253      var mw = getOrCreateFactory(player, mwFactory);
11254
11255      mw.setSource(assign({}, src), function (err, _src) {
11256
11257        // something happened, try the next middleware on the current level
11258        // make sure to use the old src
11259        if (err) {
11260          return setSourceHelper(src, mwrest, next, player, acc, lastRun);
11261        }
11262
11263        // we've succeeded, now we need to go deeper
11264        acc.push(mw);
11265
11266        // if it's the same type, continue down the current chain
11267        // otherwise, we want to go down the new chain
11268        setSourceHelper(_src, src.type === _src.type ? mwrest : middlewares[_src.type], next, player, acc, lastRun);
11269      });
11270    } else if (mwrest.length) {
11271      setSourceHelper(src, mwrest, next, player, acc, lastRun);
11272    } else if (lastRun) {
11273      next(src, acc);
11274    } else {
11275      setSourceHelper(src, middlewares['*'], next, player, acc, true);
11276    }
11277  }
11278
11279  /**
11280   * Mimetypes
11281   *
11282   * @see http://hul.harvard.edu/ois/////systems/wax/wax-public-help/mimetypes.htm
11283   * @typedef Mimetypes~Kind
11284   * @enum
11285   */
11286  var MimetypesKind = {
11287    opus: 'video/ogg',
11288    ogv: 'video/ogg',
11289    mp4: 'video/mp4',
11290    mov: 'video/mp4',
11291    m4v: 'video/mp4',
11292    mkv: 'video/x-matroska',
11293    mp3: 'audio/mpeg',
11294    aac: 'audio/aac',
11295    oga: 'audio/ogg',
11296    m3u8: 'application/x-mpegURL'
11297  };
11298
11299  /**
11300   * Get the mimetype of a given src url if possible
11301   *
11302   * @param {string} src
11303   *        The url to the src
11304   *
11305   * @return {string}
11306   *         return the mimetype if it was known or empty string otherwise
11307   */
11308  var getMimetype = function getMimetype() {
11309    var src = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
11310
11311    var ext = getFileExtension(src);
11312    var mimetype = MimetypesKind[ext.toLowerCase()];
11313
11314    return mimetype || '';
11315  };
11316
11317  /**
11318   * Find the mime type of a given source string if possible. Uses the player
11319   * source cache.
11320   *
11321   * @param {Player} player
11322   *        The player object
11323   *
11324   * @param {string} src
11325   *        The source string
11326   *
11327   * @return {string}
11328   *         The type that was found
11329   */
11330  var findMimetype = function findMimetype(player, src) {
11331    if (!src) {
11332      return '';
11333    }
11334
11335    // 1. check for the type in the `source` cache
11336    if (player.cache_.source.src === src && player.cache_.source.type) {
11337      return player.cache_.source.type;
11338    }
11339
11340    // 2. see if we have this source in our `currentSources` cache
11341    var matchingSources = player.cache_.sources.filter(function (s) {
11342      return s.src === src;
11343    });
11344
11345    if (matchingSources.length) {
11346      return matchingSources[0].type;
11347    }
11348
11349    // 3. look for the src url in source elements and use the type there
11350    var sources = player.$$('source');
11351
11352    for (var i = 0; i < sources.length; i++) {
11353      var s = sources[i];
11354
11355      if (s.type && s.src && s.src === src) {
11356        return s.type;
11357      }
11358    }
11359
11360    // 4. finally fallback to our list of mime types based on src url extension
11361    return getMimetype(src);
11362  };
11363
11364  /**
11365   * @module filter-source
11366   */
11367
11368  /**
11369   * Filter out single bad source objects or multiple source objects in an
11370   * array. Also flattens nested source object arrays into a 1 dimensional
11371   * array of source objects.
11372   *
11373   * @param {Tech~SourceObject|Tech~SourceObject[]} src
11374   *        The src object to filter
11375   *
11376   * @return {Tech~SourceObject[]}
11377   *         An array of sourceobjects containing only valid sources
11378   *
11379   * @private
11380   */
11381  var filterSource = function filterSource(src) {
11382    // traverse array
11383    if (Array.isArray(src)) {
11384      var newsrc = [];
11385
11386      src.forEach(function (srcobj) {
11387        srcobj = filterSource(srcobj);
11388
11389        if (Array.isArray(srcobj)) {
11390          newsrc = newsrc.concat(srcobj);
11391        } else if (isObject(srcobj)) {
11392          newsrc.push(srcobj);
11393        }
11394      });
11395
11396      src = newsrc;
11397    } else if (typeof src === 'string' && src.trim()) {
11398      // convert string into object
11399      src = [fixSource({ src: src })];
11400    } else if (isObject(src) && typeof src.src === 'string' && src.src && src.src.trim()) {
11401      // src is already valid
11402      src = [fixSource(src)];
11403    } else {
11404      // invalid source, turn it into an empty array
11405      src = [];
11406    }
11407
11408    return src;
11409  };
11410
11411  /**
11412   * Checks src mimetype, adding it when possible
11413   *
11414   * @param {Tech~SourceObject} src
11415   *        The src object to check
11416   * @return {Tech~SourceObject}
11417   *        src Object with known type
11418   */
11419  function fixSource(src) {
11420    var mimetype = getMimetype(src.src);
11421
11422    if (!src.type && mimetype) {
11423      src.type = mimetype;
11424    }
11425
11426    return src;
11427  }
11428
11429  /**
11430   * @file loader.js
11431   */
11432
11433  /**
11434   * The `MediaLoader` is the `Component` that decides which playback technology to load
11435   * when a player is initialized.
11436   *
11437   * @extends Component
11438   */
11439
11440  var MediaLoader = function (_Component) {
11441    inherits(MediaLoader, _Component);
11442
11443    /**
11444     * Create an instance of this class.
11445     *
11446     * @param {Player} player
11447     *        The `Player` that this class should attach to.
11448     *
11449     * @param {Object} [options]
11450     *        The key/value store of player options.
11451     *
11452     * @param {Component~ReadyCallback} [ready]
11453     *        The function that is run when this component is ready.
11454     */
11455    function MediaLoader(player, options, ready) {
11456      classCallCheck(this, MediaLoader);
11457
11458      // MediaLoader has no element
11459      var options_ = mergeOptions({ createEl: false }, options);
11460
11461      // If there are no sources when the player is initialized,
11462      // load the first supported playback technology.
11463
11464      var _this = possibleConstructorReturn(this, _Component.call(this, player, options_, ready));
11465
11466      if (!options.playerOptions.sources || options.playerOptions.sources.length === 0) {
11467        for (var i = 0, j = options.playerOptions.techOrder; i < j.length; i++) {
11468          var techName = toTitleCase(j[i]);
11469          var tech = Tech.getTech(techName);
11470
11471          // Support old behavior of techs being registered as components.
11472          // Remove once that deprecated behavior is removed.
11473          if (!techName) {
11474            tech = Component.getComponent(techName);
11475          }
11476
11477          // Check if the browser supports this technology
11478          if (tech && tech.isSupported()) {
11479            player.loadTech_(techName);
11480            break;
11481          }
11482        }
11483      } else {
11484        // Loop through playback technologies (HTML5, Flash) and check for support.
11485        // Then load the best source.
11486        // A few assumptions here:
11487        //   All playback technologies respect preload false.
11488        player.src(options.playerOptions.sources);
11489      }
11490      return _this;
11491    }
11492
11493    return MediaLoader;
11494  }(Component);
11495
11496  Component.registerComponent('MediaLoader', MediaLoader);
11497
11498  /**
11499   * @file clickable-component.js
11500   */
11501
11502  /**
11503   * Clickable Component which is clickable or keyboard actionable,
11504   * but is not a native HTML button.
11505   *
11506   * @extends Component
11507   */
11508
11509  var ClickableComponent = function (_Component) {
11510    inherits(ClickableComponent, _Component);
11511
11512    /**
11513     * Creates an instance of this class.
11514     *
11515     * @param  {Player} player
11516     *         The `Player` that this class should be attached to.
11517     *
11518     * @param  {Object} [options]
11519     *         The key/value store of player options.
11520     */
11521    function ClickableComponent(player, options) {
11522      classCallCheck(this, ClickableComponent);
11523
11524      var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
11525
11526      _this.emitTapEvents();
11527
11528      _this.enable();
11529      return _this;
11530    }
11531
11532    /**
11533     * Create the `Component`s DOM element.
11534     *
11535     * @param {string} [tag=div]
11536     *        The element's node type.
11537     *
11538     * @param {Object} [props={}]
11539     *        An object of properties that should be set on the element.
11540     *
11541     * @param {Object} [attributes={}]
11542     *        An object of attributes that should be set on the element.
11543     *
11544     * @return {Element}
11545     *         The element that gets created.
11546     */
11547
11548
11549    ClickableComponent.prototype.createEl = function createEl$$1() {
11550      var tag = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'div';
11551      var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
11552      var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
11553
11554      props = assign({
11555        innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>',
11556        className: this.buildCSSClass(),
11557        tabIndex: 0
11558      }, props);
11559
11560      if (tag === 'button') {
11561        log$1.error('Creating a ClickableComponent with an HTML element of ' + tag + ' is not supported; use a Button instead.');
11562      }
11563
11564      // Add ARIA attributes for clickable element which is not a native HTML button
11565      attributes = assign({
11566        role: 'button'
11567      }, attributes);
11568
11569      this.tabIndex_ = props.tabIndex;
11570
11571      var el = _Component.prototype.createEl.call(this, tag, props, attributes);
11572
11573      this.createControlTextEl(el);
11574
11575      return el;
11576    };
11577
11578    ClickableComponent.prototype.dispose = function dispose() {
11579      // remove controlTextEl_ on dispose
11580      this.controlTextEl_ = null;
11581
11582      _Component.prototype.dispose.call(this);
11583    };
11584
11585    /**
11586     * Create a control text element on this `Component`
11587     *
11588     * @param {Element} [el]
11589     *        Parent element for the control text.
11590     *
11591     * @return {Element}
11592     *         The control text element that gets created.
11593     */
11594
11595
11596    ClickableComponent.prototype.createControlTextEl = function createControlTextEl(el) {
11597      this.controlTextEl_ = createEl('span', {
11598        className: 'vjs-control-text'
11599      }, {
11600        // let the screen reader user know that the text of the element may change
11601        'aria-live': 'polite'
11602      });
11603
11604      if (el) {
11605        el.appendChild(this.controlTextEl_);
11606      }
11607
11608      this.controlText(this.controlText_, el);
11609
11610      return this.controlTextEl_;
11611    };
11612
11613    /**
11614     * Get or set the localize text to use for the controls on the `Component`.
11615     *
11616     * @param {string} [text]
11617     *        Control text for element.
11618     *
11619     * @param {Element} [el=this.el()]
11620     *        Element to set the title on.
11621     *
11622     * @return {string}
11623     *         - The control text when getting
11624     */
11625
11626
11627    ClickableComponent.prototype.controlText = function controlText(text) {
11628      var el = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.el();
11629
11630      if (text === undefined) {
11631        return this.controlText_ || 'Need Text';
11632      }
11633
11634      var localizedText = this.localize(text);
11635
11636      this.controlText_ = text;
11637      textContent(this.controlTextEl_, localizedText);
11638      if (!this.nonIconControl) {
11639        // Set title attribute if only an icon is shown
11640        el.setAttribute('title', localizedText);
11641      }
11642    };
11643
11644    /**
11645     * Builds the default DOM `className`.
11646     *
11647     * @return {string}
11648     *         The DOM `className` for this object.
11649     */
11650
11651
11652    ClickableComponent.prototype.buildCSSClass = function buildCSSClass() {
11653      return 'vjs-control vjs-button ' + _Component.prototype.buildCSSClass.call(this);
11654    };
11655
11656    /**
11657     * Enable this `Component`s element.
11658     */
11659
11660
11661    ClickableComponent.prototype.enable = function enable() {
11662      if (!this.enabled_) {
11663        this.enabled_ = true;
11664        this.removeClass('vjs-disabled');
11665        this.el_.setAttribute('aria-disabled', 'false');
11666        if (typeof this.tabIndex_ !== 'undefined') {
11667          this.el_.setAttribute('tabIndex', this.tabIndex_);
11668        }
11669        this.on(['tap', 'click'], this.handleClick);
11670        this.on('focus', this.handleFocus);
11671        this.on('blur', this.handleBlur);
11672      }
11673    };
11674
11675    /**
11676     * Disable this `Component`s element.
11677     */
11678
11679
11680    ClickableComponent.prototype.disable = function disable() {
11681      this.enabled_ = false;
11682      this.addClass('vjs-disabled');
11683      this.el_.setAttribute('aria-disabled', 'true');
11684      if (typeof this.tabIndex_ !== 'undefined') {
11685        this.el_.removeAttribute('tabIndex');
11686      }
11687      this.off(['tap', 'click'], this.handleClick);
11688      this.off('focus', this.handleFocus);
11689      this.off('blur', this.handleBlur);
11690    };
11691
11692    /**
11693     * This gets called when a `ClickableComponent` gets:
11694     * - Clicked (via the `click` event, listening starts in the constructor)
11695     * - Tapped (via the `tap` event, listening starts in the constructor)
11696     * - The following things happen in order:
11697     *   1. {@link ClickableComponent#handleFocus} is called via a `focus` event on the
11698     *      `ClickableComponent`.
11699     *   2. {@link ClickableComponent#handleFocus} adds a listener for `keydown` on using
11700     *      {@link ClickableComponent#handleKeyPress}.
11701     *   3. `ClickableComponent` has not had a `blur` event (`blur` means that focus was lost). The user presses
11702     *      the space or enter key.
11703     *   4. {@link ClickableComponent#handleKeyPress} calls this function with the `keydown`
11704     *      event as a parameter.
11705     *
11706     * @param {EventTarget~Event} event
11707     *        The `keydown`, `tap`, or `click` event that caused this function to be
11708     *        called.
11709     *
11710     * @listens tap
11711     * @listens click
11712     * @abstract
11713     */
11714
11715
11716    ClickableComponent.prototype.handleClick = function handleClick(event) {};
11717
11718    /**
11719     * This gets called when a `ClickableComponent` gains focus via a `focus` event.
11720     * Turns on listening for `keydown` events. When they happen it
11721     * calls `this.handleKeyPress`.
11722     *
11723     * @param {EventTarget~Event} event
11724     *        The `focus` event that caused this function to be called.
11725     *
11726     * @listens focus
11727     */
11728
11729
11730    ClickableComponent.prototype.handleFocus = function handleFocus(event) {
11731      on(document_1, 'keydown', bind(this, this.handleKeyPress));
11732    };
11733
11734    /**
11735     * Called when this ClickableComponent has focus and a key gets pressed down. By
11736     * default it will call `this.handleClick` when the key is space or enter.
11737     *
11738     * @param {EventTarget~Event} event
11739     *        The `keydown` event that caused this function to be called.
11740     *
11741     * @listens keydown
11742     */
11743
11744
11745    ClickableComponent.prototype.handleKeyPress = function handleKeyPress(event) {
11746
11747      // Support Space (32) or Enter (13) key operation to fire a click event
11748      if (event.which === 32 || event.which === 13) {
11749        event.preventDefault();
11750        this.trigger('click');
11751      } else if (_Component.prototype.handleKeyPress) {
11752
11753        // Pass keypress handling up for unsupported keys
11754        _Component.prototype.handleKeyPress.call(this, event);
11755      }
11756    };
11757
11758    /**
11759     * Called when a `ClickableComponent` loses focus. Turns off the listener for
11760     * `keydown` events. Which Stops `this.handleKeyPress` from getting called.
11761     *
11762     * @param {EventTarget~Event} event
11763     *        The `blur` event that caused this function to be called.
11764     *
11765     * @listens blur
11766     */
11767
11768
11769    ClickableComponent.prototype.handleBlur = function handleBlur(event) {
11770      off(document_1, 'keydown', bind(this, this.handleKeyPress));
11771    };
11772
11773    return ClickableComponent;
11774  }(Component);
11775
11776  Component.registerComponent('ClickableComponent', ClickableComponent);
11777
11778  /**
11779   * @file poster-image.js
11780   */
11781
11782  /**
11783   * A `ClickableComponent` that handles showing the poster image for the player.
11784   *
11785   * @extends ClickableComponent
11786   */
11787
11788  var PosterImage = function (_ClickableComponent) {
11789    inherits(PosterImage, _ClickableComponent);
11790
11791    /**
11792     * Create an instance of this class.
11793     *
11794     * @param {Player} player
11795     *        The `Player` that this class should attach to.
11796     *
11797     * @param {Object} [options]
11798     *        The key/value store of player options.
11799     */
11800    function PosterImage(player, options) {
11801      classCallCheck(this, PosterImage);
11802
11803      var _this = possibleConstructorReturn(this, _ClickableComponent.call(this, player, options));
11804
11805      _this.update();
11806      player.on('posterchange', bind(_this, _this.update));
11807      return _this;
11808    }
11809
11810    /**
11811     * Clean up and dispose of the `PosterImage`.
11812     */
11813
11814
11815    PosterImage.prototype.dispose = function dispose() {
11816      this.player().off('posterchange', this.update);
11817      _ClickableComponent.prototype.dispose.call(this);
11818    };
11819
11820    /**
11821     * Create the `PosterImage`s DOM element.
11822     *
11823     * @return {Element}
11824     *         The element that gets created.
11825     */
11826
11827
11828    PosterImage.prototype.createEl = function createEl$$1() {
11829      var el = createEl('div', {
11830        className: 'vjs-poster',
11831
11832        // Don't want poster to be tabbable.
11833        tabIndex: -1
11834      });
11835
11836      return el;
11837    };
11838
11839    /**
11840     * An {@link EventTarget~EventListener} for {@link Player#posterchange} events.
11841     *
11842     * @listens Player#posterchange
11843     *
11844     * @param {EventTarget~Event} [event]
11845     *        The `Player#posterchange` event that triggered this function.
11846     */
11847
11848
11849    PosterImage.prototype.update = function update(event) {
11850      var url = this.player().poster();
11851
11852      this.setSrc(url);
11853
11854      // If there's no poster source we should display:none on this component
11855      // so it's not still clickable or right-clickable
11856      if (url) {
11857        this.show();
11858      } else {
11859        this.hide();
11860      }
11861    };
11862
11863    /**
11864     * Set the source of the `PosterImage` depending on the display method.
11865     *
11866     * @param {string} url
11867     *        The URL to the source for the `PosterImage`.
11868     */
11869
11870
11871    PosterImage.prototype.setSrc = function setSrc(url) {
11872      var backgroundImage = '';
11873
11874      // Any falsy value should stay as an empty string, otherwise
11875      // this will throw an extra error
11876      if (url) {
11877        backgroundImage = 'url("' + url + '")';
11878      }
11879
11880      this.el_.style.backgroundImage = backgroundImage;
11881    };
11882
11883    /**
11884     * An {@link EventTarget~EventListener} for clicks on the `PosterImage`. See
11885     * {@link ClickableComponent#handleClick} for instances where this will be triggered.
11886     *
11887     * @listens tap
11888     * @listens click
11889     * @listens keydown
11890     *
11891     * @param {EventTarget~Event} event
11892     +        The `click`, `tap` or `keydown` event that caused this function to be called.
11893     */
11894
11895
11896    PosterImage.prototype.handleClick = function handleClick(event) {
11897      // We don't want a click to trigger playback when controls are disabled
11898      if (!this.player_.controls()) {
11899        return;
11900      }
11901
11902      if (this.player_.paused()) {
11903        silencePromise(this.player_.play());
11904      } else {
11905        this.player_.pause();
11906      }
11907    };
11908
11909    return PosterImage;
11910  }(ClickableComponent);
11911
11912  Component.registerComponent('PosterImage', PosterImage);
11913
11914  /**
11915   * @file text-track-display.js
11916   */
11917
11918  var darkGray = '#222';
11919  var lightGray = '#ccc';
11920  var fontMap = {
11921    monospace: 'monospace',
11922    sansSerif: 'sans-serif',
11923    serif: 'serif',
11924    monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace',
11925    monospaceSerif: '"Courier New", monospace',
11926    proportionalSansSerif: 'sans-serif',
11927    proportionalSerif: 'serif',
11928    casual: '"Comic Sans MS", Impact, fantasy',
11929    script: '"Monotype Corsiva", cursive',
11930    smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif'
11931  };
11932
11933  /**
11934   * Construct an rgba color from a given hex color code.
11935   *
11936   * @param {number} color
11937   *        Hex number for color, like #f0e.
11938   *
11939   * @param {number} opacity
11940   *        Value for opacity, 0.0 - 1.0.
11941   *
11942   * @return {string}
11943   *         The rgba color that was created, like 'rgba(255, 0, 0, 0.3)'.
11944   *
11945   * @private
11946   */
11947  function constructColor(color, opacity) {
11948    return 'rgba(' +
11949    // color looks like "#f0e"
11950    parseInt(color[1] + color[1], 16) + ',' + parseInt(color[2] + color[2], 16) + ',' + parseInt(color[3] + color[3], 16) + ',' + opacity + ')';
11951  }
11952
11953  /**
11954   * Try to update the style of a DOM element. Some style changes will throw an error,
11955   * particularly in IE8. Those should be noops.
11956   *
11957   * @param {Element} el
11958   *        The DOM element to be styled.
11959   *
11960   * @param {string} style
11961   *        The CSS property on the element that should be styled.
11962   *
11963   * @param {string} rule
11964   *        The style rule that should be applied to the property.
11965   *
11966   * @private
11967   */
11968  function tryUpdateStyle(el, style, rule) {
11969    try {
11970      el.style[style] = rule;
11971    } catch (e) {
11972
11973      // Satisfies linter.
11974      return;
11975    }
11976  }
11977
11978  /**
11979   * The component for displaying text track cues.
11980   *
11981   * @extends Component
11982   */
11983
11984  var TextTrackDisplay = function (_Component) {
11985    inherits(TextTrackDisplay, _Component);
11986
11987    /**
11988     * Creates an instance of this class.
11989     *
11990     * @param {Player} player
11991     *        The `Player` that this class should be attached to.
11992     *
11993     * @param {Object} [options]
11994     *        The key/value store of player options.
11995     *
11996     * @param {Component~ReadyCallback} [ready]
11997     *        The function to call when `TextTrackDisplay` is ready.
11998     */
11999    function TextTrackDisplay(player, options, ready) {
12000      classCallCheck(this, TextTrackDisplay);
12001
12002      var _this = possibleConstructorReturn(this, _Component.call(this, player, options, ready));
12003
12004      player.on('loadstart', bind(_this, _this.toggleDisplay));
12005      player.on('texttrackchange', bind(_this, _this.updateDisplay));
12006      player.on('loadstart', bind(_this, _this.preselectTrack));
12007
12008      // This used to be called during player init, but was causing an error
12009      // if a track should show by default and the display hadn't loaded yet.
12010      // Should probably be moved to an external track loader when we support
12011      // tracks that don't need a display.
12012      player.ready(bind(_this, function () {
12013        if (player.tech_ && player.tech_.featuresNativeTextTracks) {
12014          this.hide();
12015          return;
12016        }
12017
12018        player.on('fullscreenchange', bind(this, this.updateDisplay));
12019
12020        var tracks = this.options_.playerOptions.tracks || [];
12021
12022        for (var i = 0; i < tracks.length; i++) {
12023          this.player_.addRemoteTextTrack(tracks[i], true);
12024        }
12025
12026        this.preselectTrack();
12027      }));
12028      return _this;
12029    }
12030
12031    /**
12032    * Preselect a track following this precedence:
12033    * - matches the previously selected {@link TextTrack}'s language and kind
12034    * - matches the previously selected {@link TextTrack}'s language only
12035    * - is the first default captions track
12036    * - is the first default descriptions track
12037    *
12038    * @listens Player#loadstart
12039    */
12040
12041
12042    TextTrackDisplay.prototype.preselectTrack = function preselectTrack() {
12043      var modes = { captions: 1, subtitles: 1 };
12044      var trackList = this.player_.textTracks();
12045      var userPref = this.player_.cache_.selectedLanguage;
12046      var firstDesc = void 0;
12047      var firstCaptions = void 0;
12048      var preferredTrack = void 0;
12049
12050      for (var i = 0; i < trackList.length; i++) {
12051        var track = trackList[i];
12052
12053        if (userPref && userPref.enabled && userPref.language === track.language) {
12054          // Always choose the track that matches both language and kind
12055          if (track.kind === userPref.kind) {
12056            preferredTrack = track;
12057            // or choose the first track that matches language
12058          } else if (!preferredTrack) {
12059            preferredTrack = track;
12060          }
12061
12062          // clear everything if offTextTrackMenuItem was clicked
12063        } else if (userPref && !userPref.enabled) {
12064          preferredTrack = null;
12065          firstDesc = null;
12066          firstCaptions = null;
12067        } else if (track.default) {
12068          if (track.kind === 'descriptions' && !firstDesc) {
12069            firstDesc = track;
12070          } else if (track.kind in modes && !firstCaptions) {
12071            firstCaptions = track;
12072          }
12073        }
12074      }
12075
12076      // The preferredTrack matches the user preference and takes
12077      // precedence over all the other tracks.
12078      // So, display the preferredTrack before the first default track
12079      // and the subtitles/captions track before the descriptions track
12080      if (preferredTrack) {
12081        preferredTrack.mode = 'showing';
12082      } else if (firstCaptions) {
12083        firstCaptions.mode = 'showing';
12084      } else if (firstDesc) {
12085        firstDesc.mode = 'showing';
12086      }
12087    };
12088
12089    /**
12090     * Turn display of {@link TextTrack}'s from the current state into the other state.
12091     * There are only two states:
12092     * - 'shown'
12093     * - 'hidden'
12094     *
12095     * @listens Player#loadstart
12096     */
12097
12098
12099    TextTrackDisplay.prototype.toggleDisplay = function toggleDisplay() {
12100      if (this.player_.tech_ && this.player_.tech_.featuresNativeTextTracks) {
12101        this.hide();
12102      } else {
12103        this.show();
12104      }
12105    };
12106
12107    /**
12108     * Create the {@link Component}'s DOM element.
12109     *
12110     * @return {Element}
12111     *         The element that was created.
12112     */
12113
12114
12115    TextTrackDisplay.prototype.createEl = function createEl() {
12116      return _Component.prototype.createEl.call(this, 'div', {
12117        className: 'vjs-text-track-display'
12118      }, {
12119        'aria-live': 'off',
12120        'aria-atomic': 'true'
12121      });
12122    };
12123
12124    /**
12125     * Clear all displayed {@link TextTrack}s.
12126     */
12127
12128
12129    TextTrackDisplay.prototype.clearDisplay = function clearDisplay() {
12130      if (typeof window_1.WebVTT === 'function') {
12131        window_1.WebVTT.processCues(window_1, [], this.el_);
12132      }
12133    };
12134
12135    /**
12136     * Update the displayed TextTrack when a either a {@link Player#texttrackchange} or
12137     * a {@link Player#fullscreenchange} is fired.
12138     *
12139     * @listens Player#texttrackchange
12140     * @listens Player#fullscreenchange
12141     */
12142
12143
12144    TextTrackDisplay.prototype.updateDisplay = function updateDisplay() {
12145      var tracks = this.player_.textTracks();
12146
12147      this.clearDisplay();
12148
12149      // Track display prioritization model: if multiple tracks are 'showing',
12150      //  display the first 'subtitles' or 'captions' track which is 'showing',
12151      //  otherwise display the first 'descriptions' track which is 'showing'
12152
12153      var descriptionsTrack = null;
12154      var captionsSubtitlesTrack = null;
12155      var i = tracks.length;
12156
12157      while (i--) {
12158        var track = tracks[i];
12159
12160        if (track.mode === 'showing') {
12161          if (track.kind === 'descriptions') {
12162            descriptionsTrack = track;
12163          } else {
12164            captionsSubtitlesTrack = track;
12165          }
12166        }
12167      }
12168
12169      if (captionsSubtitlesTrack) {
12170        if (this.getAttribute('aria-live') !== 'off') {
12171          this.setAttribute('aria-live', 'off');
12172        }
12173        this.updateForTrack(captionsSubtitlesTrack);
12174      } else if (descriptionsTrack) {
12175        if (this.getAttribute('aria-live') !== 'assertive') {
12176          this.setAttribute('aria-live', 'assertive');
12177        }
12178        this.updateForTrack(descriptionsTrack);
12179      }
12180    };
12181
12182    /**
12183     * Add an {@link TextTrack} to to the {@link Tech}s {@link TextTrackList}.
12184     *
12185     * @param {TextTrack} track
12186     *        Text track object to be added to the list.
12187     */
12188
12189
12190    TextTrackDisplay.prototype.updateForTrack = function updateForTrack(track) {
12191      if (typeof window_1.WebVTT !== 'function' || !track.activeCues) {
12192        return;
12193      }
12194
12195      var cues = [];
12196
12197      for (var _i = 0; _i < track.activeCues.length; _i++) {
12198        cues.push(track.activeCues[_i]);
12199      }
12200
12201      window_1.WebVTT.processCues(window_1, cues, this.el_);
12202
12203      if (!this.player_.textTrackSettings) {
12204        return;
12205      }
12206
12207      var overrides = this.player_.textTrackSettings.getValues();
12208
12209      var i = cues.length;
12210
12211      while (i--) {
12212        var cue = cues[i];
12213
12214        if (!cue) {
12215          continue;
12216        }
12217
12218        var cueDiv = cue.displayState;
12219
12220        if (overrides.color) {
12221          cueDiv.firstChild.style.color = overrides.color;
12222        }
12223        if (overrides.textOpacity) {
12224          tryUpdateStyle(cueDiv.firstChild, 'color', constructColor(overrides.color || '#fff', overrides.textOpacity));
12225        }
12226        if (overrides.backgroundColor) {
12227          cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor;
12228        }
12229        if (overrides.backgroundOpacity) {
12230          tryUpdateStyle(cueDiv.firstChild, 'backgroundColor', constructColor(overrides.backgroundColor || '#000', overrides.backgroundOpacity));
12231        }
12232        if (overrides.windowColor) {
12233          if (overrides.windowOpacity) {
12234            tryUpdateStyle(cueDiv, 'backgroundColor', constructColor(overrides.windowColor, overrides.windowOpacity));
12235          } else {
12236            cueDiv.style.backgroundColor = overrides.windowColor;
12237          }
12238        }
12239        if (overrides.edgeStyle) {
12240          if (overrides.edgeStyle === 'dropshadow') {
12241            cueDiv.firstChild.style.textShadow = '2px 2px 3px ' + darkGray + ', 2px 2px 4px ' + darkGray + ', 2px 2px 5px ' + darkGray;
12242          } else if (overrides.edgeStyle === 'raised') {
12243            cueDiv.firstChild.style.textShadow = '1px 1px ' + darkGray + ', 2px 2px ' + darkGray + ', 3px 3px ' + darkGray;
12244          } else if (overrides.edgeStyle === 'depressed') {
12245            cueDiv.firstChild.style.textShadow = '1px 1px ' + lightGray + ', 0 1px ' + lightGray + ', -1px -1px ' + darkGray + ', 0 -1px ' + darkGray;
12246          } else if (overrides.edgeStyle === 'uniform') {
12247            cueDiv.firstChild.style.textShadow = '0 0 4px ' + darkGray + ', 0 0 4px ' + darkGray + ', 0 0 4px ' + darkGray + ', 0 0 4px ' + darkGray;
12248          }
12249        }
12250        if (overrides.fontPercent && overrides.fontPercent !== 1) {
12251          var fontSize = window_1.parseFloat(cueDiv.style.fontSize);
12252
12253          cueDiv.style.fontSize = fontSize * overrides.fontPercent + 'px';
12254          cueDiv.style.height = 'auto';
12255          cueDiv.style.top = 'auto';
12256          cueDiv.style.bottom = '2px';
12257        }
12258        if (overrides.fontFamily && overrides.fontFamily !== 'default') {
12259          if (overrides.fontFamily === 'small-caps') {
12260            cueDiv.firstChild.style.fontVariant = 'small-caps';
12261          } else {
12262            cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily];
12263          }
12264        }
12265      }
12266    };
12267
12268    return TextTrackDisplay;
12269  }(Component);
12270
12271  Component.registerComponent('TextTrackDisplay', TextTrackDisplay);
12272
12273  /**
12274   * @file loading-spinner.js
12275   */
12276
12277  /**
12278   * A loading spinner for use during waiting/loading events.
12279   *
12280   * @extends Component
12281   */
12282
12283  var LoadingSpinner = function (_Component) {
12284    inherits(LoadingSpinner, _Component);
12285
12286    function LoadingSpinner() {
12287      classCallCheck(this, LoadingSpinner);
12288      return possibleConstructorReturn(this, _Component.apply(this, arguments));
12289    }
12290
12291    /**
12292     * Create the `LoadingSpinner`s DOM element.
12293     *
12294     * @return {Element}
12295     *         The dom element that gets created.
12296     */
12297    LoadingSpinner.prototype.createEl = function createEl$$1() {
12298      var isAudio = this.player_.isAudio();
12299      var playerType = this.localize(isAudio ? 'Audio Player' : 'Video Player');
12300      var controlText = createEl('span', {
12301        className: 'vjs-control-text',
12302        innerHTML: this.localize('{1} is loading.', [playerType])
12303      });
12304
12305      var el = _Component.prototype.createEl.call(this, 'div', {
12306        className: 'vjs-loading-spinner',
12307        dir: 'ltr'
12308      });
12309
12310      el.appendChild(controlText);
12311
12312      return el;
12313    };
12314
12315    return LoadingSpinner;
12316  }(Component);
12317
12318  Component.registerComponent('LoadingSpinner', LoadingSpinner);
12319
12320  /**
12321   * @file button.js
12322   */
12323
12324  /**
12325   * Base class for all buttons.
12326   *
12327   * @extends ClickableComponent
12328   */
12329
12330  var Button = function (_ClickableComponent) {
12331    inherits(Button, _ClickableComponent);
12332
12333    function Button() {
12334      classCallCheck(this, Button);
12335      return possibleConstructorReturn(this, _ClickableComponent.apply(this, arguments));
12336    }
12337
12338    /**
12339     * Create the `Button`s DOM element.
12340     *
12341     * @param {string} [tag="button"]
12342     *        The element's node type. This argument is IGNORED: no matter what
12343     *        is passed, it will always create a `button` element.
12344     *
12345     * @param {Object} [props={}]
12346     *        An object of properties that should be set on the element.
12347     *
12348     * @param {Object} [attributes={}]
12349     *        An object of attributes that should be set on the element.
12350     *
12351     * @return {Element}
12352     *         The element that gets created.
12353     */
12354    Button.prototype.createEl = function createEl(tag) {
12355      var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
12356      var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
12357
12358      tag = 'button';
12359
12360      props = assign({
12361        innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>',
12362        className: this.buildCSSClass()
12363      }, props);
12364
12365      // Add attributes for button element
12366      attributes = assign({
12367
12368        // Necessary since the default button type is "submit"
12369        type: 'button'
12370      }, attributes);
12371
12372      var el = Component.prototype.createEl.call(this, tag, props, attributes);
12373
12374      this.createControlTextEl(el);
12375
12376      return el;
12377    };
12378
12379    /**
12380     * Add a child `Component` inside of this `Button`.
12381     *
12382     * @param {string|Component} child
12383     *        The name or instance of a child to add.
12384     *
12385     * @param {Object} [options={}]
12386     *        The key/value store of options that will get passed to children of
12387     *        the child.
12388     *
12389     * @return {Component}
12390     *         The `Component` that gets added as a child. When using a string the
12391     *         `Component` will get created by this process.
12392     *
12393     * @deprecated since version 5
12394     */
12395
12396
12397    Button.prototype.addChild = function addChild(child) {
12398      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
12399
12400      var className = this.constructor.name;
12401
12402      log$1.warn('Adding an actionable (user controllable) child to a Button (' + className + ') is not supported; use a ClickableComponent instead.');
12403
12404      // Avoid the error message generated by ClickableComponent's addChild method
12405      return Component.prototype.addChild.call(this, child, options);
12406    };
12407
12408    /**
12409     * Enable the `Button` element so that it can be activated or clicked. Use this with
12410     * {@link Button#disable}.
12411     */
12412
12413
12414    Button.prototype.enable = function enable() {
12415      _ClickableComponent.prototype.enable.call(this);
12416      this.el_.removeAttribute('disabled');
12417    };
12418
12419    /**
12420     * Disable the `Button` element so that it cannot be activated or clicked. Use this with
12421     * {@link Button#enable}.
12422     */
12423
12424
12425    Button.prototype.disable = function disable() {
12426      _ClickableComponent.prototype.disable.call(this);
12427      this.el_.setAttribute('disabled', 'disabled');
12428    };
12429
12430    /**
12431     * This gets called when a `Button` has focus and `keydown` is triggered via a key
12432     * press.
12433     *
12434     * @param {EventTarget~Event} event
12435     *        The event that caused this function to get called.
12436     *
12437     * @listens keydown
12438     */
12439
12440
12441    Button.prototype.handleKeyPress = function handleKeyPress(event) {
12442
12443      // Ignore Space (32) or Enter (13) key operation, which is handled by the browser for a button.
12444      if (event.which === 32 || event.which === 13) {
12445        return;
12446      }
12447
12448      // Pass keypress handling up for unsupported keys
12449      _ClickableComponent.prototype.handleKeyPress.call(this, event);
12450    };
12451
12452    return Button;
12453  }(ClickableComponent);
12454
12455  Component.registerComponent('Button', Button);
12456
12457  /**
12458   * @file big-play-button.js
12459   */
12460
12461  /**
12462   * The initial play button that shows before the video has played. The hiding of the
12463   * `BigPlayButton` get done via CSS and `Player` states.
12464   *
12465   * @extends Button
12466   */
12467
12468  var BigPlayButton = function (_Button) {
12469    inherits(BigPlayButton, _Button);
12470
12471    function BigPlayButton(player, options) {
12472      classCallCheck(this, BigPlayButton);
12473
12474      var _this = possibleConstructorReturn(this, _Button.call(this, player, options));
12475
12476      _this.mouseused_ = false;
12477
12478      _this.on('mousedown', _this.handleMouseDown);
12479      return _this;
12480    }
12481
12482    /**
12483     * Builds the default DOM `className`.
12484     *
12485     * @return {string}
12486     *         The DOM `className` for this object. Always returns 'vjs-big-play-button'.
12487     */
12488
12489
12490    BigPlayButton.prototype.buildCSSClass = function buildCSSClass() {
12491      return 'vjs-big-play-button';
12492    };
12493
12494    /**
12495     * This gets called when a `BigPlayButton` "clicked". See {@link ClickableComponent}
12496     * for more detailed information on what a click can be.
12497     *
12498     * @param {EventTarget~Event} event
12499     *        The `keydown`, `tap`, or `click` event that caused this function to be
12500     *        called.
12501     *
12502     * @listens tap
12503     * @listens click
12504     */
12505
12506
12507    BigPlayButton.prototype.handleClick = function handleClick(event) {
12508      var playPromise = this.player_.play();
12509
12510      // exit early if clicked via the mouse
12511      if (this.mouseused_ && event.clientX && event.clientY) {
12512        silencePromise(playPromise);
12513        return;
12514      }
12515
12516      var cb = this.player_.getChild('controlBar');
12517      var playToggle = cb && cb.getChild('playToggle');
12518
12519      if (!playToggle) {
12520        this.player_.focus();
12521        return;
12522      }
12523
12524      var playFocus = function playFocus() {
12525        return playToggle.focus();
12526      };
12527
12528      if (isPromise(playPromise)) {
12529        playPromise.then(playFocus, function () {});
12530      } else {
12531        this.setTimeout(playFocus, 1);
12532      }
12533    };
12534
12535    BigPlayButton.prototype.handleKeyPress = function handleKeyPress(event) {
12536      this.mouseused_ = false;
12537
12538      _Button.prototype.handleKeyPress.call(this, event);
12539    };
12540
12541    BigPlayButton.prototype.handleMouseDown = function handleMouseDown(event) {
12542      this.mouseused_ = true;
12543    };
12544
12545    return BigPlayButton;
12546  }(Button);
12547
12548  /**
12549   * The text that should display over the `BigPlayButton`s controls. Added to for localization.
12550   *
12551   * @type {string}
12552   * @private
12553   */
12554
12555
12556  BigPlayButton.prototype.controlText_ = 'Play Video';
12557
12558  Component.registerComponent('BigPlayButton', BigPlayButton);
12559
12560  /**
12561   * @file close-button.js
12562   */
12563
12564  /**
12565   * The `CloseButton` is a `{@link Button}` that fires a `close` event when
12566   * it gets clicked.
12567   *
12568   * @extends Button
12569   */
12570
12571  var CloseButton = function (_Button) {
12572    inherits(CloseButton, _Button);
12573
12574    /**
12575     * Creates an instance of the this class.
12576     *
12577     * @param  {Player} player
12578     *         The `Player` that this class should be attached to.
12579     *
12580     * @param  {Object} [options]
12581     *         The key/value store of player options.
12582     */
12583    function CloseButton(player, options) {
12584      classCallCheck(this, CloseButton);
12585
12586      var _this = possibleConstructorReturn(this, _Button.call(this, player, options));
12587
12588      _this.controlText(options && options.controlText || _this.localize('Close'));
12589      return _this;
12590    }
12591
12592    /**
12593     * Builds the default DOM `className`.
12594     *
12595     * @return {string}
12596     *         The DOM `className` for this object.
12597     */
12598
12599
12600    CloseButton.prototype.buildCSSClass = function buildCSSClass() {
12601      return 'vjs-close-button ' + _Button.prototype.buildCSSClass.call(this);
12602    };
12603
12604    /**
12605     * This gets called when a `CloseButton` gets clicked. See
12606     * {@link ClickableComponent#handleClick} for more information on when this will be
12607     * triggered
12608     *
12609     * @param {EventTarget~Event} event
12610     *        The `keydown`, `tap`, or `click` event that caused this function to be
12611     *        called.
12612     *
12613     * @listens tap
12614     * @listens click
12615     * @fires CloseButton#close
12616     */
12617
12618
12619    CloseButton.prototype.handleClick = function handleClick(event) {
12620
12621      /**
12622       * Triggered when the a `CloseButton` is clicked.
12623       *
12624       * @event CloseButton#close
12625       * @type {EventTarget~Event}
12626       *
12627       * @property {boolean} [bubbles=false]
12628       *           set to false so that the close event does not
12629       *           bubble up to parents if there is no listener
12630       */
12631      this.trigger({ type: 'close', bubbles: false });
12632    };
12633
12634    return CloseButton;
12635  }(Button);
12636
12637  Component.registerComponent('CloseButton', CloseButton);
12638
12639  /**
12640   * @file play-toggle.js
12641   */
12642
12643  /**
12644   * Button to toggle between play and pause.
12645   *
12646   * @extends Button
12647   */
12648
12649  var PlayToggle = function (_Button) {
12650    inherits(PlayToggle, _Button);
12651
12652    /**
12653     * Creates an instance of this class.
12654     *
12655     * @param {Player} player
12656     *        The `Player` that this class should be attached to.
12657     *
12658     * @param {Object} [options]
12659     *        The key/value store of player options.
12660     */
12661    function PlayToggle(player, options) {
12662      classCallCheck(this, PlayToggle);
12663
12664      var _this = possibleConstructorReturn(this, _Button.call(this, player, options));
12665
12666      _this.on(player, 'play', _this.handlePlay);
12667      _this.on(player, 'pause', _this.handlePause);
12668      _this.on(player, 'ended', _this.handleEnded);
12669      return _this;
12670    }
12671
12672    /**
12673     * Builds the default DOM `className`.
12674     *
12675     * @return {string}
12676     *         The DOM `className` for this object.
12677     */
12678
12679
12680    PlayToggle.prototype.buildCSSClass = function buildCSSClass() {
12681      return 'vjs-play-control ' + _Button.prototype.buildCSSClass.call(this);
12682    };
12683
12684    /**
12685     * This gets called when an `PlayToggle` is "clicked". See
12686     * {@link ClickableComponent} for more detailed information on what a click can be.
12687     *
12688     * @param {EventTarget~Event} [event]
12689     *        The `keydown`, `tap`, or `click` event that caused this function to be
12690     *        called.
12691     *
12692     * @listens tap
12693     * @listens click
12694     */
12695
12696
12697    PlayToggle.prototype.handleClick = function handleClick(event) {
12698      if (this.player_.paused()) {
12699        this.player_.play();
12700      } else {
12701        this.player_.pause();
12702      }
12703    };
12704
12705    /**
12706     * This gets called once after the video has ended and the user seeks so that
12707     * we can change the replay button back to a play button.
12708     *
12709     * @param {EventTarget~Event} [event]
12710     *        The event that caused this function to run.
12711     *
12712     * @listens Player#seeked
12713     */
12714
12715
12716    PlayToggle.prototype.handleSeeked = function handleSeeked(event) {
12717      this.removeClass('vjs-ended');
12718
12719      if (this.player_.paused()) {
12720        this.handlePause(event);
12721      } else {
12722        this.handlePlay(event);
12723      }
12724    };
12725
12726    /**
12727     * Add the vjs-playing class to the element so it can change appearance.
12728     *
12729     * @param {EventTarget~Event} [event]
12730     *        The event that caused this function to run.
12731     *
12732     * @listens Player#play
12733     */
12734
12735
12736    PlayToggle.prototype.handlePlay = function handlePlay(event) {
12737      this.removeClass('vjs-ended');
12738      this.removeClass('vjs-paused');
12739      this.addClass('vjs-playing');
12740      // change the button text to "Pause"
12741      this.controlText('Pause');
12742    };
12743
12744    /**
12745     * Add the vjs-paused class to the element so it can change appearance.
12746     *
12747     * @param {EventTarget~Event} [event]
12748     *        The event that caused this function to run.
12749     *
12750     * @listens Player#pause
12751     */
12752
12753
12754    PlayToggle.prototype.handlePause = function handlePause(event) {
12755      this.removeClass('vjs-playing');
12756      this.addClass('vjs-paused');
12757      // change the button text to "Play"
12758      this.controlText('Play');
12759    };
12760
12761    /**
12762     * Add the vjs-ended class to the element so it can change appearance
12763     *
12764     * @param {EventTarget~Event} [event]
12765     *        The event that caused this function to run.
12766     *
12767     * @listens Player#ended
12768     */
12769
12770
12771    PlayToggle.prototype.handleEnded = function handleEnded(event) {
12772      this.removeClass('vjs-playing');
12773      this.addClass('vjs-ended');
12774      // change the button text to "Replay"
12775      this.controlText('Replay');
12776
12777      // on the next seek remove the replay button
12778      this.one(this.player_, 'seeked', this.handleSeeked);
12779    };
12780
12781    return PlayToggle;
12782  }(Button);
12783
12784  /**
12785   * The text that should display over the `PlayToggle`s controls. Added for localization.
12786   *
12787   * @type {string}
12788   * @private
12789   */
12790
12791
12792  PlayToggle.prototype.controlText_ = 'Play';
12793
12794  Component.registerComponent('PlayToggle', PlayToggle);
12795
12796  /**
12797   * @file format-time.js
12798   * @module format-time
12799   */
12800
12801  /**
12802  * Format seconds as a time string, H:MM:SS or M:SS. Supplying a guide (in seconds)
12803  * will force a number of leading zeros to cover the length of the guide.
12804  *
12805  * @param {number} seconds
12806  *        Number of seconds to be turned into a string
12807  *
12808  * @param {number} guide
12809  *        Number (in seconds) to model the string after
12810  *
12811  * @return {string}
12812  *         Time formatted as H:MM:SS or M:SS
12813  */
12814  var defaultImplementation = function defaultImplementation(seconds, guide) {
12815    seconds = seconds < 0 ? 0 : seconds;
12816    var s = Math.floor(seconds % 60);
12817    var m = Math.floor(seconds / 60 % 60);
12818    var h = Math.floor(seconds / 3600);
12819    var gm = Math.floor(guide / 60 % 60);
12820    var gh = Math.floor(guide / 3600);
12821
12822    // handle invalid times
12823    if (isNaN(seconds) || seconds === Infinity) {
12824      // '-' is false for all relational operators (e.g. <, >=) so this setting
12825      // will add the minimum number of fields specified by the guide
12826      h = m = s = '-';
12827    }
12828
12829    // Check if we need to show hours
12830    h = h > 0 || gh > 0 ? h + ':' : '';
12831
12832    // If hours are showing, we may need to add a leading zero.
12833    // Always show at least one digit of minutes.
12834    m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':';
12835
12836    // Check if leading zero is need for seconds
12837    s = s < 10 ? '0' + s : s;
12838
12839    return h + m + s;
12840  };
12841
12842  var implementation = defaultImplementation;
12843
12844  /**
12845   * Replaces the default formatTime implementation with a custom implementation.
12846   *
12847   * @param {Function} customImplementation
12848   *        A function which will be used in place of the default formatTime implementation.
12849   *        Will receive the current time in seconds and the guide (in seconds) as arguments.
12850   */
12851  function setFormatTime(customImplementation) {
12852    implementation = customImplementation;
12853  }
12854
12855  /**
12856   * Resets formatTime to the default implementation.
12857   */
12858  function resetFormatTime() {
12859    implementation = defaultImplementation;
12860  }
12861
12862  function formatTime (seconds) {
12863    var guide = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : seconds;
12864
12865    return implementation(seconds, guide);
12866  }
12867
12868  /**
12869   * @file time-display.js
12870   */
12871
12872  /**
12873   * Displays the time left in the video
12874   *
12875   * @extends Component
12876   */
12877
12878  var TimeDisplay = function (_Component) {
12879    inherits(TimeDisplay, _Component);
12880
12881    /**
12882     * Creates an instance of this class.
12883     *
12884     * @param {Player} player
12885     *        The `Player` that this class should be attached to.
12886     *
12887     * @param {Object} [options]
12888     *        The key/value store of player options.
12889     */
12890    function TimeDisplay(player, options) {
12891      classCallCheck(this, TimeDisplay);
12892
12893      var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
12894
12895      _this.throttledUpdateContent = throttle(bind(_this, _this.updateContent), 25);
12896      _this.on(player, 'timeupdate', _this.throttledUpdateContent);
12897      return _this;
12898    }
12899
12900    /**
12901     * Create the `Component`'s DOM element
12902     *
12903     * @return {Element}
12904     *         The element that was created.
12905     */
12906
12907
12908    TimeDisplay.prototype.createEl = function createEl$$1(plainName) {
12909      var className = this.buildCSSClass();
12910      var el = _Component.prototype.createEl.call(this, 'div', {
12911        className: className + ' vjs-time-control vjs-control',
12912        innerHTML: '<span class="vjs-control-text">' + this.localize(this.labelText_) + '\xA0</span>'
12913      });
12914
12915      this.contentEl_ = createEl('span', {
12916        className: className + '-display'
12917      }, {
12918        // tell screen readers not to automatically read the time as it changes
12919        'aria-live': 'off'
12920      });
12921
12922      this.updateTextNode_();
12923      el.appendChild(this.contentEl_);
12924      return el;
12925    };
12926
12927    TimeDisplay.prototype.dispose = function dispose() {
12928      this.contentEl_ = null;
12929      this.textNode_ = null;
12930
12931      _Component.prototype.dispose.call(this);
12932    };
12933
12934    /**
12935     * Updates the "remaining time" text node with new content using the
12936     * contents of the `formattedTime_` property.
12937     *
12938     * @private
12939     */
12940
12941
12942    TimeDisplay.prototype.updateTextNode_ = function updateTextNode_() {
12943      if (!this.contentEl_) {
12944        return;
12945      }
12946
12947      while (this.contentEl_.firstChild) {
12948        this.contentEl_.removeChild(this.contentEl_.firstChild);
12949      }
12950
12951      this.textNode_ = document_1.createTextNode(this.formattedTime_ || this.formatTime_(0));
12952      this.contentEl_.appendChild(this.textNode_);
12953    };
12954
12955    /**
12956     * Generates a formatted time for this component to use in display.
12957     *
12958     * @param  {number} time
12959     *         A numeric time, in seconds.
12960     *
12961     * @return {string}
12962     *         A formatted time
12963     *
12964     * @private
12965     */
12966
12967
12968    TimeDisplay.prototype.formatTime_ = function formatTime_(time) {
12969      return formatTime(time);
12970    };
12971
12972    /**
12973     * Updates the time display text node if it has what was passed in changed
12974     * the formatted time.
12975     *
12976     * @param {number} time
12977     *        The time to update to
12978     *
12979     * @private
12980     */
12981
12982
12983    TimeDisplay.prototype.updateFormattedTime_ = function updateFormattedTime_(time) {
12984      var formattedTime = this.formatTime_(time);
12985
12986      if (formattedTime === this.formattedTime_) {
12987        return;
12988      }
12989
12990      this.formattedTime_ = formattedTime;
12991      this.requestAnimationFrame(this.updateTextNode_);
12992    };
12993
12994    /**
12995     * To be filled out in the child class, should update the displayed time
12996     * in accordance with the fact that the current time has changed.
12997     *
12998     * @param {EventTarget~Event} [event]
12999     *        The `timeupdate`  event that caused this to run.
13000     *
13001     * @listens Player#timeupdate
13002     */
13003
13004
13005    TimeDisplay.prototype.updateContent = function updateContent(event) {};
13006
13007    return TimeDisplay;
13008  }(Component);
13009
13010  /**
13011   * The text that is added to the `TimeDisplay` for screen reader users.
13012   *
13013   * @type {string}
13014   * @private
13015   */
13016
13017
13018  TimeDisplay.prototype.labelText_ = 'Time';
13019
13020  /**
13021   * The text that should display over the `TimeDisplay`s controls. Added to for localization.
13022   *
13023   * @type {string}
13024   * @private
13025   *
13026   * @deprecated in v7; controlText_ is not used in non-active display Components
13027   */
13028  TimeDisplay.prototype.controlText_ = 'Time';
13029
13030  Component.registerComponent('TimeDisplay', TimeDisplay);
13031
13032  /**
13033   * @file current-time-display.js
13034   */
13035
13036  /**
13037   * Displays the current time
13038   *
13039   * @extends Component
13040   */
13041
13042  var CurrentTimeDisplay = function (_TimeDisplay) {
13043    inherits(CurrentTimeDisplay, _TimeDisplay);
13044
13045    /**
13046     * Creates an instance of this class.
13047     *
13048     * @param {Player} player
13049     *        The `Player` that this class should be attached to.
13050     *
13051     * @param {Object} [options]
13052     *        The key/value store of player options.
13053     */
13054    function CurrentTimeDisplay(player, options) {
13055      classCallCheck(this, CurrentTimeDisplay);
13056
13057      var _this = possibleConstructorReturn(this, _TimeDisplay.call(this, player, options));
13058
13059      _this.on(player, 'ended', _this.handleEnded);
13060      return _this;
13061    }
13062
13063    /**
13064     * Builds the default DOM `className`.
13065     *
13066     * @return {string}
13067     *         The DOM `className` for this object.
13068     */
13069
13070
13071    CurrentTimeDisplay.prototype.buildCSSClass = function buildCSSClass() {
13072      return 'vjs-current-time';
13073    };
13074
13075    /**
13076     * Update current time display
13077     *
13078     * @param {EventTarget~Event} [event]
13079     *        The `timeupdate` event that caused this function to run.
13080     *
13081     * @listens Player#timeupdate
13082     */
13083
13084
13085    CurrentTimeDisplay.prototype.updateContent = function updateContent(event) {
13086      // Allows for smooth scrubbing, when player can't keep up.
13087      var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
13088
13089      this.updateFormattedTime_(time);
13090    };
13091
13092    /**
13093     * When the player fires ended there should be no time left. Sadly
13094     * this is not always the case, lets make it seem like that is the case
13095     * for users.
13096     *
13097     * @param {EventTarget~Event} [event]
13098     *        The `ended` event that caused this to run.
13099     *
13100     * @listens Player#ended
13101     */
13102
13103
13104    CurrentTimeDisplay.prototype.handleEnded = function handleEnded(event) {
13105      if (!this.player_.duration()) {
13106        return;
13107      }
13108      this.updateFormattedTime_(this.player_.duration());
13109    };
13110
13111    return CurrentTimeDisplay;
13112  }(TimeDisplay);
13113
13114  /**
13115   * The text that is added to the `CurrentTimeDisplay` for screen reader users.
13116   *
13117   * @type {string}
13118   * @private
13119   */
13120
13121
13122  CurrentTimeDisplay.prototype.labelText_ = 'Current Time';
13123
13124  /**
13125   * The text that should display over the `CurrentTimeDisplay`s controls. Added to for localization.
13126   *
13127   * @type {string}
13128   * @private
13129   *
13130   * @deprecated in v7; controlText_ is not used in non-active display Components
13131   */
13132  CurrentTimeDisplay.prototype.controlText_ = 'Current Time';
13133
13134  Component.registerComponent('CurrentTimeDisplay', CurrentTimeDisplay);
13135
13136  /**
13137   * @file duration-display.js
13138   */
13139
13140  /**
13141   * Displays the duration
13142   *
13143   * @extends Component
13144   */
13145
13146  var DurationDisplay = function (_TimeDisplay) {
13147    inherits(DurationDisplay, _TimeDisplay);
13148
13149    /**
13150     * Creates an instance of this class.
13151     *
13152     * @param {Player} player
13153     *        The `Player` that this class should be attached to.
13154     *
13155     * @param {Object} [options]
13156     *        The key/value store of player options.
13157     */
13158    function DurationDisplay(player, options) {
13159      classCallCheck(this, DurationDisplay);
13160
13161      // we do not want to/need to throttle duration changes,
13162      // as they should always display the changed duration as
13163      // it has changed
13164      var _this = possibleConstructorReturn(this, _TimeDisplay.call(this, player, options));
13165
13166      _this.on(player, 'durationchange', _this.updateContent);
13167
13168      // Also listen for timeupdate (in the parent) and loadedmetadata because removing those
13169      // listeners could have broken dependent applications/libraries. These
13170      // can likely be removed for 7.0.
13171      _this.on(player, 'loadedmetadata', _this.throttledUpdateContent);
13172      return _this;
13173    }
13174
13175    /**
13176     * Builds the default DOM `className`.
13177     *
13178     * @return {string}
13179     *         The DOM `className` for this object.
13180     */
13181
13182
13183    DurationDisplay.prototype.buildCSSClass = function buildCSSClass() {
13184      return 'vjs-duration';
13185    };
13186
13187    /**
13188     * Update duration time display.
13189     *
13190     * @param {EventTarget~Event} [event]
13191     *        The `durationchange`, `timeupdate`, or `loadedmetadata` event that caused
13192     *        this function to be called.
13193     *
13194     * @listens Player#durationchange
13195     * @listens Player#timeupdate
13196     * @listens Player#loadedmetadata
13197     */
13198
13199
13200    DurationDisplay.prototype.updateContent = function updateContent(event) {
13201      var duration = this.player_.duration();
13202
13203      if (duration && this.duration_ !== duration) {
13204        this.duration_ = duration;
13205        this.updateFormattedTime_(duration);
13206      }
13207    };
13208
13209    return DurationDisplay;
13210  }(TimeDisplay);
13211
13212  /**
13213   * The text that is added to the `DurationDisplay` for screen reader users.
13214   *
13215   * @type {string}
13216   * @private
13217   */
13218
13219
13220  DurationDisplay.prototype.labelText_ = 'Duration';
13221
13222  /**
13223   * The text that should display over the `DurationDisplay`s controls. Added to for localization.
13224   *
13225   * @type {string}
13226   * @private
13227   *
13228   * @deprecated in v7; controlText_ is not used in non-active display Components
13229   */
13230  DurationDisplay.prototype.controlText_ = 'Duration';
13231
13232  Component.registerComponent('DurationDisplay', DurationDisplay);
13233
13234  /**
13235   * @file time-divider.js
13236   */
13237
13238  /**
13239   * The separator between the current time and duration.
13240   * Can be hidden if it's not needed in the design.
13241   *
13242   * @extends Component
13243   */
13244
13245  var TimeDivider = function (_Component) {
13246    inherits(TimeDivider, _Component);
13247
13248    function TimeDivider() {
13249      classCallCheck(this, TimeDivider);
13250      return possibleConstructorReturn(this, _Component.apply(this, arguments));
13251    }
13252
13253    /**
13254     * Create the component's DOM element
13255     *
13256     * @return {Element}
13257     *         The element that was created.
13258     */
13259    TimeDivider.prototype.createEl = function createEl() {
13260      return _Component.prototype.createEl.call(this, 'div', {
13261        className: 'vjs-time-control vjs-time-divider',
13262        innerHTML: '<div><span>/</span></div>'
13263      });
13264    };
13265
13266    return TimeDivider;
13267  }(Component);
13268
13269  Component.registerComponent('TimeDivider', TimeDivider);
13270
13271  /**
13272   * @file remaining-time-display.js
13273   */
13274  /**
13275   * Displays the time left in the video
13276   *
13277   * @extends Component
13278   */
13279
13280  var RemainingTimeDisplay = function (_TimeDisplay) {
13281    inherits(RemainingTimeDisplay, _TimeDisplay);
13282
13283    /**
13284     * Creates an instance of this class.
13285     *
13286     * @param {Player} player
13287     *        The `Player` that this class should be attached to.
13288     *
13289     * @param {Object} [options]
13290     *        The key/value store of player options.
13291     */
13292    function RemainingTimeDisplay(player, options) {
13293      classCallCheck(this, RemainingTimeDisplay);
13294
13295      var _this = possibleConstructorReturn(this, _TimeDisplay.call(this, player, options));
13296
13297      _this.on(player, 'durationchange', _this.throttledUpdateContent);
13298      _this.on(player, 'ended', _this.handleEnded);
13299      return _this;
13300    }
13301
13302    /**
13303     * Builds the default DOM `className`.
13304     *
13305     * @return {string}
13306     *         The DOM `className` for this object.
13307     */
13308
13309
13310    RemainingTimeDisplay.prototype.buildCSSClass = function buildCSSClass() {
13311      return 'vjs-remaining-time';
13312    };
13313
13314    /**
13315     * The remaining time display prefixes numbers with a "minus" character.
13316     *
13317     * @param  {number} time
13318     *         A numeric time, in seconds.
13319     *
13320     * @return {string}
13321     *         A formatted time
13322     *
13323     * @private
13324     */
13325
13326
13327    RemainingTimeDisplay.prototype.formatTime_ = function formatTime_(time) {
13328      // TODO: The "-" should be decorative, and not announced by a screen reader
13329      return '-' + _TimeDisplay.prototype.formatTime_.call(this, time);
13330    };
13331
13332    /**
13333     * Update remaining time display.
13334     *
13335     * @param {EventTarget~Event} [event]
13336     *        The `timeupdate` or `durationchange` event that caused this to run.
13337     *
13338     * @listens Player#timeupdate
13339     * @listens Player#durationchange
13340     */
13341
13342
13343    RemainingTimeDisplay.prototype.updateContent = function updateContent(event) {
13344      if (!this.player_.duration()) {
13345        return;
13346      }
13347
13348      // @deprecated We should only use remainingTimeDisplay
13349      // as of video.js 7
13350      if (this.player_.remainingTimeDisplay) {
13351        this.updateFormattedTime_(this.player_.remainingTimeDisplay());
13352      } else {
13353        this.updateFormattedTime_(this.player_.remainingTime());
13354      }
13355    };
13356
13357    /**
13358     * When the player fires ended there should be no time left. Sadly
13359     * this is not always the case, lets make it seem like that is the case
13360     * for users.
13361     *
13362     * @param {EventTarget~Event} [event]
13363     *        The `ended` event that caused this to run.
13364     *
13365     * @listens Player#ended
13366     */
13367
13368
13369    RemainingTimeDisplay.prototype.handleEnded = function handleEnded(event) {
13370      if (!this.player_.duration()) {
13371        return;
13372      }
13373      this.updateFormattedTime_(0);
13374    };
13375
13376    return RemainingTimeDisplay;
13377  }(TimeDisplay);
13378
13379  /**
13380   * The text that is added to the `RemainingTimeDisplay` for screen reader users.
13381   *
13382   * @type {string}
13383   * @private
13384   */
13385
13386
13387  RemainingTimeDisplay.prototype.labelText_ = 'Remaining Time';
13388
13389  /**
13390   * The text that should display over the `RemainingTimeDisplay`s controls. Added to for localization.
13391   *
13392   * @type {string}
13393   * @private
13394   *
13395   * @deprecated in v7; controlText_ is not used in non-active display Components
13396   */
13397  RemainingTimeDisplay.prototype.controlText_ = 'Remaining Time';
13398
13399  Component.registerComponent('RemainingTimeDisplay', RemainingTimeDisplay);
13400
13401  /**
13402   * @file live-display.js
13403   */
13404
13405  // TODO - Future make it click to snap to live
13406
13407  /**
13408   * Displays the live indicator when duration is Infinity.
13409   *
13410   * @extends Component
13411   */
13412
13413  var LiveDisplay = function (_Component) {
13414    inherits(LiveDisplay, _Component);
13415
13416    /**
13417     * Creates an instance of this class.
13418     *
13419     * @param {Player} player
13420     *        The `Player` that this class should be attached to.
13421     *
13422     * @param {Object} [options]
13423     *        The key/value store of player options.
13424     */
13425    function LiveDisplay(player, options) {
13426      classCallCheck(this, LiveDisplay);
13427
13428      var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
13429
13430      _this.updateShowing();
13431      _this.on(_this.player(), 'durationchange', _this.updateShowing);
13432      return _this;
13433    }
13434
13435    /**
13436     * Create the `Component`'s DOM element
13437     *
13438     * @return {Element}
13439     *         The element that was created.
13440     */
13441
13442
13443    LiveDisplay.prototype.createEl = function createEl$$1() {
13444      var el = _Component.prototype.createEl.call(this, 'div', {
13445        className: 'vjs-live-control vjs-control'
13446      });
13447
13448      this.contentEl_ = createEl('div', {
13449        className: 'vjs-live-display',
13450        innerHTML: '<span class="vjs-control-text">' + this.localize('Stream Type') + '\xA0</span>' + this.localize('LIVE')
13451      }, {
13452        'aria-live': 'off'
13453      });
13454
13455      el.appendChild(this.contentEl_);
13456      return el;
13457    };
13458
13459    LiveDisplay.prototype.dispose = function dispose() {
13460      this.contentEl_ = null;
13461
13462      _Component.prototype.dispose.call(this);
13463    };
13464
13465    /**
13466     * Check the duration to see if the LiveDisplay should be showing or not. Then show/hide
13467     * it accordingly
13468     *
13469     * @param {EventTarget~Event} [event]
13470     *        The {@link Player#durationchange} event that caused this function to run.
13471     *
13472     * @listens Player#durationchange
13473     */
13474
13475
13476    LiveDisplay.prototype.updateShowing = function updateShowing(event) {
13477      if (this.player().duration() === Infinity) {
13478        this.show();
13479      } else {
13480        this.hide();
13481      }
13482    };
13483
13484    return LiveDisplay;
13485  }(Component);
13486
13487  Component.registerComponent('LiveDisplay', LiveDisplay);
13488
13489  /**
13490   * @file slider.js
13491   */
13492
13493  /**
13494   * The base functionality for a slider. Can be vertical or horizontal.
13495   * For instance the volume bar or the seek bar on a video is a slider.
13496   *
13497   * @extends Component
13498   */
13499
13500  var Slider = function (_Component) {
13501    inherits(Slider, _Component);
13502
13503    /**
13504     * Create an instance of this class
13505     *
13506     * @param {Player} player
13507     *        The `Player` that this class should be attached to.
13508     *
13509     * @param {Object} [options]
13510     *        The key/value store of player options.
13511     */
13512    function Slider(player, options) {
13513      classCallCheck(this, Slider);
13514
13515      // Set property names to bar to match with the child Slider class is looking for
13516      var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
13517
13518      _this.bar = _this.getChild(_this.options_.barName);
13519
13520      // Set a horizontal or vertical class on the slider depending on the slider type
13521      _this.vertical(!!_this.options_.vertical);
13522
13523      _this.enable();
13524      return _this;
13525    }
13526
13527    /**
13528     * Are controls are currently enabled for this slider or not.
13529     *
13530     * @return {boolean}
13531     *         true if controls are enabled, false otherwise
13532     */
13533
13534
13535    Slider.prototype.enabled = function enabled() {
13536      return this.enabled_;
13537    };
13538
13539    /**
13540     * Enable controls for this slider if they are disabled
13541     */
13542
13543
13544    Slider.prototype.enable = function enable() {
13545      if (this.enabled()) {
13546        return;
13547      }
13548
13549      this.on('mousedown', this.handleMouseDown);
13550      this.on('touchstart', this.handleMouseDown);
13551      this.on('focus', this.handleFocus);
13552      this.on('blur', this.handleBlur);
13553      this.on('click', this.handleClick);
13554
13555      this.on(this.player_, 'controlsvisible', this.update);
13556
13557      if (this.playerEvent) {
13558        this.on(this.player_, this.playerEvent, this.update);
13559      }
13560
13561      this.removeClass('disabled');
13562      this.setAttribute('tabindex', 0);
13563
13564      this.enabled_ = true;
13565    };
13566
13567    /**
13568     * Disable controls for this slider if they are enabled
13569     */
13570
13571
13572    Slider.prototype.disable = function disable() {
13573      if (!this.enabled()) {
13574        return;
13575      }
13576      var doc = this.bar.el_.ownerDocument;
13577
13578      this.off('mousedown', this.handleMouseDown);
13579      this.off('touchstart', this.handleMouseDown);
13580      this.off('focus', this.handleFocus);
13581      this.off('blur', this.handleBlur);
13582      this.off('click', this.handleClick);
13583      this.off(this.player_, 'controlsvisible', this.update);
13584      this.off(doc, 'mousemove', this.handleMouseMove);
13585      this.off(doc, 'mouseup', this.handleMouseUp);
13586      this.off(doc, 'touchmove', this.handleMouseMove);
13587      this.off(doc, 'touchend', this.handleMouseUp);
13588      this.removeAttribute('tabindex');
13589
13590      this.addClass('disabled');
13591
13592      if (this.playerEvent) {
13593        this.off(this.player_, this.playerEvent, this.update);
13594      }
13595      this.enabled_ = false;
13596    };
13597
13598    /**
13599     * Create the `Slider`s DOM element.
13600     *
13601     * @param {string} type
13602     *        Type of element to create.
13603     *
13604     * @param {Object} [props={}]
13605     *        List of properties in Object form.
13606     *
13607     * @param {Object} [attributes={}]
13608     *        list of attributes in Object form.
13609     *
13610     * @return {Element}
13611     *         The element that gets created.
13612     */
13613
13614
13615    Slider.prototype.createEl = function createEl$$1(type) {
13616      var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
13617      var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
13618
13619      // Add the slider element class to all sub classes
13620      props.className = props.className + ' vjs-slider';
13621      props = assign({
13622        tabIndex: 0
13623      }, props);
13624
13625      attributes = assign({
13626        'role': 'slider',
13627        'aria-valuenow': 0,
13628        'aria-valuemin': 0,
13629        'aria-valuemax': 100,
13630        'tabIndex': 0
13631      }, attributes);
13632
13633      return _Component.prototype.createEl.call(this, type, props, attributes);
13634    };
13635
13636    /**
13637     * Handle `mousedown` or `touchstart` events on the `Slider`.
13638     *
13639     * @param {EventTarget~Event} event
13640     *        `mousedown` or `touchstart` event that triggered this function
13641     *
13642     * @listens mousedown
13643     * @listens touchstart
13644     * @fires Slider#slideractive
13645     */
13646
13647
13648    Slider.prototype.handleMouseDown = function handleMouseDown(event) {
13649      var doc = this.bar.el_.ownerDocument;
13650
13651      if (event.type === 'mousedown') {
13652        event.preventDefault();
13653      }
13654      // Do not call preventDefault() on touchstart in Chrome
13655      // to avoid console warnings. Use a 'touch-action: none' style
13656      // instead to prevent unintented scrolling.
13657      // https://developers.google.com/web/updates/2017/01/scrolling-intervention
13658      if (event.type === 'touchstart' && !IS_CHROME) {
13659        event.preventDefault();
13660      }
13661      blockTextSelection();
13662
13663      this.addClass('vjs-sliding');
13664      /**
13665       * Triggered when the slider is in an active state
13666       *
13667       * @event Slider#slideractive
13668       * @type {EventTarget~Event}
13669       */
13670      this.trigger('slideractive');
13671
13672      this.on(doc, 'mousemove', this.handleMouseMove);
13673      this.on(doc, 'mouseup', this.handleMouseUp);
13674      this.on(doc, 'touchmove', this.handleMouseMove);
13675      this.on(doc, 'touchend', this.handleMouseUp);
13676
13677      this.handleMouseMove(event);
13678    };
13679
13680    /**
13681     * Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`.
13682     * The `mousemove` and `touchmove` events will only only trigger this function during
13683     * `mousedown` and `touchstart`. This is due to {@link Slider#handleMouseDown} and
13684     * {@link Slider#handleMouseUp}.
13685     *
13686     * @param {EventTarget~Event} event
13687     *        `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered
13688     *        this function
13689     *
13690     * @listens mousemove
13691     * @listens touchmove
13692     */
13693
13694
13695    Slider.prototype.handleMouseMove = function handleMouseMove(event) {};
13696
13697    /**
13698     * Handle `mouseup` or `touchend` events on the `Slider`.
13699     *
13700     * @param {EventTarget~Event} event
13701     *        `mouseup` or `touchend` event that triggered this function.
13702     *
13703     * @listens touchend
13704     * @listens mouseup
13705     * @fires Slider#sliderinactive
13706     */
13707
13708
13709    Slider.prototype.handleMouseUp = function handleMouseUp() {
13710      var doc = this.bar.el_.ownerDocument;
13711
13712      unblockTextSelection();
13713
13714      this.removeClass('vjs-sliding');
13715      /**
13716       * Triggered when the slider is no longer in an active state.
13717       *
13718       * @event Slider#sliderinactive
13719       * @type {EventTarget~Event}
13720       */
13721      this.trigger('sliderinactive');
13722
13723      this.off(doc, 'mousemove', this.handleMouseMove);
13724      this.off(doc, 'mouseup', this.handleMouseUp);
13725      this.off(doc, 'touchmove', this.handleMouseMove);
13726      this.off(doc, 'touchend', this.handleMouseUp);
13727
13728      this.update();
13729    };
13730
13731    /**
13732     * Update the progress bar of the `Slider`.
13733     *
13734     * @returns {number}
13735     *          The percentage of progress the progress bar represents as a
13736     *          number from 0 to 1.
13737     */
13738
13739
13740    Slider.prototype.update = function update() {
13741
13742      // In VolumeBar init we have a setTimeout for update that pops and update
13743      // to the end of the execution stack. The player is destroyed before then
13744      // update will cause an error
13745      if (!this.el_) {
13746        return;
13747      }
13748
13749      // If scrubbing, we could use a cached value to make the handle keep up
13750      // with the user's mouse. On HTML5 browsers scrubbing is really smooth, but
13751      // some flash players are slow, so we might want to utilize this later.
13752      // var progress =  (this.player_.scrubbing()) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration();
13753      var progress = this.getPercent();
13754      var bar = this.bar;
13755
13756      // If there's no bar...
13757      if (!bar) {
13758        return;
13759      }
13760
13761      // Protect against no duration and other division issues
13762      if (typeof progress !== 'number' || progress !== progress || progress < 0 || progress === Infinity) {
13763        progress = 0;
13764      }
13765
13766      // Convert to a percentage for setting
13767      var percentage = (progress * 100).toFixed(2) + '%';
13768      var style = bar.el().style;
13769
13770      // Set the new bar width or height
13771      if (this.vertical()) {
13772        style.height = percentage;
13773      } else {
13774        style.width = percentage;
13775      }
13776
13777      return progress;
13778    };
13779
13780    /**
13781     * Calculate distance for slider
13782     *
13783     * @param {EventTarget~Event} event
13784     *        The event that caused this function to run.
13785     *
13786     * @return {number}
13787     *         The current position of the Slider.
13788     *         - position.x for vertical `Slider`s
13789     *         - position.y for horizontal `Slider`s
13790     */
13791
13792
13793    Slider.prototype.calculateDistance = function calculateDistance(event) {
13794      var position = getPointerPosition(this.el_, event);
13795
13796      if (this.vertical()) {
13797        return position.y;
13798      }
13799      return position.x;
13800    };
13801
13802    /**
13803     * Handle a `focus` event on this `Slider`.
13804     *
13805     * @param {EventTarget~Event} event
13806     *        The `focus` event that caused this function to run.
13807     *
13808     * @listens focus
13809     */
13810
13811
13812    Slider.prototype.handleFocus = function handleFocus() {
13813      this.on(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress);
13814    };
13815
13816    /**
13817     * Handle a `keydown` event on the `Slider`. Watches for left, rigth, up, and down
13818     * arrow keys. This function will only be called when the slider has focus. See
13819     * {@link Slider#handleFocus} and {@link Slider#handleBlur}.
13820     *
13821     * @param {EventTarget~Event} event
13822     *        the `keydown` event that caused this function to run.
13823     *
13824     * @listens keydown
13825     */
13826
13827
13828    Slider.prototype.handleKeyPress = function handleKeyPress(event) {
13829      // Left and Down Arrows
13830      if (event.which === 37 || event.which === 40) {
13831        event.preventDefault();
13832        this.stepBack();
13833
13834        // Up and Right Arrows
13835      } else if (event.which === 38 || event.which === 39) {
13836        event.preventDefault();
13837        this.stepForward();
13838      }
13839    };
13840
13841    /**
13842     * Handle a `blur` event on this `Slider`.
13843     *
13844     * @param {EventTarget~Event} event
13845     *        The `blur` event that caused this function to run.
13846     *
13847     * @listens blur
13848     */
13849
13850    Slider.prototype.handleBlur = function handleBlur() {
13851      this.off(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress);
13852    };
13853
13854    /**
13855     * Listener for click events on slider, used to prevent clicks
13856     *   from bubbling up to parent elements like button menus.
13857     *
13858     * @param {Object} event
13859     *        Event that caused this object to run
13860     */
13861
13862
13863    Slider.prototype.handleClick = function handleClick(event) {
13864      event.stopImmediatePropagation();
13865      event.preventDefault();
13866    };
13867
13868    /**
13869     * Get/set if slider is horizontal for vertical
13870     *
13871     * @param {boolean} [bool]
13872     *        - true if slider is vertical,
13873     *        - false is horizontal
13874     *
13875     * @return {boolean}
13876     *         - true if slider is vertical, and getting
13877     *         - false if the slider is horizontal, and getting
13878     */
13879
13880
13881    Slider.prototype.vertical = function vertical(bool) {
13882      if (bool === undefined) {
13883        return this.vertical_ || false;
13884      }
13885
13886      this.vertical_ = !!bool;
13887
13888      if (this.vertical_) {
13889        this.addClass('vjs-slider-vertical');
13890      } else {
13891        this.addClass('vjs-slider-horizontal');
13892      }
13893    };
13894
13895    return Slider;
13896  }(Component);
13897
13898  Component.registerComponent('Slider', Slider);
13899
13900  /**
13901   * @file load-progress-bar.js
13902   */
13903
13904  /**
13905   * Shows loading progress
13906   *
13907   * @extends Component
13908   */
13909
13910  var LoadProgressBar = function (_Component) {
13911    inherits(LoadProgressBar, _Component);
13912
13913    /**
13914     * Creates an instance of this class.
13915     *
13916     * @param {Player} player
13917     *        The `Player` that this class should be attached to.
13918     *
13919     * @param {Object} [options]
13920     *        The key/value store of player options.
13921     */
13922    function LoadProgressBar(player, options) {
13923      classCallCheck(this, LoadProgressBar);
13924
13925      var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
13926
13927      _this.partEls_ = [];
13928      _this.on(player, 'progress', _this.update);
13929      return _this;
13930    }
13931
13932    /**
13933     * Create the `Component`'s DOM element
13934     *
13935     * @return {Element}
13936     *         The element that was created.
13937     */
13938
13939
13940    LoadProgressBar.prototype.createEl = function createEl$$1() {
13941      return _Component.prototype.createEl.call(this, 'div', {
13942        className: 'vjs-load-progress',
13943        innerHTML: '<span class="vjs-control-text"><span>' + this.localize('Loaded') + '</span>: 0%</span>'
13944      });
13945    };
13946
13947    LoadProgressBar.prototype.dispose = function dispose() {
13948      this.partEls_ = null;
13949
13950      _Component.prototype.dispose.call(this);
13951    };
13952
13953    /**
13954     * Update progress bar
13955     *
13956     * @param {EventTarget~Event} [event]
13957     *        The `progress` event that caused this function to run.
13958     *
13959     * @listens Player#progress
13960     */
13961
13962
13963    LoadProgressBar.prototype.update = function update(event) {
13964      var buffered = this.player_.buffered();
13965      var duration = this.player_.duration();
13966      var bufferedEnd = this.player_.bufferedEnd();
13967      var children = this.partEls_;
13968
13969      // get the percent width of a time compared to the total end
13970      var percentify = function percentify(time, end) {
13971        // no NaN
13972        var percent = time / end || 0;
13973
13974        return (percent >= 1 ? 1 : percent) * 100 + '%';
13975      };
13976
13977      // update the width of the progress bar
13978      this.el_.style.width = percentify(bufferedEnd, duration);
13979
13980      // add child elements to represent the individual buffered time ranges
13981      for (var i = 0; i < buffered.length; i++) {
13982        var start = buffered.start(i);
13983        var end = buffered.end(i);
13984        var part = children[i];
13985
13986        if (!part) {
13987          part = this.el_.appendChild(createEl());
13988          children[i] = part;
13989        }
13990
13991        // set the percent based on the width of the progress bar (bufferedEnd)
13992        part.style.left = percentify(start, bufferedEnd);
13993        part.style.width = percentify(end - start, bufferedEnd);
13994      }
13995
13996      // remove unused buffered range elements
13997      for (var _i = children.length; _i > buffered.length; _i--) {
13998        this.el_.removeChild(children[_i - 1]);
13999      }
14000      children.length = buffered.length;
14001    };
14002
14003    return LoadProgressBar;
14004  }(Component);
14005
14006  Component.registerComponent('LoadProgressBar', LoadProgressBar);
14007
14008  /**
14009   * @file time-tooltip.js
14010   */
14011
14012  /**
14013   * Time tooltips display a time above the progress bar.
14014   *
14015   * @extends Component
14016   */
14017
14018  var TimeTooltip = function (_Component) {
14019    inherits(TimeTooltip, _Component);
14020
14021    function TimeTooltip() {
14022      classCallCheck(this, TimeTooltip);
14023      return possibleConstructorReturn(this, _Component.apply(this, arguments));
14024    }
14025
14026    /**
14027     * Create the time tooltip DOM element
14028     *
14029     * @return {Element}
14030     *         The element that was created.
14031     */
14032    TimeTooltip.prototype.createEl = function createEl$$1() {
14033      return _Component.prototype.createEl.call(this, 'div', {
14034        className: 'vjs-time-tooltip'
14035      });
14036    };
14037
14038    /**
14039     * Updates the position of the time tooltip relative to the `SeekBar`.
14040     *
14041     * @param {Object} seekBarRect
14042     *        The `ClientRect` for the {@link SeekBar} element.
14043     *
14044     * @param {number} seekBarPoint
14045     *        A number from 0 to 1, representing a horizontal reference point
14046     *        from the left edge of the {@link SeekBar}
14047     */
14048
14049
14050    TimeTooltip.prototype.update = function update(seekBarRect, seekBarPoint, content) {
14051      var tooltipRect = getBoundingClientRect(this.el_);
14052      var playerRect = getBoundingClientRect(this.player_.el());
14053      var seekBarPointPx = seekBarRect.width * seekBarPoint;
14054
14055      // do nothing if either rect isn't available
14056      // for example, if the player isn't in the DOM for testing
14057      if (!playerRect || !tooltipRect) {
14058        return;
14059      }
14060
14061      // This is the space left of the `seekBarPoint` available within the bounds
14062      // of the player. We calculate any gap between the left edge of the player
14063      // and the left edge of the `SeekBar` and add the number of pixels in the
14064      // `SeekBar` before hitting the `seekBarPoint`
14065      var spaceLeftOfPoint = seekBarRect.left - playerRect.left + seekBarPointPx;
14066
14067      // This is the space right of the `seekBarPoint` available within the bounds
14068      // of the player. We calculate the number of pixels from the `seekBarPoint`
14069      // to the right edge of the `SeekBar` and add to that any gap between the
14070      // right edge of the `SeekBar` and the player.
14071      var spaceRightOfPoint = seekBarRect.width - seekBarPointPx + (playerRect.right - seekBarRect.right);
14072
14073      // This is the number of pixels by which the tooltip will need to be pulled
14074      // further to the right to center it over the `seekBarPoint`.
14075      var pullTooltipBy = tooltipRect.width / 2;
14076
14077      // Adjust the `pullTooltipBy` distance to the left or right depending on
14078      // the results of the space calculations above.
14079      if (spaceLeftOfPoint < pullTooltipBy) {
14080        pullTooltipBy += pullTooltipBy - spaceLeftOfPoint;
14081      } else if (spaceRightOfPoint < pullTooltipBy) {
14082        pullTooltipBy = spaceRightOfPoint;
14083      }
14084
14085      // Due to the imprecision of decimal/ratio based calculations and varying
14086      // rounding behaviors, there are cases where the spacing adjustment is off
14087      // by a pixel or two. This adds insurance to these calculations.
14088      if (pullTooltipBy < 0) {
14089        pullTooltipBy = 0;
14090      } else if (pullTooltipBy > tooltipRect.width) {
14091        pullTooltipBy = tooltipRect.width;
14092      }
14093
14094      this.el_.style.right = '-' + pullTooltipBy + 'px';
14095      textContent(this.el_, content);
14096    };
14097
14098    return TimeTooltip;
14099  }(Component);
14100
14101  Component.registerComponent('TimeTooltip', TimeTooltip);
14102
14103  /**
14104   * @file play-progress-bar.js
14105   */
14106
14107  /**
14108   * Used by {@link SeekBar} to display media playback progress as part of the
14109   * {@link ProgressControl}.
14110   *
14111   * @extends Component
14112   */
14113
14114  var PlayProgressBar = function (_Component) {
14115    inherits(PlayProgressBar, _Component);
14116
14117    function PlayProgressBar() {
14118      classCallCheck(this, PlayProgressBar);
14119      return possibleConstructorReturn(this, _Component.apply(this, arguments));
14120    }
14121
14122    /**
14123     * Create the the DOM element for this class.
14124     *
14125     * @return {Element}
14126     *         The element that was created.
14127     */
14128    PlayProgressBar.prototype.createEl = function createEl() {
14129      return _Component.prototype.createEl.call(this, 'div', {
14130        className: 'vjs-play-progress vjs-slider-bar',
14131        innerHTML: '<span class="vjs-control-text"><span>' + this.localize('Progress') + '</span>: 0%</span>'
14132      });
14133    };
14134
14135    /**
14136     * Enqueues updates to its own DOM as well as the DOM of its
14137     * {@link TimeTooltip} child.
14138     *
14139     * @param {Object} seekBarRect
14140     *        The `ClientRect` for the {@link SeekBar} element.
14141     *
14142     * @param {number} seekBarPoint
14143     *        A number from 0 to 1, representing a horizontal reference point
14144     *        from the left edge of the {@link SeekBar}
14145     */
14146
14147
14148    PlayProgressBar.prototype.update = function update(seekBarRect, seekBarPoint) {
14149      var _this2 = this;
14150
14151      // If there is an existing rAF ID, cancel it so we don't over-queue.
14152      if (this.rafId_) {
14153        this.cancelAnimationFrame(this.rafId_);
14154      }
14155
14156      this.rafId_ = this.requestAnimationFrame(function () {
14157        var time = _this2.player_.scrubbing() ? _this2.player_.getCache().currentTime : _this2.player_.currentTime();
14158
14159        var content = formatTime(time, _this2.player_.duration());
14160        var timeTooltip = _this2.getChild('timeTooltip');
14161
14162        if (timeTooltip) {
14163          timeTooltip.update(seekBarRect, seekBarPoint, content);
14164        }
14165      });
14166    };
14167
14168    return PlayProgressBar;
14169  }(Component);
14170
14171  /**
14172   * Default options for {@link PlayProgressBar}.
14173   *
14174   * @type {Object}
14175   * @private
14176   */
14177
14178
14179  PlayProgressBar.prototype.options_ = {
14180    children: []
14181  };
14182
14183  // Time tooltips should not be added to a player on mobile devices
14184  if (!IS_IOS && !IS_ANDROID) {
14185    PlayProgressBar.prototype.options_.children.push('timeTooltip');
14186  }
14187
14188  Component.registerComponent('PlayProgressBar', PlayProgressBar);
14189
14190  /**
14191   * @file mouse-time-display.js
14192   */
14193
14194  /**
14195   * The {@link MouseTimeDisplay} component tracks mouse movement over the
14196   * {@link ProgressControl}. It displays an indicator and a {@link TimeTooltip}
14197   * indicating the time which is represented by a given point in the
14198   * {@link ProgressControl}.
14199   *
14200   * @extends Component
14201   */
14202
14203  var MouseTimeDisplay = function (_Component) {
14204    inherits(MouseTimeDisplay, _Component);
14205
14206    /**
14207     * Creates an instance of this class.
14208     *
14209     * @param {Player} player
14210     *        The {@link Player} that this class should be attached to.
14211     *
14212     * @param {Object} [options]
14213     *        The key/value store of player options.
14214     */
14215    function MouseTimeDisplay(player, options) {
14216      classCallCheck(this, MouseTimeDisplay);
14217
14218      var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
14219
14220      _this.update = throttle(bind(_this, _this.update), 25);
14221      return _this;
14222    }
14223
14224    /**
14225     * Create the DOM element for this class.
14226     *
14227     * @return {Element}
14228     *         The element that was created.
14229     */
14230
14231
14232    MouseTimeDisplay.prototype.createEl = function createEl() {
14233      return _Component.prototype.createEl.call(this, 'div', {
14234        className: 'vjs-mouse-display'
14235      });
14236    };
14237
14238    /**
14239     * Enqueues updates to its own DOM as well as the DOM of its
14240     * {@link TimeTooltip} child.
14241     *
14242     * @param {Object} seekBarRect
14243     *        The `ClientRect` for the {@link SeekBar} element.
14244     *
14245     * @param {number} seekBarPoint
14246     *        A number from 0 to 1, representing a horizontal reference point
14247     *        from the left edge of the {@link SeekBar}
14248     */
14249
14250
14251    MouseTimeDisplay.prototype.update = function update(seekBarRect, seekBarPoint) {
14252      var _this2 = this;
14253
14254      // If there is an existing rAF ID, cancel it so we don't over-queue.
14255      if (this.rafId_) {
14256        this.cancelAnimationFrame(this.rafId_);
14257      }
14258
14259      this.rafId_ = this.requestAnimationFrame(function () {
14260        var duration = _this2.player_.duration();
14261        var content = formatTime(seekBarPoint * duration, duration);
14262
14263        _this2.el_.style.left = seekBarRect.width * seekBarPoint + 'px';
14264        _this2.getChild('timeTooltip').update(seekBarRect, seekBarPoint, content);
14265      });
14266    };
14267
14268    return MouseTimeDisplay;
14269  }(Component);
14270
14271  /**
14272   * Default options for `MouseTimeDisplay`
14273   *
14274   * @type {Object}
14275   * @private
14276   */
14277
14278
14279  MouseTimeDisplay.prototype.options_ = {
14280    children: ['timeTooltip']
14281  };
14282
14283  Component.registerComponent('MouseTimeDisplay', MouseTimeDisplay);
14284
14285  /**
14286   * @file seek-bar.js
14287   */
14288
14289  // The number of seconds the `step*` functions move the timeline.
14290  var STEP_SECONDS = 5;
14291
14292  // The interval at which the bar should update as it progresses.
14293  var UPDATE_REFRESH_INTERVAL = 30;
14294
14295  /**
14296   * Seek bar and container for the progress bars. Uses {@link PlayProgressBar}
14297   * as its `bar`.
14298   *
14299   * @extends Slider
14300   */
14301
14302  var SeekBar = function (_Slider) {
14303    inherits(SeekBar, _Slider);
14304
14305    /**
14306     * Creates an instance of this class.
14307     *
14308     * @param {Player} player
14309     *        The `Player` that this class should be attached to.
14310     *
14311     * @param {Object} [options]
14312     *        The key/value store of player options.
14313     */
14314    function SeekBar(player, options) {
14315      classCallCheck(this, SeekBar);
14316
14317      var _this = possibleConstructorReturn(this, _Slider.call(this, player, options));
14318
14319      _this.setEventHandlers_();
14320      return _this;
14321    }
14322
14323    /**
14324     * Sets the event handlers
14325     *
14326     * @private
14327     */
14328
14329
14330    SeekBar.prototype.setEventHandlers_ = function setEventHandlers_() {
14331      var _this2 = this;
14332
14333      this.update = throttle(bind(this, this.update), UPDATE_REFRESH_INTERVAL);
14334
14335      this.on(this.player_, 'timeupdate', this.update);
14336      this.on(this.player_, 'ended', this.handleEnded);
14337
14338      // when playing, let's ensure we smoothly update the play progress bar
14339      // via an interval
14340      this.updateInterval = null;
14341
14342      this.on(this.player_, ['playing'], function () {
14343        _this2.clearInterval(_this2.updateInterval);
14344
14345        _this2.updateInterval = _this2.setInterval(function () {
14346          _this2.requestAnimationFrame(function () {
14347            _this2.update();
14348          });
14349        }, UPDATE_REFRESH_INTERVAL);
14350      });
14351
14352      this.on(this.player_, ['ended', 'pause', 'waiting'], function () {
14353        _this2.clearInterval(_this2.updateInterval);
14354      });
14355
14356      this.on(this.player_, ['timeupdate', 'ended'], this.update);
14357    };
14358
14359    /**
14360     * Create the `Component`'s DOM element
14361     *
14362     * @return {Element}
14363     *         The element that was created.
14364     */
14365
14366
14367    SeekBar.prototype.createEl = function createEl$$1() {
14368      return _Slider.prototype.createEl.call(this, 'div', {
14369        className: 'vjs-progress-holder'
14370      }, {
14371        'aria-label': this.localize('Progress Bar')
14372      });
14373    };
14374
14375    /**
14376     * This function updates the play progress bar and accessibility
14377     * attributes to whatever is passed in.
14378     *
14379     * @param {number} currentTime
14380     *        The currentTime value that should be used for accessibility
14381     *
14382     * @param {number} percent
14383     *        The percentage as a decimal that the bar should be filled from 0-1.
14384     *
14385     * @private
14386     */
14387
14388
14389    SeekBar.prototype.update_ = function update_(currentTime, percent) {
14390      var duration = this.player_.duration();
14391
14392      // machine readable value of progress bar (percentage complete)
14393      this.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2));
14394
14395      // human readable value of progress bar (time complete)
14396      this.el_.setAttribute('aria-valuetext', this.localize('progress bar timing: currentTime={1} duration={2}', [formatTime(currentTime, duration), formatTime(duration, duration)], '{1} of {2}'));
14397
14398      // Update the `PlayProgressBar`.
14399      this.bar.update(getBoundingClientRect(this.el_), percent);
14400    };
14401
14402    /**
14403     * Update the seek bar's UI.
14404     *
14405     * @param {EventTarget~Event} [event]
14406     *        The `timeupdate` or `ended` event that caused this to run.
14407     *
14408     * @listens Player#timeupdate
14409     *
14410     * @returns {number}
14411     *          The current percent at a number from 0-1
14412     */
14413
14414
14415    SeekBar.prototype.update = function update(event) {
14416      var percent = _Slider.prototype.update.call(this);
14417
14418      this.update_(this.getCurrentTime_(), percent);
14419      return percent;
14420    };
14421
14422    /**
14423     * Get the value of current time but allows for smooth scrubbing,
14424     * when player can't keep up.
14425     *
14426     * @return {number}
14427     *         The current time value to display
14428     *
14429     * @private
14430     */
14431
14432
14433    SeekBar.prototype.getCurrentTime_ = function getCurrentTime_() {
14434      return this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
14435    };
14436
14437    /**
14438     * We want the seek bar to be full on ended
14439     * no matter what the actual internal values are. so we force it.
14440     *
14441     * @param {EventTarget~Event} [event]
14442     *        The `timeupdate` or `ended` event that caused this to run.
14443     *
14444     * @listens Player#ended
14445     */
14446
14447
14448    SeekBar.prototype.handleEnded = function handleEnded(event) {
14449      this.update_(this.player_.duration(), 1);
14450    };
14451
14452    /**
14453     * Get the percentage of media played so far.
14454     *
14455     * @return {number}
14456     *         The percentage of media played so far (0 to 1).
14457     */
14458
14459
14460    SeekBar.prototype.getPercent = function getPercent() {
14461      var percent = this.getCurrentTime_() / this.player_.duration();
14462
14463      return percent >= 1 ? 1 : percent || 0;
14464    };
14465
14466    /**
14467     * Handle mouse down on seek bar
14468     *
14469     * @param {EventTarget~Event} event
14470     *        The `mousedown` event that caused this to run.
14471     *
14472     * @listens mousedown
14473     */
14474
14475
14476    SeekBar.prototype.handleMouseDown = function handleMouseDown(event) {
14477      if (!isSingleLeftClick(event)) {
14478        return;
14479      }
14480
14481      // Stop event propagation to prevent double fire in progress-control.js
14482      event.stopPropagation();
14483      this.player_.scrubbing(true);
14484
14485      this.videoWasPlaying = !this.player_.paused();
14486      this.player_.pause();
14487
14488      _Slider.prototype.handleMouseDown.call(this, event);
14489    };
14490
14491    /**
14492     * Handle mouse move on seek bar
14493     *
14494     * @param {EventTarget~Event} event
14495     *        The `mousemove` event that caused this to run.
14496     *
14497     * @listens mousemove
14498     */
14499
14500
14501    SeekBar.prototype.handleMouseMove = function handleMouseMove(event) {
14502      if (!isSingleLeftClick(event)) {
14503        return;
14504      }
14505
14506      var newTime = this.calculateDistance(event) * this.player_.duration();
14507
14508      // Don't let video end while scrubbing.
14509      if (newTime === this.player_.duration()) {
14510        newTime = newTime - 0.1;
14511      }
14512
14513      // Set new time (tell player to seek to new time)
14514      this.player_.currentTime(newTime);
14515    };
14516
14517    SeekBar.prototype.enable = function enable() {
14518      _Slider.prototype.enable.call(this);
14519      var mouseTimeDisplay = this.getChild('mouseTimeDisplay');
14520
14521      if (!mouseTimeDisplay) {
14522        return;
14523      }
14524
14525      mouseTimeDisplay.show();
14526    };
14527
14528    SeekBar.prototype.disable = function disable() {
14529      _Slider.prototype.disable.call(this);
14530      var mouseTimeDisplay = this.getChild('mouseTimeDisplay');
14531
14532      if (!mouseTimeDisplay) {
14533        return;
14534      }
14535
14536      mouseTimeDisplay.hide();
14537    };
14538
14539    /**
14540     * Handle mouse up on seek bar
14541     *
14542     * @param {EventTarget~Event} event
14543     *        The `mouseup` event that caused this to run.
14544     *
14545     * @listens mouseup
14546     */
14547
14548
14549    SeekBar.prototype.handleMouseUp = function handleMouseUp(event) {
14550      _Slider.prototype.handleMouseUp.call(this, event);
14551
14552      // Stop event propagation to prevent double fire in progress-control.js
14553      if (event) {
14554        event.stopPropagation();
14555      }
14556      this.player_.scrubbing(false);
14557
14558      /**
14559       * Trigger timeupdate because we're done seeking and the time has changed.
14560       * This is particularly useful for if the player is paused to time the time displays.
14561       *
14562       * @event Tech#timeupdate
14563       * @type {EventTarget~Event}
14564       */
14565      this.player_.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
14566      if (this.videoWasPlaying) {
14567        silencePromise(this.player_.play());
14568      }
14569    };
14570
14571    /**
14572     * Move more quickly fast forward for keyboard-only users
14573     */
14574
14575
14576    SeekBar.prototype.stepForward = function stepForward() {
14577      this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS);
14578    };
14579
14580    /**
14581     * Move more quickly rewind for keyboard-only users
14582     */
14583
14584
14585    SeekBar.prototype.stepBack = function stepBack() {
14586      this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS);
14587    };
14588
14589    /**
14590     * Toggles the playback state of the player
14591     * This gets called when enter or space is used on the seekbar
14592     *
14593     * @param {EventTarget~Event} event
14594     *        The `keydown` event that caused this function to be called
14595     *
14596     */
14597
14598
14599    SeekBar.prototype.handleAction = function handleAction(event) {
14600      if (this.player_.paused()) {
14601        this.player_.play();
14602      } else {
14603        this.player_.pause();
14604      }
14605    };
14606
14607    /**
14608     * Called when this SeekBar has focus and a key gets pressed down. By
14609     * default it will call `this.handleAction` when the key is space or enter.
14610     *
14611     * @param {EventTarget~Event} event
14612     *        The `keydown` event that caused this function to be called.
14613     *
14614     * @listens keydown
14615     */
14616
14617
14618    SeekBar.prototype.handleKeyPress = function handleKeyPress(event) {
14619
14620      // Support Space (32) or Enter (13) key operation to fire a click event
14621      if (event.which === 32 || event.which === 13) {
14622        event.preventDefault();
14623        this.handleAction(event);
14624      } else if (_Slider.prototype.handleKeyPress) {
14625
14626        // Pass keypress handling up for unsupported keys
14627        _Slider.prototype.handleKeyPress.call(this, event);
14628      }
14629    };
14630
14631    return SeekBar;
14632  }(Slider);
14633
14634  /**
14635   * Default options for the `SeekBar`
14636   *
14637   * @type {Object}
14638   * @private
14639   */
14640
14641
14642  SeekBar.prototype.options_ = {
14643    children: ['loadProgressBar', 'playProgressBar'],
14644    barName: 'playProgressBar'
14645  };
14646
14647  // MouseTimeDisplay tooltips should not be added to a player on mobile devices
14648  if (!IS_IOS && !IS_ANDROID) {
14649    SeekBar.prototype.options_.children.splice(1, 0, 'mouseTimeDisplay');
14650  }
14651
14652  /**
14653   * Call the update event for this Slider when this event happens on the player.
14654   *
14655   * @type {string}
14656   */
14657  SeekBar.prototype.playerEvent = 'timeupdate';
14658
14659  Component.registerComponent('SeekBar', SeekBar);
14660
14661  /**
14662   * @file progress-control.js
14663   */
14664
14665  /**
14666   * The Progress Control component contains the seek bar, load progress,
14667   * and play progress.
14668   *
14669   * @extends Component
14670   */
14671
14672  var ProgressControl = function (_Component) {
14673    inherits(ProgressControl, _Component);
14674
14675    /**
14676     * Creates an instance of this class.
14677     *
14678     * @param {Player} player
14679     *        The `Player` that this class should be attached to.
14680     *
14681     * @param {Object} [options]
14682     *        The key/value store of player options.
14683     */
14684    function ProgressControl(player, options) {
14685      classCallCheck(this, ProgressControl);
14686
14687      var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
14688
14689      _this.handleMouseMove = throttle(bind(_this, _this.handleMouseMove), 25);
14690      _this.throttledHandleMouseSeek = throttle(bind(_this, _this.handleMouseSeek), 25);
14691
14692      _this.enable();
14693      return _this;
14694    }
14695
14696    /**
14697     * Create the `Component`'s DOM element
14698     *
14699     * @return {Element}
14700     *         The element that was created.
14701     */
14702
14703
14704    ProgressControl.prototype.createEl = function createEl$$1() {
14705      return _Component.prototype.createEl.call(this, 'div', {
14706        className: 'vjs-progress-control vjs-control'
14707      });
14708    };
14709
14710    /**
14711     * When the mouse moves over the `ProgressControl`, the pointer position
14712     * gets passed down to the `MouseTimeDisplay` component.
14713     *
14714     * @param {EventTarget~Event} event
14715     *        The `mousemove` event that caused this function to run.
14716     *
14717     * @listen mousemove
14718     */
14719
14720
14721    ProgressControl.prototype.handleMouseMove = function handleMouseMove(event) {
14722      var seekBar = this.getChild('seekBar');
14723
14724      if (seekBar) {
14725        var mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay');
14726        var seekBarEl = seekBar.el();
14727        var seekBarRect = getBoundingClientRect(seekBarEl);
14728        var seekBarPoint = getPointerPosition(seekBarEl, event).x;
14729
14730        // The default skin has a gap on either side of the `SeekBar`. This means
14731        // that it's possible to trigger this behavior outside the boundaries of
14732        // the `SeekBar`. This ensures we stay within it at all times.
14733        if (seekBarPoint > 1) {
14734          seekBarPoint = 1;
14735        } else if (seekBarPoint < 0) {
14736          seekBarPoint = 0;
14737        }
14738
14739        if (mouseTimeDisplay) {
14740          mouseTimeDisplay.update(seekBarRect, seekBarPoint);
14741        }
14742      }
14743    };
14744
14745    /**
14746     * A throttled version of the {@link ProgressControl#handleMouseSeek} listener.
14747     *
14748     * @method ProgressControl#throttledHandleMouseSeek
14749     * @param {EventTarget~Event} event
14750     *        The `mousemove` event that caused this function to run.
14751     *
14752     * @listen mousemove
14753     * @listen touchmove
14754     */
14755
14756    /**
14757     * Handle `mousemove` or `touchmove` events on the `ProgressControl`.
14758     *
14759     * @param {EventTarget~Event} event
14760     *        `mousedown` or `touchstart` event that triggered this function
14761     *
14762     * @listens mousemove
14763     * @listens touchmove
14764     */
14765
14766
14767    ProgressControl.prototype.handleMouseSeek = function handleMouseSeek(event) {
14768      var seekBar = this.getChild('seekBar');
14769
14770      if (seekBar) {
14771        seekBar.handleMouseMove(event);
14772      }
14773    };
14774
14775    /**
14776     * Are controls are currently enabled for this progress control.
14777     *
14778     * @return {boolean}
14779     *         true if controls are enabled, false otherwise
14780     */
14781
14782
14783    ProgressControl.prototype.enabled = function enabled() {
14784      return this.enabled_;
14785    };
14786
14787    /**
14788     * Disable all controls on the progress control and its children
14789     */
14790
14791
14792    ProgressControl.prototype.disable = function disable() {
14793      this.children().forEach(function (child) {
14794        return child.disable && child.disable();
14795      });
14796
14797      if (!this.enabled()) {
14798        return;
14799      }
14800
14801      this.off(['mousedown', 'touchstart'], this.handleMouseDown);
14802      this.off(this.el_, 'mousemove', this.handleMouseMove);
14803      this.handleMouseUp();
14804
14805      this.addClass('disabled');
14806
14807      this.enabled_ = false;
14808    };
14809
14810    /**
14811     * Enable all controls on the progress control and its children
14812     */
14813
14814
14815    ProgressControl.prototype.enable = function enable() {
14816      this.children().forEach(function (child) {
14817        return child.enable && child.enable();
14818      });
14819
14820      if (this.enabled()) {
14821        return;
14822      }
14823
14824      this.on(['mousedown', 'touchstart'], this.handleMouseDown);
14825      this.on(this.el_, 'mousemove', this.handleMouseMove);
14826      this.removeClass('disabled');
14827
14828      this.enabled_ = true;
14829    };
14830
14831    /**
14832     * Handle `mousedown` or `touchstart` events on the `ProgressControl`.
14833     *
14834     * @param {EventTarget~Event} event
14835     *        `mousedown` or `touchstart` event that triggered this function
14836     *
14837     * @listens mousedown
14838     * @listens touchstart
14839     */
14840
14841
14842    ProgressControl.prototype.handleMouseDown = function handleMouseDown(event) {
14843      var doc = this.el_.ownerDocument;
14844      var seekBar = this.getChild('seekBar');
14845
14846      if (seekBar) {
14847        seekBar.handleMouseDown(event);
14848      }
14849
14850      this.on(doc, 'mousemove', this.throttledHandleMouseSeek);
14851      this.on(doc, 'touchmove', this.throttledHandleMouseSeek);
14852      this.on(doc, 'mouseup', this.handleMouseUp);
14853      this.on(doc, 'touchend', this.handleMouseUp);
14854    };
14855
14856    /**
14857     * Handle `mouseup` or `touchend` events on the `ProgressControl`.
14858     *
14859     * @param {EventTarget~Event} event
14860     *        `mouseup` or `touchend` event that triggered this function.
14861     *
14862     * @listens touchend
14863     * @listens mouseup
14864     */
14865
14866
14867    ProgressControl.prototype.handleMouseUp = function handleMouseUp(event) {
14868      var doc = this.el_.ownerDocument;
14869      var seekBar = this.getChild('seekBar');
14870
14871      if (seekBar) {
14872        seekBar.handleMouseUp(event);
14873      }
14874
14875      this.off(doc, 'mousemove', this.throttledHandleMouseSeek);
14876      this.off(doc, 'touchmove', this.throttledHandleMouseSeek);
14877      this.off(doc, 'mouseup', this.handleMouseUp);
14878      this.off(doc, 'touchend', this.handleMouseUp);
14879    };
14880
14881    return ProgressControl;
14882  }(Component);
14883
14884  /**
14885   * Default options for `ProgressControl`
14886   *
14887   * @type {Object}
14888   * @private
14889   */
14890
14891
14892  ProgressControl.prototype.options_ = {
14893    children: ['seekBar']
14894  };
14895
14896  Component.registerComponent('ProgressControl', ProgressControl);
14897
14898  /**
14899   * @file fullscreen-toggle.js
14900   */
14901
14902  /**
14903   * Toggle fullscreen video
14904   *
14905   * @extends Button
14906   */
14907
14908  var FullscreenToggle = function (_Button) {
14909    inherits(FullscreenToggle, _Button);
14910
14911    /**
14912     * Creates an instance of this class.
14913     *
14914     * @param {Player} player
14915     *        The `Player` that this class should be attached to.
14916     *
14917     * @param {Object} [options]
14918     *        The key/value store of player options.
14919     */
14920    function FullscreenToggle(player, options) {
14921      classCallCheck(this, FullscreenToggle);
14922
14923      var _this = possibleConstructorReturn(this, _Button.call(this, player, options));
14924
14925      _this.on(player, 'fullscreenchange', _this.handleFullscreenChange);
14926      return _this;
14927    }
14928
14929    /**
14930     * Builds the default DOM `className`.
14931     *
14932     * @return {string}
14933     *         The DOM `className` for this object.
14934     */
14935
14936
14937    FullscreenToggle.prototype.buildCSSClass = function buildCSSClass() {
14938      return 'vjs-fullscreen-control ' + _Button.prototype.buildCSSClass.call(this);
14939    };
14940
14941    /**
14942     * Handles fullscreenchange on the player and change control text accordingly.
14943     *
14944     * @param {EventTarget~Event} [event]
14945     *        The {@link Player#fullscreenchange} event that caused this function to be
14946     *        called.
14947     *
14948     * @listens Player#fullscreenchange
14949     */
14950
14951
14952    FullscreenToggle.prototype.handleFullscreenChange = function handleFullscreenChange(event) {
14953      if (this.player_.isFullscreen()) {
14954        this.controlText('Non-Fullscreen');
14955      } else {
14956        this.controlText('Fullscreen');
14957      }
14958    };
14959
14960    /**
14961     * This gets called when an `FullscreenToggle` is "clicked". See
14962     * {@link ClickableComponent} for more detailed information on what a click can be.
14963     *
14964     * @param {EventTarget~Event} [event]
14965     *        The `keydown`, `tap`, or `click` event that caused this function to be
14966     *        called.
14967     *
14968     * @listens tap
14969     * @listens click
14970     */
14971
14972
14973    FullscreenToggle.prototype.handleClick = function handleClick(event) {
14974      if (!this.player_.isFullscreen()) {
14975        this.player_.requestFullscreen();
14976      } else {
14977        this.player_.exitFullscreen();
14978      }
14979    };
14980
14981    return FullscreenToggle;
14982  }(Button);
14983
14984  /**
14985   * The text that should display over the `FullscreenToggle`s controls. Added for localization.
14986   *
14987   * @type {string}
14988   * @private
14989   */
14990
14991
14992  FullscreenToggle.prototype.controlText_ = 'Fullscreen';
14993
14994  Component.registerComponent('FullscreenToggle', FullscreenToggle);
14995
14996  /**
14997   * Check if volume control is supported and if it isn't hide the
14998   * `Component` that was passed  using the `vjs-hidden` class.
14999   *
15000   * @param {Component} self
15001   *        The component that should be hidden if volume is unsupported
15002   *
15003   * @param {Player} player
15004   *        A reference to the player
15005   *
15006   * @private
15007   */
15008  var checkVolumeSupport = function checkVolumeSupport(self, player) {
15009    // hide volume controls when they're not supported by the current tech
15010    if (player.tech_ && !player.tech_.featuresVolumeControl) {
15011      self.addClass('vjs-hidden');
15012    }
15013
15014    self.on(player, 'loadstart', function () {
15015      if (!player.tech_.featuresVolumeControl) {
15016        self.addClass('vjs-hidden');
15017      } else {
15018        self.removeClass('vjs-hidden');
15019      }
15020    });
15021  };
15022
15023  /**
15024   * @file volume-level.js
15025   */
15026
15027  /**
15028   * Shows volume level
15029   *
15030   * @extends Component
15031   */
15032
15033  var VolumeLevel = function (_Component) {
15034    inherits(VolumeLevel, _Component);
15035
15036    function VolumeLevel() {
15037      classCallCheck(this, VolumeLevel);
15038      return possibleConstructorReturn(this, _Component.apply(this, arguments));
15039    }
15040
15041    /**
15042     * Create the `Component`'s DOM element
15043     *
15044     * @return {Element}
15045     *         The element that was created.
15046     */
15047    VolumeLevel.prototype.createEl = function createEl() {
15048      return _Component.prototype.createEl.call(this, 'div', {
15049        className: 'vjs-volume-level',
15050        innerHTML: '<span class="vjs-control-text"></span>'
15051      });
15052    };
15053
15054    return VolumeLevel;
15055  }(Component);
15056
15057  Component.registerComponent('VolumeLevel', VolumeLevel);
15058
15059  /**
15060   * @file volume-bar.js
15061   */
15062
15063  /**
15064   * The bar that contains the volume level and can be clicked on to adjust the level
15065   *
15066   * @extends Slider
15067   */
15068
15069  var VolumeBar = function (_Slider) {
15070    inherits(VolumeBar, _Slider);
15071
15072    /**
15073     * Creates an instance of this class.
15074     *
15075     * @param {Player} player
15076     *        The `Player` that this class should be attached to.
15077     *
15078     * @param {Object} [options]
15079     *        The key/value store of player options.
15080     */
15081    function VolumeBar(player, options) {
15082      classCallCheck(this, VolumeBar);
15083
15084      var _this = possibleConstructorReturn(this, _Slider.call(this, player, options));
15085
15086      _this.on('slideractive', _this.updateLastVolume_);
15087      _this.on(player, 'volumechange', _this.updateARIAAttributes);
15088      player.ready(function () {
15089        return _this.updateARIAAttributes();
15090      });
15091      return _this;
15092    }
15093
15094    /**
15095     * Create the `Component`'s DOM element
15096     *
15097     * @return {Element}
15098     *         The element that was created.
15099     */
15100
15101
15102    VolumeBar.prototype.createEl = function createEl$$1() {
15103      return _Slider.prototype.createEl.call(this, 'div', {
15104        className: 'vjs-volume-bar vjs-slider-bar'
15105      }, {
15106        'aria-label': this.localize('Volume Level'),
15107        'aria-live': 'polite'
15108      });
15109    };
15110
15111    /**
15112     * Handle mouse down on volume bar
15113     *
15114     * @param {EventTarget~Event} event
15115     *        The `mousedown` event that caused this to run.
15116     *
15117     * @listens mousedown
15118     */
15119
15120
15121    VolumeBar.prototype.handleMouseDown = function handleMouseDown(event) {
15122      if (!isSingleLeftClick(event)) {
15123        return;
15124      }
15125
15126      _Slider.prototype.handleMouseDown.call(this, event);
15127    };
15128
15129    /**
15130     * Handle movement events on the {@link VolumeMenuButton}.
15131     *
15132     * @param {EventTarget~Event} event
15133     *        The event that caused this function to run.
15134     *
15135     * @listens mousemove
15136     */
15137
15138
15139    VolumeBar.prototype.handleMouseMove = function handleMouseMove(event) {
15140      if (!isSingleLeftClick(event)) {
15141        return;
15142      }
15143
15144      this.checkMuted();
15145      this.player_.volume(this.calculateDistance(event));
15146    };
15147
15148    /**
15149     * If the player is muted unmute it.
15150     */
15151
15152
15153    VolumeBar.prototype.checkMuted = function checkMuted() {
15154      if (this.player_.muted()) {
15155        this.player_.muted(false);
15156      }
15157    };
15158
15159    /**
15160     * Get percent of volume level
15161     *
15162     * @return {number}
15163     *         Volume level percent as a decimal number.
15164     */
15165
15166
15167    VolumeBar.prototype.getPercent = function getPercent() {
15168      if (this.player_.muted()) {
15169        return 0;
15170      }
15171      return this.player_.volume();
15172    };
15173
15174    /**
15175     * Increase volume level for keyboard users
15176     */
15177
15178
15179    VolumeBar.prototype.stepForward = function stepForward() {
15180      this.checkMuted();
15181      this.player_.volume(this.player_.volume() + 0.1);
15182    };
15183
15184    /**
15185     * Decrease volume level for keyboard users
15186     */
15187
15188
15189    VolumeBar.prototype.stepBack = function stepBack() {
15190      this.checkMuted();
15191      this.player_.volume(this.player_.volume() - 0.1);
15192    };
15193
15194    /**
15195     * Update ARIA accessibility attributes
15196     *
15197     * @param {EventTarget~Event} [event]
15198     *        The `volumechange` event that caused this function to run.
15199     *
15200     * @listens Player#volumechange
15201     */
15202
15203
15204    VolumeBar.prototype.updateARIAAttributes = function updateARIAAttributes(event) {
15205      var ariaValue = this.player_.muted() ? 0 : this.volumeAsPercentage_();
15206
15207      this.el_.setAttribute('aria-valuenow', ariaValue);
15208      this.el_.setAttribute('aria-valuetext', ariaValue + '%');
15209    };
15210
15211    /**
15212     * Returns the current value of the player volume as a percentage
15213     *
15214     * @private
15215     */
15216
15217
15218    VolumeBar.prototype.volumeAsPercentage_ = function volumeAsPercentage_() {
15219      return Math.round(this.player_.volume() * 100);
15220    };
15221
15222    /**
15223     * When user starts dragging the VolumeBar, store the volume and listen for
15224     * the end of the drag. When the drag ends, if the volume was set to zero,
15225     * set lastVolume to the stored volume.
15226     *
15227     * @listens slideractive
15228     * @private
15229     */
15230
15231
15232    VolumeBar.prototype.updateLastVolume_ = function updateLastVolume_() {
15233      var _this2 = this;
15234
15235      var volumeBeforeDrag = this.player_.volume();
15236
15237      this.one('sliderinactive', function () {
15238        if (_this2.player_.volume() === 0) {
15239          _this2.player_.lastVolume_(volumeBeforeDrag);
15240        }
15241      });
15242    };
15243
15244    return VolumeBar;
15245  }(Slider);
15246
15247  /**
15248   * Default options for the `VolumeBar`
15249   *
15250   * @type {Object}
15251   * @private
15252   */
15253
15254
15255  VolumeBar.prototype.options_ = {
15256    children: ['volumeLevel'],
15257    barName: 'volumeLevel'
15258  };
15259
15260  /**
15261   * Call the update event for this Slider when this event happens on the player.
15262   *
15263   * @type {string}
15264   */
15265  VolumeBar.prototype.playerEvent = 'volumechange';
15266
15267  Component.registerComponent('VolumeBar', VolumeBar);
15268
15269  /**
15270   * @file volume-control.js
15271   */
15272
15273  /**
15274   * The component for controlling the volume level
15275   *
15276   * @extends Component
15277   */
15278
15279  var VolumeControl = function (_Component) {
15280    inherits(VolumeControl, _Component);
15281
15282    /**
15283     * Creates an instance of this class.
15284     *
15285     * @param {Player} player
15286     *        The `Player` that this class should be attached to.
15287     *
15288     * @param {Object} [options={}]
15289     *        The key/value store of player options.
15290     */
15291    function VolumeControl(player) {
15292      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
15293      classCallCheck(this, VolumeControl);
15294
15295      options.vertical = options.vertical || false;
15296
15297      // Pass the vertical option down to the VolumeBar if
15298      // the VolumeBar is turned on.
15299      if (typeof options.volumeBar === 'undefined' || isPlain(options.volumeBar)) {
15300        options.volumeBar = options.volumeBar || {};
15301        options.volumeBar.vertical = options.vertical;
15302      }
15303
15304      // hide this control if volume support is missing
15305      var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
15306
15307      checkVolumeSupport(_this, player);
15308
15309      _this.throttledHandleMouseMove = throttle(bind(_this, _this.handleMouseMove), 25);
15310
15311      _this.on('mousedown', _this.handleMouseDown);
15312      _this.on('touchstart', _this.handleMouseDown);
15313
15314      // while the slider is active (the mouse has been pressed down and
15315      // is dragging) or in focus we do not want to hide the VolumeBar
15316      _this.on(_this.volumeBar, ['focus', 'slideractive'], function () {
15317        _this.volumeBar.addClass('vjs-slider-active');
15318        _this.addClass('vjs-slider-active');
15319        _this.trigger('slideractive');
15320      });
15321
15322      _this.on(_this.volumeBar, ['blur', 'sliderinactive'], function () {
15323        _this.volumeBar.removeClass('vjs-slider-active');
15324        _this.removeClass('vjs-slider-active');
15325        _this.trigger('sliderinactive');
15326      });
15327      return _this;
15328    }
15329
15330    /**
15331     * Create the `Component`'s DOM element
15332     *
15333     * @return {Element}
15334     *         The element that was created.
15335     */
15336
15337
15338    VolumeControl.prototype.createEl = function createEl() {
15339      var orientationClass = 'vjs-volume-horizontal';
15340
15341      if (this.options_.vertical) {
15342        orientationClass = 'vjs-volume-vertical';
15343      }
15344
15345      return _Component.prototype.createEl.call(this, 'div', {
15346        className: 'vjs-volume-control vjs-control ' + orientationClass
15347      });
15348    };
15349
15350    /**
15351     * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
15352     *
15353     * @param {EventTarget~Event} event
15354     *        `mousedown` or `touchstart` event that triggered this function
15355     *
15356     * @listens mousedown
15357     * @listens touchstart
15358     */
15359
15360
15361    VolumeControl.prototype.handleMouseDown = function handleMouseDown(event) {
15362      var doc = this.el_.ownerDocument;
15363
15364      this.on(doc, 'mousemove', this.throttledHandleMouseMove);
15365      this.on(doc, 'touchmove', this.throttledHandleMouseMove);
15366      this.on(doc, 'mouseup', this.handleMouseUp);
15367      this.on(doc, 'touchend', this.handleMouseUp);
15368    };
15369
15370    /**
15371     * Handle `mouseup` or `touchend` events on the `VolumeControl`.
15372     *
15373     * @param {EventTarget~Event} event
15374     *        `mouseup` or `touchend` event that triggered this function.
15375     *
15376     * @listens touchend
15377     * @listens mouseup
15378     */
15379
15380
15381    VolumeControl.prototype.handleMouseUp = function handleMouseUp(event) {
15382      var doc = this.el_.ownerDocument;
15383
15384      this.off(doc, 'mousemove', this.throttledHandleMouseMove);
15385      this.off(doc, 'touchmove', this.throttledHandleMouseMove);
15386      this.off(doc, 'mouseup', this.handleMouseUp);
15387      this.off(doc, 'touchend', this.handleMouseUp);
15388    };
15389
15390    /**
15391     * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
15392     *
15393     * @param {EventTarget~Event} event
15394     *        `mousedown` or `touchstart` event that triggered this function
15395     *
15396     * @listens mousedown
15397     * @listens touchstart
15398     */
15399
15400
15401    VolumeControl.prototype.handleMouseMove = function handleMouseMove(event) {
15402      this.volumeBar.handleMouseMove(event);
15403    };
15404
15405    return VolumeControl;
15406  }(Component);
15407
15408  /**
15409   * Default options for the `VolumeControl`
15410   *
15411   * @type {Object}
15412   * @private
15413   */
15414
15415
15416  VolumeControl.prototype.options_ = {
15417    children: ['volumeBar']
15418  };
15419
15420  Component.registerComponent('VolumeControl', VolumeControl);
15421
15422  /**
15423   * @file mute-toggle.js
15424   */
15425
15426  /**
15427   * A button component for muting the audio.
15428   *
15429   * @extends Button
15430   */
15431
15432  var MuteToggle = function (_Button) {
15433    inherits(MuteToggle, _Button);
15434
15435    /**
15436     * Creates an instance of this class.
15437     *
15438     * @param {Player} player
15439     *        The `Player` that this class should be attached to.
15440     *
15441     * @param {Object} [options]
15442     *        The key/value store of player options.
15443     */
15444    function MuteToggle(player, options) {
15445      classCallCheck(this, MuteToggle);
15446
15447      // hide this control if volume support is missing
15448      var _this = possibleConstructorReturn(this, _Button.call(this, player, options));
15449
15450      checkVolumeSupport(_this, player);
15451
15452      _this.on(player, ['loadstart', 'volumechange'], _this.update);
15453      return _this;
15454    }
15455
15456    /**
15457     * Builds the default DOM `className`.
15458     *
15459     * @return {string}
15460     *         The DOM `className` for this object.
15461     */
15462
15463
15464    MuteToggle.prototype.buildCSSClass = function buildCSSClass() {
15465      return 'vjs-mute-control ' + _Button.prototype.buildCSSClass.call(this);
15466    };
15467
15468    /**
15469     * This gets called when an `MuteToggle` is "clicked". See
15470     * {@link ClickableComponent} for more detailed information on what a click can be.
15471     *
15472     * @param {EventTarget~Event} [event]
15473     *        The `keydown`, `tap`, or `click` event that caused this function to be
15474     *        called.
15475     *
15476     * @listens tap
15477     * @listens click
15478     */
15479
15480
15481    MuteToggle.prototype.handleClick = function handleClick(event) {
15482      var vol = this.player_.volume();
15483      var lastVolume = this.player_.lastVolume_();
15484
15485      if (vol === 0) {
15486        var volumeToSet = lastVolume < 0.1 ? 0.1 : lastVolume;
15487
15488        this.player_.volume(volumeToSet);
15489        this.player_.muted(false);
15490      } else {
15491        this.player_.muted(this.player_.muted() ? false : true);
15492      }
15493    };
15494
15495    /**
15496     * Update the `MuteToggle` button based on the state of `volume` and `muted`
15497     * on the player.
15498     *
15499     * @param {EventTarget~Event} [event]
15500     *        The {@link Player#loadstart} event if this function was called
15501     *        through an event.
15502     *
15503     * @listens Player#loadstart
15504     * @listens Player#volumechange
15505     */
15506
15507
15508    MuteToggle.prototype.update = function update(event) {
15509      this.updateIcon_();
15510      this.updateControlText_();
15511    };
15512
15513    /**
15514     * Update the appearance of the `MuteToggle` icon.
15515     *
15516     * Possible states (given `level` variable below):
15517     * - 0: crossed out
15518     * - 1: zero bars of volume
15519     * - 2: one bar of volume
15520     * - 3: two bars of volume
15521     *
15522     * @private
15523     */
15524
15525
15526    MuteToggle.prototype.updateIcon_ = function updateIcon_() {
15527      var vol = this.player_.volume();
15528      var level = 3;
15529
15530      if (vol === 0 || this.player_.muted()) {
15531        level = 0;
15532      } else if (vol < 0.33) {
15533        level = 1;
15534      } else if (vol < 0.67) {
15535        level = 2;
15536      }
15537
15538      // TODO improve muted icon classes
15539      for (var i = 0; i < 4; i++) {
15540        removeClass(this.el_, 'vjs-vol-' + i);
15541      }
15542      addClass(this.el_, 'vjs-vol-' + level);
15543    };
15544
15545    /**
15546     * If `muted` has changed on the player, update the control text
15547     * (`title` attribute on `vjs-mute-control` element and content of
15548     * `vjs-control-text` element).
15549     *
15550     * @private
15551     */
15552
15553
15554    MuteToggle.prototype.updateControlText_ = function updateControlText_() {
15555      var soundOff = this.player_.muted() || this.player_.volume() === 0;
15556      var text = soundOff ? 'Unmute' : 'Mute';
15557
15558      if (this.controlText() !== text) {
15559        this.controlText(text);
15560      }
15561    };
15562
15563    return MuteToggle;
15564  }(Button);
15565
15566  /**
15567   * The text that should display over the `MuteToggle`s controls. Added for localization.
15568   *
15569   * @type {string}
15570   * @private
15571   */
15572
15573
15574  MuteToggle.prototype.controlText_ = 'Mute';
15575
15576  Component.registerComponent('MuteToggle', MuteToggle);
15577
15578  /**
15579   * @file volume-control.js
15580   */
15581
15582  /**
15583   * A Component to contain the MuteToggle and VolumeControl so that
15584   * they can work together.
15585   *
15586   * @extends Component
15587   */
15588
15589  var VolumePanel = function (_Component) {
15590    inherits(VolumePanel, _Component);
15591
15592    /**
15593     * Creates an instance of this class.
15594     *
15595     * @param {Player} player
15596     *        The `Player` that this class should be attached to.
15597     *
15598     * @param {Object} [options={}]
15599     *        The key/value store of player options.
15600     */
15601    function VolumePanel(player) {
15602      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
15603      classCallCheck(this, VolumePanel);
15604
15605      if (typeof options.inline !== 'undefined') {
15606        options.inline = options.inline;
15607      } else {
15608        options.inline = true;
15609      }
15610
15611      // pass the inline option down to the VolumeControl as vertical if
15612      // the VolumeControl is on.
15613      if (typeof options.volumeControl === 'undefined' || isPlain(options.volumeControl)) {
15614        options.volumeControl = options.volumeControl || {};
15615        options.volumeControl.vertical = !options.inline;
15616      }
15617
15618      // hide this control if volume support is missing
15619      var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
15620
15621      checkVolumeSupport(_this, player);
15622
15623      // while the slider is active (the mouse has been pressed down and
15624      // is dragging) we do not want to hide the VolumeBar
15625      _this.on(_this.volumeControl, ['slideractive'], _this.sliderActive_);
15626
15627      _this.on(_this.volumeControl, ['sliderinactive'], _this.sliderInactive_);
15628      return _this;
15629    }
15630
15631    /**
15632     * Add vjs-slider-active class to the VolumePanel
15633     *
15634     * @listens VolumeControl#slideractive
15635     * @private
15636     */
15637
15638
15639    VolumePanel.prototype.sliderActive_ = function sliderActive_() {
15640      this.addClass('vjs-slider-active');
15641    };
15642
15643    /**
15644     * Removes vjs-slider-active class to the VolumePanel
15645     *
15646     * @listens VolumeControl#sliderinactive
15647     * @private
15648     */
15649
15650
15651    VolumePanel.prototype.sliderInactive_ = function sliderInactive_() {
15652      this.removeClass('vjs-slider-active');
15653    };
15654
15655    /**
15656     * Create the `Component`'s DOM element
15657     *
15658     * @return {Element}
15659     *         The element that was created.
15660     */
15661
15662
15663    VolumePanel.prototype.createEl = function createEl() {
15664      var orientationClass = 'vjs-volume-panel-horizontal';
15665
15666      if (!this.options_.inline) {
15667        orientationClass = 'vjs-volume-panel-vertical';
15668      }
15669
15670      return _Component.prototype.createEl.call(this, 'div', {
15671        className: 'vjs-volume-panel vjs-control ' + orientationClass
15672      });
15673    };
15674
15675    return VolumePanel;
15676  }(Component);
15677
15678  /**
15679   * Default options for the `VolumeControl`
15680   *
15681   * @type {Object}
15682   * @private
15683   */
15684
15685
15686  VolumePanel.prototype.options_ = {
15687    children: ['muteToggle', 'volumeControl']
15688  };
15689
15690  Component.registerComponent('VolumePanel', VolumePanel);
15691
15692  /**
15693   * @file menu.js
15694   */
15695
15696  /**
15697   * The Menu component is used to build popup menus, including subtitle and
15698   * captions selection menus.
15699   *
15700   * @extends Component
15701   */
15702
15703  var Menu = function (_Component) {
15704    inherits(Menu, _Component);
15705
15706    /**
15707     * Create an instance of this class.
15708     *
15709     * @param {Player} player
15710     *        the player that this component should attach to
15711     *
15712     * @param {Object} [options]
15713     *        Object of option names and values
15714     *
15715     */
15716    function Menu(player, options) {
15717      classCallCheck(this, Menu);
15718
15719      var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
15720
15721      if (options) {
15722        _this.menuButton_ = options.menuButton;
15723      }
15724
15725      _this.focusedChild_ = -1;
15726
15727      _this.on('keydown', _this.handleKeyPress);
15728      return _this;
15729    }
15730
15731    /**
15732     * Add a {@link MenuItem} to the menu.
15733     *
15734     * @param {Object|string} component
15735     *        The name or instance of the `MenuItem` to add.
15736     *
15737     */
15738
15739
15740    Menu.prototype.addItem = function addItem(component) {
15741      this.addChild(component);
15742      component.on('click', bind(this, function (event) {
15743        // Unpress the associated MenuButton, and move focus back to it
15744        if (this.menuButton_) {
15745          this.menuButton_.unpressButton();
15746
15747          // don't focus menu button if item is a caption settings item
15748          // because focus will move elsewhere
15749          if (component.name() !== 'CaptionSettingsMenuItem') {
15750            this.menuButton_.focus();
15751          }
15752        }
15753      }));
15754    };
15755
15756    /**
15757     * Create the `Menu`s DOM element.
15758     *
15759     * @return {Element}
15760     *         the element that was created
15761     */
15762
15763
15764    Menu.prototype.createEl = function createEl$$1() {
15765      var contentElType = this.options_.contentElType || 'ul';
15766
15767      this.contentEl_ = createEl(contentElType, {
15768        className: 'vjs-menu-content'
15769      });
15770
15771      this.contentEl_.setAttribute('role', 'menu');
15772
15773      var el = _Component.prototype.createEl.call(this, 'div', {
15774        append: this.contentEl_,
15775        className: 'vjs-menu'
15776      });
15777
15778      el.appendChild(this.contentEl_);
15779
15780      // Prevent clicks from bubbling up. Needed for Menu Buttons,
15781      // where a click on the parent is significant
15782      on(el, 'click', function (event) {
15783        event.preventDefault();
15784        event.stopImmediatePropagation();
15785      });
15786
15787      return el;
15788    };
15789
15790    Menu.prototype.dispose = function dispose() {
15791      this.contentEl_ = null;
15792
15793      _Component.prototype.dispose.call(this);
15794    };
15795
15796    /**
15797     * Handle a `keydown` event on this menu. This listener is added in the constructor.
15798     *
15799     * @param {EventTarget~Event} event
15800     *        A `keydown` event that happened on the menu.
15801     *
15802     * @listens keydown
15803     */
15804
15805
15806    Menu.prototype.handleKeyPress = function handleKeyPress(event) {
15807      // Left and Down Arrows
15808      if (event.which === 37 || event.which === 40) {
15809        event.preventDefault();
15810        this.stepForward();
15811
15812        // Up and Right Arrows
15813      } else if (event.which === 38 || event.which === 39) {
15814        event.preventDefault();
15815        this.stepBack();
15816      }
15817    };
15818
15819    /**
15820     * Move to next (lower) menu item for keyboard users.
15821     */
15822
15823
15824    Menu.prototype.stepForward = function stepForward() {
15825      var stepChild = 0;
15826
15827      if (this.focusedChild_ !== undefined) {
15828        stepChild = this.focusedChild_ + 1;
15829      }
15830      this.focus(stepChild);
15831    };
15832
15833    /**
15834     * Move to previous (higher) menu item for keyboard users.
15835     */
15836
15837
15838    Menu.prototype.stepBack = function stepBack() {
15839      var stepChild = 0;
15840
15841      if (this.focusedChild_ !== undefined) {
15842        stepChild = this.focusedChild_ - 1;
15843      }
15844      this.focus(stepChild);
15845    };
15846
15847    /**
15848     * Set focus on a {@link MenuItem} in the `Menu`.
15849     *
15850     * @param {Object|string} [item=0]
15851     *        Index of child item set focus on.
15852     */
15853
15854
15855    Menu.prototype.focus = function focus() {
15856      var item = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
15857
15858      var children = this.children().slice();
15859      var haveTitle = children.length && children[0].className && /vjs-menu-title/.test(children[0].className);
15860
15861      if (haveTitle) {
15862        children.shift();
15863      }
15864
15865      if (children.length > 0) {
15866        if (item < 0) {
15867          item = 0;
15868        } else if (item >= children.length) {
15869          item = children.length - 1;
15870        }
15871
15872        this.focusedChild_ = item;
15873
15874        children[item].el_.focus();
15875      }
15876    };
15877
15878    return Menu;
15879  }(Component);
15880
15881  Component.registerComponent('Menu', Menu);
15882
15883  /**
15884   * @file menu-button.js
15885   */
15886
15887  /**
15888   * A `MenuButton` class for any popup {@link Menu}.
15889   *
15890   * @extends Component
15891   */
15892
15893  var MenuButton = function (_Component) {
15894    inherits(MenuButton, _Component);
15895
15896    /**
15897     * Creates an instance of this class.
15898     *
15899     * @param {Player} player
15900     *        The `Player` that this class should be attached to.
15901     *
15902     * @param {Object} [options={}]
15903     *        The key/value store of player options.
15904     */
15905    function MenuButton(player) {
15906      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
15907      classCallCheck(this, MenuButton);
15908
15909      var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
15910
15911      _this.menuButton_ = new Button(player, options);
15912
15913      _this.menuButton_.controlText(_this.controlText_);
15914      _this.menuButton_.el_.setAttribute('aria-haspopup', 'true');
15915
15916      // Add buildCSSClass values to the button, not the wrapper
15917      var buttonClass = Button.prototype.buildCSSClass();
15918
15919      _this.menuButton_.el_.className = _this.buildCSSClass() + ' ' + buttonClass;
15920      _this.menuButton_.removeClass('vjs-control');
15921
15922      _this.addChild(_this.menuButton_);
15923
15924      _this.update();
15925
15926      _this.enabled_ = true;
15927
15928      _this.on(_this.menuButton_, 'tap', _this.handleClick);
15929      _this.on(_this.menuButton_, 'click', _this.handleClick);
15930      _this.on(_this.menuButton_, 'focus', _this.handleFocus);
15931      _this.on(_this.menuButton_, 'blur', _this.handleBlur);
15932
15933      _this.on('keydown', _this.handleSubmenuKeyPress);
15934      return _this;
15935    }
15936
15937    /**
15938     * Update the menu based on the current state of its items.
15939     */
15940
15941
15942    MenuButton.prototype.update = function update() {
15943      var menu = this.createMenu();
15944
15945      if (this.menu) {
15946        this.menu.dispose();
15947        this.removeChild(this.menu);
15948      }
15949
15950      this.menu = menu;
15951      this.addChild(menu);
15952
15953      /**
15954       * Track the state of the menu button
15955       *
15956       * @type {Boolean}
15957       * @private
15958       */
15959      this.buttonPressed_ = false;
15960      this.menuButton_.el_.setAttribute('aria-expanded', 'false');
15961
15962      if (this.items && this.items.length <= this.hideThreshold_) {
15963        this.hide();
15964      } else {
15965        this.show();
15966      }
15967    };
15968
15969    /**
15970     * Create the menu and add all items to it.
15971     *
15972     * @return {Menu}
15973     *         The constructed menu
15974     */
15975
15976
15977    MenuButton.prototype.createMenu = function createMenu() {
15978      var menu = new Menu(this.player_, { menuButton: this });
15979
15980      /**
15981       * Hide the menu if the number of items is less than or equal to this threshold. This defaults
15982       * to 0 and whenever we add items which can be hidden to the menu we'll increment it. We list
15983       * it here because every time we run `createMenu` we need to reset the value.
15984       *
15985       * @protected
15986       * @type {Number}
15987       */
15988      this.hideThreshold_ = 0;
15989
15990      // Add a title list item to the top
15991      if (this.options_.title) {
15992        var title = createEl('li', {
15993          className: 'vjs-menu-title',
15994          innerHTML: toTitleCase(this.options_.title),
15995          tabIndex: -1
15996        });
15997
15998        this.hideThreshold_ += 1;
15999
16000        menu.children_.unshift(title);
16001        prependTo(title, menu.contentEl());
16002      }
16003
16004      this.items = this.createItems();
16005
16006      if (this.items) {
16007        // Add menu items to the menu
16008        for (var i = 0; i < this.items.length; i++) {
16009          menu.addItem(this.items[i]);
16010        }
16011      }
16012
16013      return menu;
16014    };
16015
16016    /**
16017     * Create the list of menu items. Specific to each subclass.
16018     *
16019     * @abstract
16020     */
16021
16022
16023    MenuButton.prototype.createItems = function createItems() {};
16024
16025    /**
16026     * Create the `MenuButtons`s DOM element.
16027     *
16028     * @return {Element}
16029     *         The element that gets created.
16030     */
16031
16032
16033    MenuButton.prototype.createEl = function createEl$$1() {
16034      return _Component.prototype.createEl.call(this, 'div', {
16035        className: this.buildWrapperCSSClass()
16036      }, {});
16037    };
16038
16039    /**
16040     * Allow sub components to stack CSS class names for the wrapper element
16041     *
16042     * @return {string}
16043     *         The constructed wrapper DOM `className`
16044     */
16045
16046
16047    MenuButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() {
16048      var menuButtonClass = 'vjs-menu-button';
16049
16050      // If the inline option is passed, we want to use different styles altogether.
16051      if (this.options_.inline === true) {
16052        menuButtonClass += '-inline';
16053      } else {
16054        menuButtonClass += '-popup';
16055      }
16056
16057      // TODO: Fix the CSS so that this isn't necessary
16058      var buttonClass = Button.prototype.buildCSSClass();
16059
16060      return 'vjs-menu-button ' + menuButtonClass + ' ' + buttonClass + ' ' + _Component.prototype.buildCSSClass.call(this);
16061    };
16062
16063    /**
16064     * Builds the default DOM `className`.
16065     *
16066     * @return {string}
16067     *         The DOM `className` for this object.
16068     */
16069
16070
16071    MenuButton.prototype.buildCSSClass = function buildCSSClass() {
16072      var menuButtonClass = 'vjs-menu-button';
16073
16074      // If the inline option is passed, we want to use different styles altogether.
16075      if (this.options_.inline === true) {
16076        menuButtonClass += '-inline';
16077      } else {
16078        menuButtonClass += '-popup';
16079      }
16080
16081      return 'vjs-menu-button ' + menuButtonClass + ' ' + _Component.prototype.buildCSSClass.call(this);
16082    };
16083
16084    /**
16085     * Get or set the localized control text that will be used for accessibility.
16086     *
16087     * > NOTE: This will come from the internal `menuButton_` element.
16088     *
16089     * @param {string} [text]
16090     *        Control text for element.
16091     *
16092     * @param {Element} [el=this.menuButton_.el()]
16093     *        Element to set the title on.
16094     *
16095     * @return {string}
16096     *         - The control text when getting
16097     */
16098
16099
16100    MenuButton.prototype.controlText = function controlText(text) {
16101      var el = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.menuButton_.el();
16102
16103      return this.menuButton_.controlText(text, el);
16104    };
16105
16106    /**
16107     * Handle a click on a `MenuButton`.
16108     * See {@link ClickableComponent#handleClick} for instances where this is called.
16109     *
16110     * @param {EventTarget~Event} event
16111     *        The `keydown`, `tap`, or `click` event that caused this function to be
16112     *        called.
16113     *
16114     * @listens tap
16115     * @listens click
16116     */
16117
16118
16119    MenuButton.prototype.handleClick = function handleClick(event) {
16120      // When you click the button it adds focus, which will show the menu.
16121      // So we'll remove focus when the mouse leaves the button. Focus is needed
16122      // for tab navigation.
16123
16124      this.one(this.menu.contentEl(), 'mouseleave', bind(this, function (e) {
16125        this.unpressButton();
16126        this.el_.blur();
16127      }));
16128      if (this.buttonPressed_) {
16129        this.unpressButton();
16130      } else {
16131        this.pressButton();
16132      }
16133    };
16134
16135    /**
16136     * Set the focus to the actual button, not to this element
16137     */
16138
16139
16140    MenuButton.prototype.focus = function focus() {
16141      this.menuButton_.focus();
16142    };
16143
16144    /**
16145     * Remove the focus from the actual button, not this element
16146     */
16147
16148
16149    MenuButton.prototype.blur = function blur() {
16150      this.menuButton_.blur();
16151    };
16152
16153    /**
16154     * This gets called when a `MenuButton` gains focus via a `focus` event.
16155     * Turns on listening for `keydown` events. When they happen it
16156     * calls `this.handleKeyPress`.
16157     *
16158     * @param {EventTarget~Event} event
16159     *        The `focus` event that caused this function to be called.
16160     *
16161     * @listens focus
16162     */
16163
16164
16165    MenuButton.prototype.handleFocus = function handleFocus() {
16166      on(document_1, 'keydown', bind(this, this.handleKeyPress));
16167    };
16168
16169    /**
16170     * Called when a `MenuButton` loses focus. Turns off the listener for
16171     * `keydown` events. Which Stops `this.handleKeyPress` from getting called.
16172     *
16173     * @param {EventTarget~Event} event
16174     *        The `blur` event that caused this function to be called.
16175     *
16176     * @listens blur
16177     */
16178
16179
16180    MenuButton.prototype.handleBlur = function handleBlur() {
16181      off(document_1, 'keydown', bind(this, this.handleKeyPress));
16182    };
16183
16184    /**
16185     * Handle tab, escape, down arrow, and up arrow keys for `MenuButton`. See
16186     * {@link ClickableComponent#handleKeyPress} for instances where this is called.
16187     *
16188     * @param {EventTarget~Event} event
16189     *        The `keydown` event that caused this function to be called.
16190     *
16191     * @listens keydown
16192     */
16193
16194
16195    MenuButton.prototype.handleKeyPress = function handleKeyPress(event) {
16196
16197      // Escape (27) key or Tab (9) key unpress the 'button'
16198      if (event.which === 27 || event.which === 9) {
16199        if (this.buttonPressed_) {
16200          this.unpressButton();
16201        }
16202        // Don't preventDefault for Tab key - we still want to lose focus
16203        if (event.which !== 9) {
16204          event.preventDefault();
16205          // Set focus back to the menu button's button
16206          this.menuButton_.el_.focus();
16207        }
16208        // Up (38) key or Down (40) key press the 'button'
16209      } else if (event.which === 38 || event.which === 40) {
16210        if (!this.buttonPressed_) {
16211          this.pressButton();
16212          event.preventDefault();
16213        }
16214      }
16215    };
16216
16217    /**
16218     * Handle a `keydown` event on a sub-menu. The listener for this is added in
16219     * the constructor.
16220     *
16221     * @param {EventTarget~Event} event
16222     *        Key press event
16223     *
16224     * @listens keydown
16225     */
16226
16227
16228    MenuButton.prototype.handleSubmenuKeyPress = function handleSubmenuKeyPress(event) {
16229
16230      // Escape (27) key or Tab (9) key unpress the 'button'
16231      if (event.which === 27 || event.which === 9) {
16232        if (this.buttonPressed_) {
16233          this.unpressButton();
16234        }
16235        // Don't preventDefault for Tab key - we still want to lose focus
16236        if (event.which !== 9) {
16237          event.preventDefault();
16238          // Set focus back to the menu button's button
16239          this.menuButton_.el_.focus();
16240        }
16241      }
16242    };
16243
16244    /**
16245     * Put the current `MenuButton` into a pressed state.
16246     */
16247
16248
16249    MenuButton.prototype.pressButton = function pressButton() {
16250      if (this.enabled_) {
16251        this.buttonPressed_ = true;
16252        this.menu.lockShowing();
16253        this.menuButton_.el_.setAttribute('aria-expanded', 'true');
16254
16255        // set the focus into the submenu, except on iOS where it is resulting in
16256        // undesired scrolling behavior when the player is in an iframe
16257        if (IS_IOS && isInFrame()) {
16258          // Return early so that the menu isn't focused
16259          return;
16260        }
16261
16262        this.menu.focus();
16263      }
16264    };
16265
16266    /**
16267     * Take the current `MenuButton` out of a pressed state.
16268     */
16269
16270
16271    MenuButton.prototype.unpressButton = function unpressButton() {
16272      if (this.enabled_) {
16273        this.buttonPressed_ = false;
16274        this.menu.unlockShowing();
16275        this.menuButton_.el_.setAttribute('aria-expanded', 'false');
16276      }
16277    };
16278
16279    /**
16280     * Disable the `MenuButton`. Don't allow it to be clicked.
16281     */
16282
16283
16284    MenuButton.prototype.disable = function disable() {
16285      this.unpressButton();
16286
16287      this.enabled_ = false;
16288      this.addClass('vjs-disabled');
16289
16290      this.menuButton_.disable();
16291    };
16292
16293    /**
16294     * Enable the `MenuButton`. Allow it to be clicked.
16295     */
16296
16297
16298    MenuButton.prototype.enable = function enable() {
16299      this.enabled_ = true;
16300      this.removeClass('vjs-disabled');
16301
16302      this.menuButton_.enable();
16303    };
16304
16305    return MenuButton;
16306  }(Component);
16307
16308  Component.registerComponent('MenuButton', MenuButton);
16309
16310  /**
16311   * @file track-button.js
16312   */
16313
16314  /**
16315   * The base class for buttons that toggle specific  track types (e.g. subtitles).
16316   *
16317   * @extends MenuButton
16318   */
16319
16320  var TrackButton = function (_MenuButton) {
16321    inherits(TrackButton, _MenuButton);
16322
16323    /**
16324     * Creates an instance of this class.
16325     *
16326     * @param {Player} player
16327     *        The `Player` that this class should be attached to.
16328     *
16329     * @param {Object} [options]
16330     *        The key/value store of player options.
16331     */
16332    function TrackButton(player, options) {
16333      classCallCheck(this, TrackButton);
16334
16335      var tracks = options.tracks;
16336
16337      var _this = possibleConstructorReturn(this, _MenuButton.call(this, player, options));
16338
16339      if (_this.items.length <= 1) {
16340        _this.hide();
16341      }
16342
16343      if (!tracks) {
16344        return possibleConstructorReturn(_this);
16345      }
16346
16347      var updateHandler = bind(_this, _this.update);
16348
16349      tracks.addEventListener('removetrack', updateHandler);
16350      tracks.addEventListener('addtrack', updateHandler);
16351      _this.player_.on('ready', updateHandler);
16352
16353      _this.player_.on('dispose', function () {
16354        tracks.removeEventListener('removetrack', updateHandler);
16355        tracks.removeEventListener('addtrack', updateHandler);
16356      });
16357      return _this;
16358    }
16359
16360    return TrackButton;
16361  }(MenuButton);
16362
16363  Component.registerComponent('TrackButton', TrackButton);
16364
16365  /**
16366   * @file menu-item.js
16367   */
16368
16369  /**
16370   * The component for a menu item. `<li>`
16371   *
16372   * @extends ClickableComponent
16373   */
16374
16375  var MenuItem = function (_ClickableComponent) {
16376    inherits(MenuItem, _ClickableComponent);
16377
16378    /**
16379     * Creates an instance of the this class.
16380     *
16381     * @param {Player} player
16382     *        The `Player` that this class should be attached to.
16383     *
16384     * @param {Object} [options={}]
16385     *        The key/value store of player options.
16386     *
16387     */
16388    function MenuItem(player, options) {
16389      classCallCheck(this, MenuItem);
16390
16391      var _this = possibleConstructorReturn(this, _ClickableComponent.call(this, player, options));
16392
16393      _this.selectable = options.selectable;
16394      _this.isSelected_ = options.selected || false;
16395      _this.multiSelectable = options.multiSelectable;
16396
16397      _this.selected(_this.isSelected_);
16398
16399      if (_this.selectable) {
16400        if (_this.multiSelectable) {
16401          _this.el_.setAttribute('role', 'menuitemcheckbox');
16402        } else {
16403          _this.el_.setAttribute('role', 'menuitemradio');
16404        }
16405      } else {
16406        _this.el_.setAttribute('role', 'menuitem');
16407      }
16408      return _this;
16409    }
16410
16411    /**
16412     * Create the `MenuItem's DOM element
16413     *
16414     * @param {string} [type=li]
16415     *        Element's node type, not actually used, always set to `li`.
16416     *
16417     * @param {Object} [props={}]
16418     *        An object of properties that should be set on the element
16419     *
16420     * @param {Object} [attrs={}]
16421     *        An object of attributes that should be set on the element
16422     *
16423     * @return {Element}
16424     *         The element that gets created.
16425     */
16426
16427
16428    MenuItem.prototype.createEl = function createEl(type, props, attrs) {
16429      // The control is textual, not just an icon
16430      this.nonIconControl = true;
16431
16432      return _ClickableComponent.prototype.createEl.call(this, 'li', assign({
16433        className: 'vjs-menu-item',
16434        innerHTML: '<span class="vjs-menu-item-text">' + this.localize(this.options_.label) + '</span>',
16435        tabIndex: -1
16436      }, props), attrs);
16437    };
16438
16439    /**
16440     * Any click on a `MenuItem` puts it into the selected state.
16441     * See {@link ClickableComponent#handleClick} for instances where this is called.
16442     *
16443     * @param {EventTarget~Event} event
16444     *        The `keydown`, `tap`, or `click` event that caused this function to be
16445     *        called.
16446     *
16447     * @listens tap
16448     * @listens click
16449     */
16450
16451
16452    MenuItem.prototype.handleClick = function handleClick(event) {
16453      this.selected(true);
16454    };
16455
16456    /**
16457     * Set the state for this menu item as selected or not.
16458     *
16459     * @param {boolean} selected
16460     *        if the menu item is selected or not
16461     */
16462
16463
16464    MenuItem.prototype.selected = function selected(_selected) {
16465      if (this.selectable) {
16466        if (_selected) {
16467          this.addClass('vjs-selected');
16468          this.el_.setAttribute('aria-checked', 'true');
16469          // aria-checked isn't fully supported by browsers/screen readers,
16470          // so indicate selected state to screen reader in the control text.
16471          this.controlText(', selected');
16472          this.isSelected_ = true;
16473        } else {
16474          this.removeClass('vjs-selected');
16475          this.el_.setAttribute('aria-checked', 'false');
16476          // Indicate un-selected state to screen reader
16477          this.controlText('');
16478          this.isSelected_ = false;
16479        }
16480      }
16481    };
16482
16483    return MenuItem;
16484  }(ClickableComponent);
16485
16486  Component.registerComponent('MenuItem', MenuItem);
16487
16488  /**
16489   * @file text-track-menu-item.js
16490   */
16491
16492  /**
16493   * The specific menu item type for selecting a language within a text track kind
16494   *
16495   * @extends MenuItem
16496   */
16497
16498  var TextTrackMenuItem = function (_MenuItem) {
16499    inherits(TextTrackMenuItem, _MenuItem);
16500
16501    /**
16502     * Creates an instance of this class.
16503     *
16504     * @param {Player} player
16505     *        The `Player` that this class should be attached to.
16506     *
16507     * @param {Object} [options]
16508     *        The key/value store of player options.
16509     */
16510    function TextTrackMenuItem(player, options) {
16511      classCallCheck(this, TextTrackMenuItem);
16512
16513      var track = options.track;
16514      var tracks = player.textTracks();
16515
16516      // Modify options for parent MenuItem class's init.
16517      options.label = track.label || track.language || 'Unknown';
16518      options.selected = track.mode === 'showing';
16519
16520      var _this = possibleConstructorReturn(this, _MenuItem.call(this, player, options));
16521
16522      _this.track = track;
16523      var changeHandler = function changeHandler() {
16524        for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
16525          args[_key] = arguments[_key];
16526        }
16527
16528        _this.handleTracksChange.apply(_this, args);
16529      };
16530      var selectedLanguageChangeHandler = function selectedLanguageChangeHandler() {
16531        for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
16532          args[_key2] = arguments[_key2];
16533        }
16534
16535        _this.handleSelectedLanguageChange.apply(_this, args);
16536      };
16537
16538      player.on(['loadstart', 'texttrackchange'], changeHandler);
16539      tracks.addEventListener('change', changeHandler);
16540      tracks.addEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
16541      _this.on('dispose', function () {
16542        player.off(['loadstart', 'texttrackchange'], changeHandler);
16543        tracks.removeEventListener('change', changeHandler);
16544        tracks.removeEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
16545      });
16546
16547      // iOS7 doesn't dispatch change events to TextTrackLists when an
16548      // associated track's mode changes. Without something like
16549      // Object.observe() (also not present on iOS7), it's not
16550      // possible to detect changes to the mode attribute and polyfill
16551      // the change event. As a poor substitute, we manually dispatch
16552      // change events whenever the controls modify the mode.
16553      if (tracks.onchange === undefined) {
16554        var event = void 0;
16555
16556        _this.on(['tap', 'click'], function () {
16557          if (_typeof(window_1.Event) !== 'object') {
16558            // Android 2.3 throws an Illegal Constructor error for window.Event
16559            try {
16560              event = new window_1.Event('change');
16561            } catch (err) {
16562              // continue regardless of error
16563            }
16564          }
16565
16566          if (!event) {
16567            event = document_1.createEvent('Event');
16568            event.initEvent('change', true, true);
16569          }
16570
16571          tracks.dispatchEvent(event);
16572        });
16573      }
16574
16575      // set the default state based on current tracks
16576      _this.handleTracksChange();
16577      return _this;
16578    }
16579
16580    /**
16581     * This gets called when an `TextTrackMenuItem` is "clicked". See
16582     * {@link ClickableComponent} for more detailed information on what a click can be.
16583     *
16584     * @param {EventTarget~Event} event
16585     *        The `keydown`, `tap`, or `click` event that caused this function to be
16586     *        called.
16587     *
16588     * @listens tap
16589     * @listens click
16590     */
16591
16592
16593    TextTrackMenuItem.prototype.handleClick = function handleClick(event) {
16594      var kind = this.track.kind;
16595      var kinds = this.track.kinds;
16596      var tracks = this.player_.textTracks();
16597
16598      if (!kinds) {
16599        kinds = [kind];
16600      }
16601
16602      _MenuItem.prototype.handleClick.call(this, event);
16603
16604      if (!tracks) {
16605        return;
16606      }
16607
16608      for (var i = 0; i < tracks.length; i++) {
16609        var track = tracks[i];
16610
16611        if (track === this.track && kinds.indexOf(track.kind) > -1) {
16612          if (track.mode !== 'showing') {
16613            track.mode = 'showing';
16614          }
16615        } else if (track.mode !== 'disabled') {
16616          track.mode = 'disabled';
16617        }
16618      }
16619    };
16620
16621    /**
16622     * Handle text track list change
16623     *
16624     * @param {EventTarget~Event} event
16625     *        The `change` event that caused this function to be called.
16626     *
16627     * @listens TextTrackList#change
16628     */
16629
16630
16631    TextTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) {
16632      var shouldBeSelected = this.track.mode === 'showing';
16633
16634      // Prevent redundant selected() calls because they may cause
16635      // screen readers to read the appended control text unnecessarily
16636      if (shouldBeSelected !== this.isSelected_) {
16637        this.selected(shouldBeSelected);
16638      }
16639    };
16640
16641    TextTrackMenuItem.prototype.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) {
16642      if (this.track.mode === 'showing') {
16643        var selectedLanguage = this.player_.cache_.selectedLanguage;
16644
16645        // Don't replace the kind of track across the same language
16646        if (selectedLanguage && selectedLanguage.enabled && selectedLanguage.language === this.track.language && selectedLanguage.kind !== this.track.kind) {
16647          return;
16648        }
16649
16650        this.player_.cache_.selectedLanguage = {
16651          enabled: true,
16652          language: this.track.language,
16653          kind: this.track.kind
16654        };
16655      }
16656    };
16657
16658    TextTrackMenuItem.prototype.dispose = function dispose() {
16659      // remove reference to track object on dispose
16660      this.track = null;
16661
16662      _MenuItem.prototype.dispose.call(this);
16663    };
16664
16665    return TextTrackMenuItem;
16666  }(MenuItem);
16667
16668  Component.registerComponent('TextTrackMenuItem', TextTrackMenuItem);
16669
16670  /**
16671   * @file off-text-track-menu-item.js
16672   */
16673
16674  /**
16675   * A special menu item for turning of a specific type of text track
16676   *
16677   * @extends TextTrackMenuItem
16678   */
16679
16680  var OffTextTrackMenuItem = function (_TextTrackMenuItem) {
16681    inherits(OffTextTrackMenuItem, _TextTrackMenuItem);
16682
16683    /**
16684     * Creates an instance of this class.
16685     *
16686     * @param {Player} player
16687     *        The `Player` that this class should be attached to.
16688     *
16689     * @param {Object} [options]
16690     *        The key/value store of player options.
16691     */
16692    function OffTextTrackMenuItem(player, options) {
16693      classCallCheck(this, OffTextTrackMenuItem);
16694
16695      // Create pseudo track info
16696      // Requires options['kind']
16697      options.track = {
16698        player: player,
16699        kind: options.kind,
16700        kinds: options.kinds,
16701        default: false,
16702        mode: 'disabled'
16703      };
16704
16705      if (!options.kinds) {
16706        options.kinds = [options.kind];
16707      }
16708
16709      if (options.label) {
16710        options.track.label = options.label;
16711      } else {
16712        options.track.label = options.kinds.join(' and ') + ' off';
16713      }
16714
16715      // MenuItem is selectable
16716      options.selectable = true;
16717      // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
16718      options.multiSelectable = false;
16719
16720      return possibleConstructorReturn(this, _TextTrackMenuItem.call(this, player, options));
16721    }
16722
16723    /**
16724     * Handle text track change
16725     *
16726     * @param {EventTarget~Event} event
16727     *        The event that caused this function to run
16728     */
16729
16730
16731    OffTextTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) {
16732      var tracks = this.player().textTracks();
16733      var shouldBeSelected = true;
16734
16735      for (var i = 0, l = tracks.length; i < l; i++) {
16736        var track = tracks[i];
16737
16738        if (this.options_.kinds.indexOf(track.kind) > -1 && track.mode === 'showing') {
16739          shouldBeSelected = false;
16740          break;
16741        }
16742      }
16743
16744      // Prevent redundant selected() calls because they may cause
16745      // screen readers to read the appended control text unnecessarily
16746      if (shouldBeSelected !== this.isSelected_) {
16747        this.selected(shouldBeSelected);
16748      }
16749    };
16750
16751    OffTextTrackMenuItem.prototype.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) {
16752      var tracks = this.player().textTracks();
16753      var allHidden = true;
16754
16755      for (var i = 0, l = tracks.length; i < l; i++) {
16756        var track = tracks[i];
16757
16758        if (['captions', 'descriptions', 'subtitles'].indexOf(track.kind) > -1 && track.mode === 'showing') {
16759          allHidden = false;
16760          break;
16761        }
16762      }
16763
16764      if (allHidden) {
16765        this.player_.cache_.selectedLanguage = {
16766          enabled: false
16767        };
16768      }
16769    };
16770
16771    return OffTextTrackMenuItem;
16772  }(TextTrackMenuItem);
16773
16774  Component.registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem);
16775
16776  /**
16777   * @file text-track-button.js
16778   */
16779
16780  /**
16781   * The base class for buttons that toggle specific text track types (e.g. subtitles)
16782   *
16783   * @extends MenuButton
16784   */
16785
16786  var TextTrackButton = function (_TrackButton) {
16787    inherits(TextTrackButton, _TrackButton);
16788
16789    /**
16790     * Creates an instance of this class.
16791     *
16792     * @param {Player} player
16793     *        The `Player` that this class should be attached to.
16794     *
16795     * @param {Object} [options={}]
16796     *        The key/value store of player options.
16797     */
16798    function TextTrackButton(player) {
16799      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
16800      classCallCheck(this, TextTrackButton);
16801
16802      options.tracks = player.textTracks();
16803
16804      return possibleConstructorReturn(this, _TrackButton.call(this, player, options));
16805    }
16806
16807    /**
16808     * Create a menu item for each text track
16809     *
16810     * @param {TextTrackMenuItem[]} [items=[]]
16811     *        Existing array of items to use during creation
16812     *
16813     * @return {TextTrackMenuItem[]}
16814     *         Array of menu items that were created
16815     */
16816
16817
16818    TextTrackButton.prototype.createItems = function createItems() {
16819      var items = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
16820      var TrackMenuItem = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : TextTrackMenuItem;
16821
16822
16823      // Label is an override for the [track] off label
16824      // USed to localise captions/subtitles
16825      var label = void 0;
16826
16827      if (this.label_) {
16828        label = this.label_ + ' off';
16829      }
16830      // Add an OFF menu item to turn all tracks off
16831      items.push(new OffTextTrackMenuItem(this.player_, {
16832        kinds: this.kinds_,
16833        kind: this.kind_,
16834        label: label
16835      }));
16836
16837      this.hideThreshold_ += 1;
16838
16839      var tracks = this.player_.textTracks();
16840
16841      if (!Array.isArray(this.kinds_)) {
16842        this.kinds_ = [this.kind_];
16843      }
16844
16845      for (var i = 0; i < tracks.length; i++) {
16846        var track = tracks[i];
16847
16848        // only add tracks that are of an appropriate kind and have a label
16849        if (this.kinds_.indexOf(track.kind) > -1) {
16850
16851          var item = new TrackMenuItem(this.player_, {
16852            track: track,
16853            // MenuItem is selectable
16854            selectable: true,
16855            // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
16856            multiSelectable: false
16857          });
16858
16859          item.addClass('vjs-' + track.kind + '-menu-item');
16860          items.push(item);
16861        }
16862      }
16863
16864      return items;
16865    };
16866
16867    return TextTrackButton;
16868  }(TrackButton);
16869
16870  Component.registerComponent('TextTrackButton', TextTrackButton);
16871
16872  /**
16873   * @file chapters-track-menu-item.js
16874   */
16875
16876  /**
16877   * The chapter track menu item
16878   *
16879   * @extends MenuItem
16880   */
16881
16882  var ChaptersTrackMenuItem = function (_MenuItem) {
16883    inherits(ChaptersTrackMenuItem, _MenuItem);
16884
16885    /**
16886     * Creates an instance of this class.
16887     *
16888     * @param {Player} player
16889     *        The `Player` that this class should be attached to.
16890     *
16891     * @param {Object} [options]
16892     *        The key/value store of player options.
16893     */
16894    function ChaptersTrackMenuItem(player, options) {
16895      classCallCheck(this, ChaptersTrackMenuItem);
16896
16897      var track = options.track;
16898      var cue = options.cue;
16899      var currentTime = player.currentTime();
16900
16901      // Modify options for parent MenuItem class's init.
16902      options.selectable = true;
16903      options.multiSelectable = false;
16904      options.label = cue.text;
16905      options.selected = cue.startTime <= currentTime && currentTime < cue.endTime;
16906
16907      var _this = possibleConstructorReturn(this, _MenuItem.call(this, player, options));
16908
16909      _this.track = track;
16910      _this.cue = cue;
16911      track.addEventListener('cuechange', bind(_this, _this.update));
16912      return _this;
16913    }
16914
16915    /**
16916     * This gets called when an `ChaptersTrackMenuItem` is "clicked". See
16917     * {@link ClickableComponent} for more detailed information on what a click can be.
16918     *
16919     * @param {EventTarget~Event} [event]
16920     *        The `keydown`, `tap`, or `click` event that caused this function to be
16921     *        called.
16922     *
16923     * @listens tap
16924     * @listens click
16925     */
16926
16927
16928    ChaptersTrackMenuItem.prototype.handleClick = function handleClick(event) {
16929      _MenuItem.prototype.handleClick.call(this);
16930      this.player_.currentTime(this.cue.startTime);
16931      this.update(this.cue.startTime);
16932    };
16933
16934    /**
16935     * Update chapter menu item
16936     *
16937     * @param {EventTarget~Event} [event]
16938     *        The `cuechange` event that caused this function to run.
16939     *
16940     * @listens TextTrack#cuechange
16941     */
16942
16943
16944    ChaptersTrackMenuItem.prototype.update = function update(event) {
16945      var cue = this.cue;
16946      var currentTime = this.player_.currentTime();
16947
16948      // vjs.log(currentTime, cue.startTime);
16949      this.selected(cue.startTime <= currentTime && currentTime < cue.endTime);
16950    };
16951
16952    return ChaptersTrackMenuItem;
16953  }(MenuItem);
16954
16955  Component.registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem);
16956
16957  /**
16958   * @file chapters-button.js
16959   */
16960
16961  /**
16962   * The button component for toggling and selecting chapters
16963   * Chapters act much differently than other text tracks
16964   * Cues are navigation vs. other tracks of alternative languages
16965   *
16966   * @extends TextTrackButton
16967   */
16968
16969  var ChaptersButton = function (_TextTrackButton) {
16970    inherits(ChaptersButton, _TextTrackButton);
16971
16972    /**
16973     * Creates an instance of this class.
16974     *
16975     * @param {Player} player
16976     *        The `Player` that this class should be attached to.
16977     *
16978     * @param {Object} [options]
16979     *        The key/value store of player options.
16980     *
16981     * @param {Component~ReadyCallback} [ready]
16982     *        The function to call when this function is ready.
16983     */
16984    function ChaptersButton(player, options, ready) {
16985      classCallCheck(this, ChaptersButton);
16986      return possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready));
16987    }
16988
16989    /**
16990     * Builds the default DOM `className`.
16991     *
16992     * @return {string}
16993     *         The DOM `className` for this object.
16994     */
16995
16996
16997    ChaptersButton.prototype.buildCSSClass = function buildCSSClass() {
16998      return 'vjs-chapters-button ' + _TextTrackButton.prototype.buildCSSClass.call(this);
16999    };
17000
17001    ChaptersButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() {
17002      return 'vjs-chapters-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
17003    };
17004
17005    /**
17006     * Update the menu based on the current state of its items.
17007     *
17008     * @param {EventTarget~Event} [event]
17009     *        An event that triggered this function to run.
17010     *
17011     * @listens TextTrackList#addtrack
17012     * @listens TextTrackList#removetrack
17013     * @listens TextTrackList#change
17014     */
17015
17016
17017    ChaptersButton.prototype.update = function update(event) {
17018      if (!this.track_ || event && (event.type === 'addtrack' || event.type === 'removetrack')) {
17019        this.setTrack(this.findChaptersTrack());
17020      }
17021      _TextTrackButton.prototype.update.call(this);
17022    };
17023
17024    /**
17025     * Set the currently selected track for the chapters button.
17026     *
17027     * @param {TextTrack} track
17028     *        The new track to select. Nothing will change if this is the currently selected
17029     *        track.
17030     */
17031
17032
17033    ChaptersButton.prototype.setTrack = function setTrack(track) {
17034      if (this.track_ === track) {
17035        return;
17036      }
17037
17038      if (!this.updateHandler_) {
17039        this.updateHandler_ = this.update.bind(this);
17040      }
17041
17042      // here this.track_ refers to the old track instance
17043      if (this.track_) {
17044        var remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
17045
17046        if (remoteTextTrackEl) {
17047          remoteTextTrackEl.removeEventListener('load', this.updateHandler_);
17048        }
17049
17050        this.track_ = null;
17051      }
17052
17053      this.track_ = track;
17054
17055      // here this.track_ refers to the new track instance
17056      if (this.track_) {
17057        this.track_.mode = 'hidden';
17058
17059        var _remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
17060
17061        if (_remoteTextTrackEl) {
17062          _remoteTextTrackEl.addEventListener('load', this.updateHandler_);
17063        }
17064      }
17065    };
17066
17067    /**
17068     * Find the track object that is currently in use by this ChaptersButton
17069     *
17070     * @return {TextTrack|undefined}
17071     *         The current track or undefined if none was found.
17072     */
17073
17074
17075    ChaptersButton.prototype.findChaptersTrack = function findChaptersTrack() {
17076      var tracks = this.player_.textTracks() || [];
17077
17078      for (var i = tracks.length - 1; i >= 0; i--) {
17079        // We will always choose the last track as our chaptersTrack
17080        var track = tracks[i];
17081
17082        if (track.kind === this.kind_) {
17083          return track;
17084        }
17085      }
17086    };
17087
17088    /**
17089     * Get the caption for the ChaptersButton based on the track label. This will also
17090     * use the current tracks localized kind as a fallback if a label does not exist.
17091     *
17092     * @return {string}
17093     *         The tracks current label or the localized track kind.
17094     */
17095
17096
17097    ChaptersButton.prototype.getMenuCaption = function getMenuCaption() {
17098      if (this.track_ && this.track_.label) {
17099        return this.track_.label;
17100      }
17101      return this.localize(toTitleCase(this.kind_));
17102    };
17103
17104    /**
17105     * Create menu from chapter track
17106     *
17107     * @return {Menu}
17108     *         New menu for the chapter buttons
17109     */
17110
17111
17112    ChaptersButton.prototype.createMenu = function createMenu() {
17113      this.options_.title = this.getMenuCaption();
17114      return _TextTrackButton.prototype.createMenu.call(this);
17115    };
17116
17117    /**
17118     * Create a menu item for each text track
17119     *
17120     * @return {TextTrackMenuItem[]}
17121     *         Array of menu items
17122     */
17123
17124
17125    ChaptersButton.prototype.createItems = function createItems() {
17126      var items = [];
17127
17128      if (!this.track_) {
17129        return items;
17130      }
17131
17132      var cues = this.track_.cues;
17133
17134      if (!cues) {
17135        return items;
17136      }
17137
17138      for (var i = 0, l = cues.length; i < l; i++) {
17139        var cue = cues[i];
17140        var mi = new ChaptersTrackMenuItem(this.player_, { track: this.track_, cue: cue });
17141
17142        items.push(mi);
17143      }
17144
17145      return items;
17146    };
17147
17148    return ChaptersButton;
17149  }(TextTrackButton);
17150
17151  /**
17152   * `kind` of TextTrack to look for to associate it with this menu.
17153   *
17154   * @type {string}
17155   * @private
17156   */
17157
17158
17159  ChaptersButton.prototype.kind_ = 'chapters';
17160
17161  /**
17162   * The text that should display over the `ChaptersButton`s controls. Added for localization.
17163   *
17164   * @type {string}
17165   * @private
17166   */
17167  ChaptersButton.prototype.controlText_ = 'Chapters';
17168
17169  Component.registerComponent('ChaptersButton', ChaptersButton);
17170
17171  /**
17172   * @file descriptions-button.js
17173   */
17174
17175  /**
17176   * The button component for toggling and selecting descriptions
17177   *
17178   * @extends TextTrackButton
17179   */
17180
17181  var DescriptionsButton = function (_TextTrackButton) {
17182    inherits(DescriptionsButton, _TextTrackButton);
17183
17184    /**
17185     * Creates an instance of this class.
17186     *
17187     * @param {Player} player
17188     *        The `Player` that this class should be attached to.
17189     *
17190     * @param {Object} [options]
17191     *        The key/value store of player options.
17192     *
17193     * @param {Component~ReadyCallback} [ready]
17194     *        The function to call when this component is ready.
17195     */
17196    function DescriptionsButton(player, options, ready) {
17197      classCallCheck(this, DescriptionsButton);
17198
17199      var _this = possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready));
17200
17201      var tracks = player.textTracks();
17202      var changeHandler = bind(_this, _this.handleTracksChange);
17203
17204      tracks.addEventListener('change', changeHandler);
17205      _this.on('dispose', function () {
17206        tracks.removeEventListener('change', changeHandler);
17207      });
17208      return _this;
17209    }
17210
17211    /**
17212     * Handle text track change
17213     *
17214     * @param {EventTarget~Event} event
17215     *        The event that caused this function to run
17216     *
17217     * @listens TextTrackList#change
17218     */
17219
17220
17221    DescriptionsButton.prototype.handleTracksChange = function handleTracksChange(event) {
17222      var tracks = this.player().textTracks();
17223      var disabled = false;
17224
17225      // Check whether a track of a different kind is showing
17226      for (var i = 0, l = tracks.length; i < l; i++) {
17227        var track = tracks[i];
17228
17229        if (track.kind !== this.kind_ && track.mode === 'showing') {
17230          disabled = true;
17231          break;
17232        }
17233      }
17234
17235      // If another track is showing, disable this menu button
17236      if (disabled) {
17237        this.disable();
17238      } else {
17239        this.enable();
17240      }
17241    };
17242
17243    /**
17244     * Builds the default DOM `className`.
17245     *
17246     * @return {string}
17247     *         The DOM `className` for this object.
17248     */
17249
17250
17251    DescriptionsButton.prototype.buildCSSClass = function buildCSSClass() {
17252      return 'vjs-descriptions-button ' + _TextTrackButton.prototype.buildCSSClass.call(this);
17253    };
17254
17255    DescriptionsButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() {
17256      return 'vjs-descriptions-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
17257    };
17258
17259    return DescriptionsButton;
17260  }(TextTrackButton);
17261
17262  /**
17263   * `kind` of TextTrack to look for to associate it with this menu.
17264   *
17265   * @type {string}
17266   * @private
17267   */
17268
17269
17270  DescriptionsButton.prototype.kind_ = 'descriptions';
17271
17272  /**
17273   * The text that should display over the `DescriptionsButton`s controls. Added for localization.
17274   *
17275   * @type {string}
17276   * @private
17277   */
17278  DescriptionsButton.prototype.controlText_ = 'Descriptions';
17279
17280  Component.registerComponent('DescriptionsButton', DescriptionsButton);
17281
17282  /**
17283   * @file subtitles-button.js
17284   */
17285
17286  /**
17287   * The button component for toggling and selecting subtitles
17288   *
17289   * @extends TextTrackButton
17290   */
17291
17292  var SubtitlesButton = function (_TextTrackButton) {
17293    inherits(SubtitlesButton, _TextTrackButton);
17294
17295    /**
17296     * Creates an instance of this class.
17297     *
17298     * @param {Player} player
17299     *        The `Player` that this class should be attached to.
17300     *
17301     * @param {Object} [options]
17302     *        The key/value store of player options.
17303     *
17304     * @param {Component~ReadyCallback} [ready]
17305     *        The function to call when this component is ready.
17306     */
17307    function SubtitlesButton(player, options, ready) {
17308      classCallCheck(this, SubtitlesButton);
17309      return possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready));
17310    }
17311
17312    /**
17313     * Builds the default DOM `className`.
17314     *
17315     * @return {string}
17316     *         The DOM `className` for this object.
17317     */
17318
17319
17320    SubtitlesButton.prototype.buildCSSClass = function buildCSSClass() {
17321      return 'vjs-subtitles-button ' + _TextTrackButton.prototype.buildCSSClass.call(this);
17322    };
17323
17324    SubtitlesButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() {
17325      return 'vjs-subtitles-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
17326    };
17327
17328    return SubtitlesButton;
17329  }(TextTrackButton);
17330
17331  /**
17332   * `kind` of TextTrack to look for to associate it with this menu.
17333   *
17334   * @type {string}
17335   * @private
17336   */
17337
17338
17339  SubtitlesButton.prototype.kind_ = 'subtitles';
17340
17341  /**
17342   * The text that should display over the `SubtitlesButton`s controls. Added for localization.
17343   *
17344   * @type {string}
17345   * @private
17346   */
17347  SubtitlesButton.prototype.controlText_ = 'Subtitles';
17348
17349  Component.registerComponent('SubtitlesButton', SubtitlesButton);
17350
17351  /**
17352   * @file caption-settings-menu-item.js
17353   */
17354
17355  /**
17356   * The menu item for caption track settings menu
17357   *
17358   * @extends TextTrackMenuItem
17359   */
17360
17361  var CaptionSettingsMenuItem = function (_TextTrackMenuItem) {
17362    inherits(CaptionSettingsMenuItem, _TextTrackMenuItem);
17363
17364    /**
17365     * Creates an instance of this class.
17366     *
17367     * @param {Player} player
17368     *        The `Player` that this class should be attached to.
17369     *
17370     * @param {Object} [options]
17371     *        The key/value store of player options.
17372     */
17373    function CaptionSettingsMenuItem(player, options) {
17374      classCallCheck(this, CaptionSettingsMenuItem);
17375
17376      options.track = {
17377        player: player,
17378        kind: options.kind,
17379        label: options.kind + ' settings',
17380        selectable: false,
17381        default: false,
17382        mode: 'disabled'
17383      };
17384
17385      // CaptionSettingsMenuItem has no concept of 'selected'
17386      options.selectable = false;
17387
17388      options.name = 'CaptionSettingsMenuItem';
17389
17390      var _this = possibleConstructorReturn(this, _TextTrackMenuItem.call(this, player, options));
17391
17392      _this.addClass('vjs-texttrack-settings');
17393      _this.controlText(', opens ' + options.kind + ' settings dialog');
17394      return _this;
17395    }
17396
17397    /**
17398     * This gets called when an `CaptionSettingsMenuItem` is "clicked". See
17399     * {@link ClickableComponent} for more detailed information on what a click can be.
17400     *
17401     * @param {EventTarget~Event} [event]
17402     *        The `keydown`, `tap`, or `click` event that caused this function to be
17403     *        called.
17404     *
17405     * @listens tap
17406     * @listens click
17407     */
17408
17409
17410    CaptionSettingsMenuItem.prototype.handleClick = function handleClick(event) {
17411      this.player().getChild('textTrackSettings').open();
17412    };
17413
17414    return CaptionSettingsMenuItem;
17415  }(TextTrackMenuItem);
17416
17417  Component.registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem);
17418
17419  /**
17420   * @file captions-button.js
17421   */
17422
17423  /**
17424   * The button component for toggling and selecting captions
17425   *
17426   * @extends TextTrackButton
17427   */
17428
17429  var CaptionsButton = function (_TextTrackButton) {
17430    inherits(CaptionsButton, _TextTrackButton);
17431
17432    /**
17433     * Creates an instance of this class.
17434     *
17435     * @param {Player} player
17436     *        The `Player` that this class should be attached to.
17437     *
17438     * @param {Object} [options]
17439     *        The key/value store of player options.
17440     *
17441     * @param {Component~ReadyCallback} [ready]
17442     *        The function to call when this component is ready.
17443     */
17444    function CaptionsButton(player, options, ready) {
17445      classCallCheck(this, CaptionsButton);
17446      return possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready));
17447    }
17448
17449    /**
17450     * Builds the default DOM `className`.
17451     *
17452     * @return {string}
17453     *         The DOM `className` for this object.
17454     */
17455
17456
17457    CaptionsButton.prototype.buildCSSClass = function buildCSSClass() {
17458      return 'vjs-captions-button ' + _TextTrackButton.prototype.buildCSSClass.call(this);
17459    };
17460
17461    CaptionsButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() {
17462      return 'vjs-captions-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
17463    };
17464
17465    /**
17466     * Create caption menu items
17467     *
17468     * @return {CaptionSettingsMenuItem[]}
17469     *         The array of current menu items.
17470     */
17471
17472
17473    CaptionsButton.prototype.createItems = function createItems() {
17474      var items = [];
17475
17476      if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
17477        items.push(new CaptionSettingsMenuItem(this.player_, { kind: this.kind_ }));
17478
17479        this.hideThreshold_ += 1;
17480      }
17481
17482      return _TextTrackButton.prototype.createItems.call(this, items);
17483    };
17484
17485    return CaptionsButton;
17486  }(TextTrackButton);
17487
17488  /**
17489   * `kind` of TextTrack to look for to associate it with this menu.
17490   *
17491   * @type {string}
17492   * @private
17493   */
17494
17495
17496  CaptionsButton.prototype.kind_ = 'captions';
17497
17498  /**
17499   * The text that should display over the `CaptionsButton`s controls. Added for localization.
17500   *
17501   * @type {string}
17502   * @private
17503   */
17504  CaptionsButton.prototype.controlText_ = 'Captions';
17505
17506  Component.registerComponent('CaptionsButton', CaptionsButton);
17507
17508  /**
17509   * @file subs-caps-menu-item.js
17510   */
17511
17512  /**
17513   * SubsCapsMenuItem has an [cc] icon to distinguish captions from subtitles
17514   * in the SubsCapsMenu.
17515   *
17516   * @extends TextTrackMenuItem
17517   */
17518
17519  var SubsCapsMenuItem = function (_TextTrackMenuItem) {
17520    inherits(SubsCapsMenuItem, _TextTrackMenuItem);
17521
17522    function SubsCapsMenuItem() {
17523      classCallCheck(this, SubsCapsMenuItem);
17524      return possibleConstructorReturn(this, _TextTrackMenuItem.apply(this, arguments));
17525    }
17526
17527    SubsCapsMenuItem.prototype.createEl = function createEl(type, props, attrs) {
17528      var innerHTML = '<span class="vjs-menu-item-text">' + this.localize(this.options_.label);
17529
17530      if (this.options_.track.kind === 'captions') {
17531        innerHTML += '\n        <span aria-hidden="true" class="vjs-icon-placeholder"></span>\n        <span class="vjs-control-text"> ' + this.localize('Captions') + '</span>\n      ';
17532      }
17533
17534      innerHTML += '</span>';
17535
17536      var el = _TextTrackMenuItem.prototype.createEl.call(this, type, assign({
17537        innerHTML: innerHTML
17538      }, props), attrs);
17539
17540      return el;
17541    };
17542
17543    return SubsCapsMenuItem;
17544  }(TextTrackMenuItem);
17545
17546  Component.registerComponent('SubsCapsMenuItem', SubsCapsMenuItem);
17547
17548  /**
17549   * @file sub-caps-button.js
17550   */
17551  /**
17552   * The button component for toggling and selecting captions and/or subtitles
17553   *
17554   * @extends TextTrackButton
17555   */
17556
17557  var SubsCapsButton = function (_TextTrackButton) {
17558    inherits(SubsCapsButton, _TextTrackButton);
17559
17560    function SubsCapsButton(player) {
17561      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
17562      classCallCheck(this, SubsCapsButton);
17563
17564      // Although North America uses "captions" in most cases for
17565      // "captions and subtitles" other locales use "subtitles"
17566      var _this = possibleConstructorReturn(this, _TextTrackButton.call(this, player, options));
17567
17568      _this.label_ = 'subtitles';
17569      if (['en', 'en-us', 'en-ca', 'fr-ca'].indexOf(_this.player_.language_) > -1) {
17570        _this.label_ = 'captions';
17571      }
17572      _this.menuButton_.controlText(toTitleCase(_this.label_));
17573      return _this;
17574    }
17575
17576    /**
17577     * Builds the default DOM `className`.
17578     *
17579     * @return {string}
17580     *         The DOM `className` for this object.
17581     */
17582
17583
17584    SubsCapsButton.prototype.buildCSSClass = function buildCSSClass() {
17585      return 'vjs-subs-caps-button ' + _TextTrackButton.prototype.buildCSSClass.call(this);
17586    };
17587
17588    SubsCapsButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() {
17589      return 'vjs-subs-caps-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
17590    };
17591
17592    /**
17593     * Create caption/subtitles menu items
17594     *
17595     * @return {CaptionSettingsMenuItem[]}
17596     *         The array of current menu items.
17597     */
17598
17599
17600    SubsCapsButton.prototype.createItems = function createItems() {
17601      var items = [];
17602
17603      if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
17604        items.push(new CaptionSettingsMenuItem(this.player_, { kind: this.label_ }));
17605
17606        this.hideThreshold_ += 1;
17607      }
17608
17609      items = _TextTrackButton.prototype.createItems.call(this, items, SubsCapsMenuItem);
17610      return items;
17611    };
17612
17613    return SubsCapsButton;
17614  }(TextTrackButton);
17615
17616  /**
17617   * `kind`s of TextTrack to look for to associate it with this menu.
17618   *
17619   * @type {array}
17620   * @private
17621   */
17622
17623
17624  SubsCapsButton.prototype.kinds_ = ['captions', 'subtitles'];
17625
17626  /**
17627   * The text that should display over the `SubsCapsButton`s controls.
17628   *
17629   *
17630   * @type {string}
17631   * @private
17632   */
17633  SubsCapsButton.prototype.controlText_ = 'Subtitles';
17634
17635  Component.registerComponent('SubsCapsButton', SubsCapsButton);
17636
17637  /**
17638   * @file audio-track-menu-item.js
17639   */
17640
17641  /**
17642   * An {@link AudioTrack} {@link MenuItem}
17643   *
17644   * @extends MenuItem
17645   */
17646
17647  var AudioTrackMenuItem = function (_MenuItem) {
17648    inherits(AudioTrackMenuItem, _MenuItem);
17649
17650    /**
17651     * Creates an instance of this class.
17652     *
17653     * @param {Player} player
17654     *        The `Player` that this class should be attached to.
17655     *
17656     * @param {Object} [options]
17657     *        The key/value store of player options.
17658     */
17659    function AudioTrackMenuItem(player, options) {
17660      classCallCheck(this, AudioTrackMenuItem);
17661
17662      var track = options.track;
17663      var tracks = player.audioTracks();
17664
17665      // Modify options for parent MenuItem class's init.
17666      options.label = track.label || track.language || 'Unknown';
17667      options.selected = track.enabled;
17668
17669      var _this = possibleConstructorReturn(this, _MenuItem.call(this, player, options));
17670
17671      _this.track = track;
17672
17673      var changeHandler = function changeHandler() {
17674        for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
17675          args[_key] = arguments[_key];
17676        }
17677
17678        _this.handleTracksChange.apply(_this, args);
17679      };
17680
17681      tracks.addEventListener('change', changeHandler);
17682      _this.on('dispose', function () {
17683        tracks.removeEventListener('change', changeHandler);
17684      });
17685      return _this;
17686    }
17687
17688    /**
17689     * This gets called when an `AudioTrackMenuItem is "clicked". See {@link ClickableComponent}
17690     * for more detailed information on what a click can be.
17691     *
17692     * @param {EventTarget~Event} [event]
17693     *        The `keydown`, `tap`, or `click` event that caused this function to be
17694     *        called.
17695     *
17696     * @listens tap
17697     * @listens click
17698     */
17699
17700
17701    AudioTrackMenuItem.prototype.handleClick = function handleClick(event) {
17702      var tracks = this.player_.audioTracks();
17703
17704      _MenuItem.prototype.handleClick.call(this, event);
17705
17706      for (var i = 0; i < tracks.length; i++) {
17707        var track = tracks[i];
17708
17709        track.enabled = track === this.track;
17710      }
17711    };
17712
17713    /**
17714     * Handle any {@link AudioTrack} change.
17715     *
17716     * @param {EventTarget~Event} [event]
17717     *        The {@link AudioTrackList#change} event that caused this to run.
17718     *
17719     * @listens AudioTrackList#change
17720     */
17721
17722
17723    AudioTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) {
17724      this.selected(this.track.enabled);
17725    };
17726
17727    return AudioTrackMenuItem;
17728  }(MenuItem);
17729
17730  Component.registerComponent('AudioTrackMenuItem', AudioTrackMenuItem);
17731
17732  /**
17733   * @file audio-track-button.js
17734   */
17735
17736  /**
17737   * The base class for buttons that toggle specific {@link AudioTrack} types.
17738   *
17739   * @extends TrackButton
17740   */
17741
17742  var AudioTrackButton = function (_TrackButton) {
17743    inherits(AudioTrackButton, _TrackButton);
17744
17745    /**
17746     * Creates an instance of this class.
17747     *
17748     * @param {Player} player
17749     *        The `Player` that this class should be attached to.
17750     *
17751     * @param {Object} [options={}]
17752     *        The key/value store of player options.
17753     */
17754    function AudioTrackButton(player) {
17755      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
17756      classCallCheck(this, AudioTrackButton);
17757
17758      options.tracks = player.audioTracks();
17759
17760      return possibleConstructorReturn(this, _TrackButton.call(this, player, options));
17761    }
17762
17763    /**
17764     * Builds the default DOM `className`.
17765     *
17766     * @return {string}
17767     *         The DOM `className` for this object.
17768     */
17769
17770
17771    AudioTrackButton.prototype.buildCSSClass = function buildCSSClass() {
17772      return 'vjs-audio-button ' + _TrackButton.prototype.buildCSSClass.call(this);
17773    };
17774
17775    AudioTrackButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() {
17776      return 'vjs-audio-button ' + _TrackButton.prototype.buildWrapperCSSClass.call(this);
17777    };
17778
17779    /**
17780     * Create a menu item for each audio track
17781     *
17782     * @param {AudioTrackMenuItem[]} [items=[]]
17783     *        An array of existing menu items to use.
17784     *
17785     * @return {AudioTrackMenuItem[]}
17786     *         An array of menu items
17787     */
17788
17789
17790    AudioTrackButton.prototype.createItems = function createItems() {
17791      var items = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
17792
17793      // if there's only one audio track, there no point in showing it
17794      this.hideThreshold_ = 1;
17795
17796      var tracks = this.player_.audioTracks();
17797
17798      for (var i = 0; i < tracks.length; i++) {
17799        var track = tracks[i];
17800
17801        items.push(new AudioTrackMenuItem(this.player_, {
17802          track: track,
17803          // MenuItem is selectable
17804          selectable: true,
17805          // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
17806          multiSelectable: false
17807        }));
17808      }
17809
17810      return items;
17811    };
17812
17813    return AudioTrackButton;
17814  }(TrackButton);
17815
17816  /**
17817   * The text that should display over the `AudioTrackButton`s controls. Added for localization.
17818   *
17819   * @type {string}
17820   * @private
17821   */
17822
17823
17824  AudioTrackButton.prototype.controlText_ = 'Audio Track';
17825  Component.registerComponent('AudioTrackButton', AudioTrackButton);
17826
17827  /**
17828   * @file playback-rate-menu-item.js
17829   */
17830
17831  /**
17832   * The specific menu item type for selecting a playback rate.
17833   *
17834   * @extends MenuItem
17835   */
17836
17837  var PlaybackRateMenuItem = function (_MenuItem) {
17838    inherits(PlaybackRateMenuItem, _MenuItem);
17839
17840    /**
17841     * Creates an instance of this class.
17842     *
17843     * @param {Player} player
17844     *        The `Player` that this class should be attached to.
17845     *
17846     * @param {Object} [options]
17847     *        The key/value store of player options.
17848     */
17849    function PlaybackRateMenuItem(player, options) {
17850      classCallCheck(this, PlaybackRateMenuItem);
17851
17852      var label = options.rate;
17853      var rate = parseFloat(label, 10);
17854
17855      // Modify options for parent MenuItem class's init.
17856      options.label = label;
17857      options.selected = rate === 1;
17858      options.selectable = true;
17859      options.multiSelectable = false;
17860
17861      var _this = possibleConstructorReturn(this, _MenuItem.call(this, player, options));
17862
17863      _this.label = label;
17864      _this.rate = rate;
17865
17866      _this.on(player, 'ratechange', _this.update);
17867      return _this;
17868    }
17869
17870    /**
17871     * This gets called when an `PlaybackRateMenuItem` is "clicked". See
17872     * {@link ClickableComponent} for more detailed information on what a click can be.
17873     *
17874     * @param {EventTarget~Event} [event]
17875     *        The `keydown`, `tap`, or `click` event that caused this function to be
17876     *        called.
17877     *
17878     * @listens tap
17879     * @listens click
17880     */
17881
17882
17883    PlaybackRateMenuItem.prototype.handleClick = function handleClick(event) {
17884      _MenuItem.prototype.handleClick.call(this);
17885      this.player().playbackRate(this.rate);
17886    };
17887
17888    /**
17889     * Update the PlaybackRateMenuItem when the playbackrate changes.
17890     *
17891     * @param {EventTarget~Event} [event]
17892     *        The `ratechange` event that caused this function to run.
17893     *
17894     * @listens Player#ratechange
17895     */
17896
17897
17898    PlaybackRateMenuItem.prototype.update = function update(event) {
17899      this.selected(this.player().playbackRate() === this.rate);
17900    };
17901
17902    return PlaybackRateMenuItem;
17903  }(MenuItem);
17904
17905  /**
17906   * The text that should display over the `PlaybackRateMenuItem`s controls. Added for localization.
17907   *
17908   * @type {string}
17909   * @private
17910   */
17911
17912
17913  PlaybackRateMenuItem.prototype.contentElType = 'button';
17914
17915  Component.registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem);
17916
17917  /**
17918   * @file playback-rate-menu-button.js
17919   */
17920
17921  /**
17922   * The component for controlling the playback rate.
17923   *
17924   * @extends MenuButton
17925   */
17926
17927  var PlaybackRateMenuButton = function (_MenuButton) {
17928    inherits(PlaybackRateMenuButton, _MenuButton);
17929
17930    /**
17931     * Creates an instance of this class.
17932     *
17933     * @param {Player} player
17934     *        The `Player` that this class should be attached to.
17935     *
17936     * @param {Object} [options]
17937     *        The key/value store of player options.
17938     */
17939    function PlaybackRateMenuButton(player, options) {
17940      classCallCheck(this, PlaybackRateMenuButton);
17941
17942      var _this = possibleConstructorReturn(this, _MenuButton.call(this, player, options));
17943
17944      _this.updateVisibility();
17945      _this.updateLabel();
17946
17947      _this.on(player, 'loadstart', _this.updateVisibility);
17948      _this.on(player, 'ratechange', _this.updateLabel);
17949      return _this;
17950    }
17951
17952    /**
17953     * Create the `Component`'s DOM element
17954     *
17955     * @return {Element}
17956     *         The element that was created.
17957     */
17958
17959
17960    PlaybackRateMenuButton.prototype.createEl = function createEl$$1() {
17961      var el = _MenuButton.prototype.createEl.call(this);
17962
17963      this.labelEl_ = createEl('div', {
17964        className: 'vjs-playback-rate-value',
17965        innerHTML: '1x'
17966      });
17967
17968      el.appendChild(this.labelEl_);
17969
17970      return el;
17971    };
17972
17973    PlaybackRateMenuButton.prototype.dispose = function dispose() {
17974      this.labelEl_ = null;
17975
17976      _MenuButton.prototype.dispose.call(this);
17977    };
17978
17979    /**
17980     * Builds the default DOM `className`.
17981     *
17982     * @return {string}
17983     *         The DOM `className` for this object.
17984     */
17985
17986
17987    PlaybackRateMenuButton.prototype.buildCSSClass = function buildCSSClass() {
17988      return 'vjs-playback-rate ' + _MenuButton.prototype.buildCSSClass.call(this);
17989    };
17990
17991    PlaybackRateMenuButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() {
17992      return 'vjs-playback-rate ' + _MenuButton.prototype.buildWrapperCSSClass.call(this);
17993    };
17994
17995    /**
17996     * Create the playback rate menu
17997     *
17998     * @return {Menu}
17999     *         Menu object populated with {@link PlaybackRateMenuItem}s
18000     */
18001
18002
18003    PlaybackRateMenuButton.prototype.createMenu = function createMenu() {
18004      var menu = new Menu(this.player());
18005      var rates = this.playbackRates();
18006
18007      if (rates) {
18008        for (var i = rates.length - 1; i >= 0; i--) {
18009          menu.addChild(new PlaybackRateMenuItem(this.player(), { rate: rates[i] + 'x' }));
18010        }
18011      }
18012
18013      return menu;
18014    };
18015
18016    /**
18017     * Updates ARIA accessibility attributes
18018     */
18019
18020
18021    PlaybackRateMenuButton.prototype.updateARIAAttributes = function updateARIAAttributes() {
18022      // Current playback rate
18023      this.el().setAttribute('aria-valuenow', this.player().playbackRate());
18024    };
18025
18026    /**
18027     * This gets called when an `PlaybackRateMenuButton` is "clicked". See
18028     * {@link ClickableComponent} for more detailed information on what a click can be.
18029     *
18030     * @param {EventTarget~Event} [event]
18031     *        The `keydown`, `tap`, or `click` event that caused this function to be
18032     *        called.
18033     *
18034     * @listens tap
18035     * @listens click
18036     */
18037
18038
18039    PlaybackRateMenuButton.prototype.handleClick = function handleClick(event) {
18040      // select next rate option
18041      var currentRate = this.player().playbackRate();
18042      var rates = this.playbackRates();
18043
18044      // this will select first one if the last one currently selected
18045      var newRate = rates[0];
18046
18047      for (var i = 0; i < rates.length; i++) {
18048        if (rates[i] > currentRate) {
18049          newRate = rates[i];
18050          break;
18051        }
18052      }
18053      this.player().playbackRate(newRate);
18054    };
18055
18056    /**
18057     * Get possible playback rates
18058     *
18059     * @return {Array}
18060     *         All possible playback rates
18061     */
18062
18063
18064    PlaybackRateMenuButton.prototype.playbackRates = function playbackRates() {
18065      return this.options_.playbackRates || this.options_.playerOptions && this.options_.playerOptions.playbackRates;
18066    };
18067
18068    /**
18069     * Get whether playback rates is supported by the tech
18070     * and an array of playback rates exists
18071     *
18072     * @return {boolean}
18073     *         Whether changing playback rate is supported
18074     */
18075
18076
18077    PlaybackRateMenuButton.prototype.playbackRateSupported = function playbackRateSupported() {
18078      return this.player().tech_ && this.player().tech_.featuresPlaybackRate && this.playbackRates() && this.playbackRates().length > 0;
18079    };
18080
18081    /**
18082     * Hide playback rate controls when they're no playback rate options to select
18083     *
18084     * @param {EventTarget~Event} [event]
18085     *        The event that caused this function to run.
18086     *
18087     * @listens Player#loadstart
18088     */
18089
18090
18091    PlaybackRateMenuButton.prototype.updateVisibility = function updateVisibility(event) {
18092      if (this.playbackRateSupported()) {
18093        this.removeClass('vjs-hidden');
18094      } else {
18095        this.addClass('vjs-hidden');
18096      }
18097    };
18098
18099    /**
18100     * Update button label when rate changed
18101     *
18102     * @param {EventTarget~Event} [event]
18103     *        The event that caused this function to run.
18104     *
18105     * @listens Player#ratechange
18106     */
18107
18108
18109    PlaybackRateMenuButton.prototype.updateLabel = function updateLabel(event) {
18110      if (this.playbackRateSupported()) {
18111        this.labelEl_.innerHTML = this.player().playbackRate() + 'x';
18112      }
18113    };
18114
18115    return PlaybackRateMenuButton;
18116  }(MenuButton);
18117
18118  /**
18119   * The text that should display over the `FullscreenToggle`s controls. Added for localization.
18120   *
18121   * @type {string}
18122   * @private
18123   */
18124
18125
18126  PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate';
18127
18128  Component.registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton);
18129
18130  /**
18131   * @file spacer.js
18132   */
18133
18134  /**
18135   * Just an empty spacer element that can be used as an append point for plugins, etc.
18136   * Also can be used to create space between elements when necessary.
18137   *
18138   * @extends Component
18139   */
18140
18141  var Spacer = function (_Component) {
18142    inherits(Spacer, _Component);
18143
18144    function Spacer() {
18145      classCallCheck(this, Spacer);
18146      return possibleConstructorReturn(this, _Component.apply(this, arguments));
18147    }
18148
18149    /**
18150     * Builds the default DOM `className`.
18151     *
18152     * @return {string}
18153     *         The DOM `className` for this object.
18154     */
18155    Spacer.prototype.buildCSSClass = function buildCSSClass() {
18156      return 'vjs-spacer ' + _Component.prototype.buildCSSClass.call(this);
18157    };
18158
18159    /**
18160     * Create the `Component`'s DOM element
18161     *
18162     * @return {Element}
18163     *         The element that was created.
18164     */
18165
18166
18167    Spacer.prototype.createEl = function createEl() {
18168      return _Component.prototype.createEl.call(this, 'div', {
18169        className: this.buildCSSClass()
18170      });
18171    };
18172
18173    return Spacer;
18174  }(Component);
18175
18176  Component.registerComponent('Spacer', Spacer);
18177
18178  /**
18179   * @file custom-control-spacer.js
18180   */
18181
18182  /**
18183   * Spacer specifically meant to be used as an insertion point for new plugins, etc.
18184   *
18185   * @extends Spacer
18186   */
18187
18188  var CustomControlSpacer = function (_Spacer) {
18189    inherits(CustomControlSpacer, _Spacer);
18190
18191    function CustomControlSpacer() {
18192      classCallCheck(this, CustomControlSpacer);
18193      return possibleConstructorReturn(this, _Spacer.apply(this, arguments));
18194    }
18195
18196    /**
18197     * Builds the default DOM `className`.
18198     *
18199     * @return {string}
18200     *         The DOM `className` for this object.
18201     */
18202    CustomControlSpacer.prototype.buildCSSClass = function buildCSSClass() {
18203      return 'vjs-custom-control-spacer ' + _Spacer.prototype.buildCSSClass.call(this);
18204    };
18205
18206    /**
18207     * Create the `Component`'s DOM element
18208     *
18209     * @return {Element}
18210     *         The element that was created.
18211     */
18212
18213
18214    CustomControlSpacer.prototype.createEl = function createEl() {
18215      var el = _Spacer.prototype.createEl.call(this, {
18216        className: this.buildCSSClass()
18217      });
18218
18219      // No-flex/table-cell mode requires there be some content
18220      // in the cell to fill the remaining space of the table.
18221      el.innerHTML = '\xA0';
18222      return el;
18223    };
18224
18225    return CustomControlSpacer;
18226  }(Spacer);
18227
18228  Component.registerComponent('CustomControlSpacer', CustomControlSpacer);
18229
18230  /**
18231   * @file control-bar.js
18232   */
18233
18234  /**
18235   * Container of main controls.
18236   *
18237   * @extends Component
18238   */
18239
18240  var ControlBar = function (_Component) {
18241    inherits(ControlBar, _Component);
18242
18243    function ControlBar() {
18244      classCallCheck(this, ControlBar);
18245      return possibleConstructorReturn(this, _Component.apply(this, arguments));
18246    }
18247
18248    /**
18249     * Create the `Component`'s DOM element
18250     *
18251     * @return {Element}
18252     *         The element that was created.
18253     */
18254    ControlBar.prototype.createEl = function createEl() {
18255      return _Component.prototype.createEl.call(this, 'div', {
18256        className: 'vjs-control-bar',
18257        dir: 'ltr'
18258      });
18259    };
18260
18261    return ControlBar;
18262  }(Component);
18263
18264  /**
18265   * Default options for `ControlBar`
18266   *
18267   * @type {Object}
18268   * @private
18269   */
18270
18271
18272  ControlBar.prototype.options_ = {
18273    children: ['playToggle', 'volumePanel', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'descriptionsButton', 'subsCapsButton', 'audioTrackButton', 'fullscreenToggle']
18274  };
18275
18276  Component.registerComponent('ControlBar', ControlBar);
18277
18278  /**
18279   * @file error-display.js
18280   */
18281
18282  /**
18283   * A display that indicates an error has occurred. This means that the video
18284   * is unplayable.
18285   *
18286   * @extends ModalDialog
18287   */
18288
18289  var ErrorDisplay = function (_ModalDialog) {
18290    inherits(ErrorDisplay, _ModalDialog);
18291
18292    /**
18293     * Creates an instance of this class.
18294     *
18295     * @param  {Player} player
18296     *         The `Player` that this class should be attached to.
18297     *
18298     * @param  {Object} [options]
18299     *         The key/value store of player options.
18300     */
18301    function ErrorDisplay(player, options) {
18302      classCallCheck(this, ErrorDisplay);
18303
18304      var _this = possibleConstructorReturn(this, _ModalDialog.call(this, player, options));
18305
18306      _this.on(player, 'error', _this.open);
18307      return _this;
18308    }
18309
18310    /**
18311     * Builds the default DOM `className`.
18312     *
18313     * @return {string}
18314     *         The DOM `className` for this object.
18315     *
18316     * @deprecated Since version 5.
18317     */
18318
18319
18320    ErrorDisplay.prototype.buildCSSClass = function buildCSSClass() {
18321      return 'vjs-error-display ' + _ModalDialog.prototype.buildCSSClass.call(this);
18322    };
18323
18324    /**
18325     * Gets the localized error message based on the `Player`s error.
18326     *
18327     * @return {string}
18328     *         The `Player`s error message localized or an empty string.
18329     */
18330
18331
18332    ErrorDisplay.prototype.content = function content() {
18333      var error = this.player().error();
18334
18335      return error ? this.localize(error.message) : '';
18336    };
18337
18338    return ErrorDisplay;
18339  }(ModalDialog);
18340
18341  /**
18342   * The default options for an `ErrorDisplay`.
18343   *
18344   * @private
18345   */
18346
18347
18348  ErrorDisplay.prototype.options_ = mergeOptions(ModalDialog.prototype.options_, {
18349    pauseOnOpen: false,
18350    fillAlways: true,
18351    temporary: false,
18352    uncloseable: true
18353  });
18354
18355  Component.registerComponent('ErrorDisplay', ErrorDisplay);
18356
18357  /**
18358   * @file text-track-settings.js
18359   */
18360
18361  var LOCAL_STORAGE_KEY = 'vjs-text-track-settings';
18362
18363  var COLOR_BLACK = ['#000', 'Black'];
18364  var COLOR_BLUE = ['#00F', 'Blue'];
18365  var COLOR_CYAN = ['#0FF', 'Cyan'];
18366  var COLOR_GREEN = ['#0F0', 'Green'];
18367  var COLOR_MAGENTA = ['#F0F', 'Magenta'];
18368  var COLOR_RED = ['#F00', 'Red'];
18369  var COLOR_WHITE = ['#FFF', 'White'];
18370  var COLOR_YELLOW = ['#FF0', 'Yellow'];
18371
18372  var OPACITY_OPAQUE = ['1', 'Opaque'];
18373  var OPACITY_SEMI = ['0.5', 'Semi-Transparent'];
18374  var OPACITY_TRANS = ['0', 'Transparent'];
18375
18376  // Configuration for the various <select> elements in the DOM of this component.
18377  //
18378  // Possible keys include:
18379  //
18380  // `default`:
18381  //   The default option index. Only needs to be provided if not zero.
18382  // `parser`:
18383  //   A function which is used to parse the value from the selected option in
18384  //   a customized way.
18385  // `selector`:
18386  //   The selector used to find the associated <select> element.
18387  var selectConfigs = {
18388    backgroundColor: {
18389      selector: '.vjs-bg-color > select',
18390      id: 'captions-background-color-%s',
18391      label: 'Color',
18392      options: [COLOR_BLACK, COLOR_WHITE, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
18393    },
18394
18395    backgroundOpacity: {
18396      selector: '.vjs-bg-opacity > select',
18397      id: 'captions-background-opacity-%s',
18398      label: 'Transparency',
18399      options: [OPACITY_OPAQUE, OPACITY_SEMI, OPACITY_TRANS]
18400    },
18401
18402    color: {
18403      selector: '.vjs-fg-color > select',
18404      id: 'captions-foreground-color-%s',
18405      label: 'Color',
18406      options: [COLOR_WHITE, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
18407    },
18408
18409    edgeStyle: {
18410      selector: '.vjs-edge-style > select',
18411      id: '%s',
18412      label: 'Text Edge Style',
18413      options: [['none', 'None'], ['raised', 'Raised'], ['depressed', 'Depressed'], ['uniform', 'Uniform'], ['dropshadow', 'Dropshadow']]
18414    },
18415
18416    fontFamily: {
18417      selector: '.vjs-font-family > select',
18418      id: 'captions-font-family-%s',
18419      label: 'Font Family',
18420      options: [['proportionalSansSerif', 'Proportional Sans-Serif'], ['monospaceSansSerif', 'Monospace Sans-Serif'], ['proportionalSerif', 'Proportional Serif'], ['monospaceSerif', 'Monospace Serif'], ['casual', 'Casual'], ['script', 'Script'], ['small-caps', 'Small Caps']]
18421    },
18422
18423    fontPercent: {
18424      selector: '.vjs-font-percent > select',
18425      id: 'captions-font-size-%s',
18426      label: 'Font Size',
18427      options: [['0.50', '50%'], ['0.75', '75%'], ['1.00', '100%'], ['1.25', '125%'], ['1.50', '150%'], ['1.75', '175%'], ['2.00', '200%'], ['3.00', '300%'], ['4.00', '400%']],
18428      default: 2,
18429      parser: function parser(v) {
18430        return v === '1.00' ? null : Number(v);
18431      }
18432    },
18433
18434    textOpacity: {
18435      selector: '.vjs-text-opacity > select',
18436      id: 'captions-foreground-opacity-%s',
18437      label: 'Transparency',
18438      options: [OPACITY_OPAQUE, OPACITY_SEMI]
18439    },
18440
18441    // Options for this object are defined below.
18442    windowColor: {
18443      selector: '.vjs-window-color > select',
18444      id: 'captions-window-color-%s',
18445      label: 'Color'
18446    },
18447
18448    // Options for this object are defined below.
18449    windowOpacity: {
18450      selector: '.vjs-window-opacity > select',
18451      id: 'captions-window-opacity-%s',
18452      label: 'Transparency',
18453      options: [OPACITY_TRANS, OPACITY_SEMI, OPACITY_OPAQUE]
18454    }
18455  };
18456
18457  selectConfigs.windowColor.options = selectConfigs.backgroundColor.options;
18458
18459  /**
18460   * Get the actual value of an option.
18461   *
18462   * @param  {string} value
18463   *         The value to get
18464   *
18465   * @param  {Function} [parser]
18466   *         Optional function to adjust the value.
18467   *
18468   * @return {Mixed}
18469   *         - Will be `undefined` if no value exists
18470   *         - Will be `undefined` if the given value is "none".
18471   *         - Will be the actual value otherwise.
18472   *
18473   * @private
18474   */
18475  function parseOptionValue(value, parser) {
18476    if (parser) {
18477      value = parser(value);
18478    }
18479
18480    if (value && value !== 'none') {
18481      return value;
18482    }
18483  }
18484
18485  /**
18486   * Gets the value of the selected <option> element within a <select> element.
18487   *
18488   * @param  {Element} el
18489   *         the element to look in
18490   *
18491   * @param  {Function} [parser]
18492   *         Optional function to adjust the value.
18493   *
18494   * @return {Mixed}
18495   *         - Will be `undefined` if no value exists
18496   *         - Will be `undefined` if the given value is "none".
18497   *         - Will be the actual value otherwise.
18498   *
18499   * @private
18500   */
18501  function getSelectedOptionValue(el, parser) {
18502    var value = el.options[el.options.selectedIndex].value;
18503
18504    return parseOptionValue(value, parser);
18505  }
18506
18507  /**
18508   * Sets the selected <option> element within a <select> element based on a
18509   * given value.
18510   *
18511   * @param {Element} el
18512   *        The element to look in.
18513   *
18514   * @param {string} value
18515   *        the property to look on.
18516   *
18517   * @param {Function} [parser]
18518   *        Optional function to adjust the value before comparing.
18519   *
18520   * @private
18521   */
18522  function setSelectedOption(el, value, parser) {
18523    if (!value) {
18524      return;
18525    }
18526
18527    for (var i = 0; i < el.options.length; i++) {
18528      if (parseOptionValue(el.options[i].value, parser) === value) {
18529        el.selectedIndex = i;
18530        break;
18531      }
18532    }
18533  }
18534
18535  /**
18536   * Manipulate Text Tracks settings.
18537   *
18538   * @extends ModalDialog
18539   */
18540
18541  var TextTrackSettings = function (_ModalDialog) {
18542    inherits(TextTrackSettings, _ModalDialog);
18543
18544    /**
18545     * Creates an instance of this class.
18546     *
18547     * @param {Player} player
18548     *         The `Player` that this class should be attached to.
18549     *
18550     * @param {Object} [options]
18551     *         The key/value store of player options.
18552     */
18553    function TextTrackSettings(player, options) {
18554      classCallCheck(this, TextTrackSettings);
18555
18556      options.temporary = false;
18557
18558      var _this = possibleConstructorReturn(this, _ModalDialog.call(this, player, options));
18559
18560      _this.updateDisplay = bind(_this, _this.updateDisplay);
18561
18562      // fill the modal and pretend we have opened it
18563      _this.fill();
18564      _this.hasBeenOpened_ = _this.hasBeenFilled_ = true;
18565
18566      _this.endDialog = createEl('p', {
18567        className: 'vjs-control-text',
18568        textContent: _this.localize('End of dialog window.')
18569      });
18570      _this.el().appendChild(_this.endDialog);
18571
18572      _this.setDefaults();
18573
18574      // Grab `persistTextTrackSettings` from the player options if not passed in child options
18575      if (options.persistTextTrackSettings === undefined) {
18576        _this.options_.persistTextTrackSettings = _this.options_.playerOptions.persistTextTrackSettings;
18577      }
18578
18579      _this.on(_this.$('.vjs-done-button'), 'click', function () {
18580        _this.saveSettings();
18581        _this.close();
18582      });
18583
18584      _this.on(_this.$('.vjs-default-button'), 'click', function () {
18585        _this.setDefaults();
18586        _this.updateDisplay();
18587      });
18588
18589      each(selectConfigs, function (config) {
18590        _this.on(_this.$(config.selector), 'change', _this.updateDisplay);
18591      });
18592
18593      if (_this.options_.persistTextTrackSettings) {
18594        _this.restoreSettings();
18595      }
18596      return _this;
18597    }
18598
18599    TextTrackSettings.prototype.dispose = function dispose() {
18600      this.endDialog = null;
18601
18602      _ModalDialog.prototype.dispose.call(this);
18603    };
18604
18605    /**
18606     * Create a <select> element with configured options.
18607     *
18608     * @param {string} key
18609     *        Configuration key to use during creation.
18610     *
18611     * @return {string}
18612     *         An HTML string.
18613     *
18614     * @private
18615     */
18616
18617
18618    TextTrackSettings.prototype.createElSelect_ = function createElSelect_(key) {
18619      var _this2 = this;
18620
18621      var legendId = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
18622      var type = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'label';
18623
18624      var config = selectConfigs[key];
18625      var id = config.id.replace('%s', this.id_);
18626      var selectLabelledbyIds = [legendId, id].join(' ').trim();
18627
18628      return ['<' + type + ' id="' + id + '" class="' + (type === 'label' ? 'vjs-label' : '') + '">', this.localize(config.label), '</' + type + '>', '<select aria-labelledby="' + selectLabelledbyIds + '">'].concat(config.options.map(function (o) {
18629        var optionId = id + '-' + o[1].replace(/\W+/g, '');
18630
18631        return ['<option id="' + optionId + '" value="' + o[0] + '" ', 'aria-labelledby="' + selectLabelledbyIds + ' ' + optionId + '">', _this2.localize(o[1]), '</option>'].join('');
18632      })).concat('</select>').join('');
18633    };
18634
18635    /**
18636     * Create foreground color element for the component
18637     *
18638     * @return {string}
18639     *         An HTML string.
18640     *
18641     * @private
18642     */
18643
18644
18645    TextTrackSettings.prototype.createElFgColor_ = function createElFgColor_() {
18646      var legendId = 'captions-text-legend-' + this.id_;
18647
18648      return ['<fieldset class="vjs-fg-color vjs-track-setting">', '<legend id="' + legendId + '">', this.localize('Text'), '</legend>', this.createElSelect_('color', legendId), '<span class="vjs-text-opacity vjs-opacity">', this.createElSelect_('textOpacity', legendId), '</span>', '</fieldset>'].join('');
18649    };
18650
18651    /**
18652     * Create background color element for the component
18653     *
18654     * @return {string}
18655     *         An HTML string.
18656     *
18657     * @private
18658     */
18659
18660
18661    TextTrackSettings.prototype.createElBgColor_ = function createElBgColor_() {
18662      var legendId = 'captions-background-' + this.id_;
18663
18664      return ['<fieldset class="vjs-bg-color vjs-track-setting">', '<legend id="' + legendId + '">', this.localize('Background'), '</legend>', this.createElSelect_('backgroundColor', legendId), '<span class="vjs-bg-opacity vjs-opacity">', this.createElSelect_('backgroundOpacity', legendId), '</span>', '</fieldset>'].join('');
18665    };
18666
18667    /**
18668     * Create window color element for the component
18669     *
18670     * @return {string}
18671     *         An HTML string.
18672     *
18673     * @private
18674     */
18675
18676
18677    TextTrackSettings.prototype.createElWinColor_ = function createElWinColor_() {
18678      var legendId = 'captions-window-' + this.id_;
18679
18680      return ['<fieldset class="vjs-window-color vjs-track-setting">', '<legend id="' + legendId + '">', this.localize('Window'), '</legend>', this.createElSelect_('windowColor', legendId), '<span class="vjs-window-opacity vjs-opacity">', this.createElSelect_('windowOpacity', legendId), '</span>', '</fieldset>'].join('');
18681    };
18682
18683    /**
18684     * Create color elements for the component
18685     *
18686     * @return {Element}
18687     *         The element that was created
18688     *
18689     * @private
18690     */
18691
18692
18693    TextTrackSettings.prototype.createElColors_ = function createElColors_() {
18694      return createEl('div', {
18695        className: 'vjs-track-settings-colors',
18696        innerHTML: [this.createElFgColor_(), this.createElBgColor_(), this.createElWinColor_()].join('')
18697      });
18698    };
18699
18700    /**
18701     * Create font elements for the component
18702     *
18703     * @return {Element}
18704     *         The element that was created.
18705     *
18706     * @private
18707     */
18708
18709
18710    TextTrackSettings.prototype.createElFont_ = function createElFont_() {
18711      return createEl('div', {
18712        className: 'vjs-track-settings-font',
18713        innerHTML: ['<fieldset class="vjs-font-percent vjs-track-setting">', this.createElSelect_('fontPercent', '', 'legend'), '</fieldset>', '<fieldset class="vjs-edge-style vjs-track-setting">', this.createElSelect_('edgeStyle', '', 'legend'), '</fieldset>', '<fieldset class="vjs-font-family vjs-track-setting">', this.createElSelect_('fontFamily', '', 'legend'), '</fieldset>'].join('')
18714      });
18715    };
18716
18717    /**
18718     * Create controls for the component
18719     *
18720     * @return {Element}
18721     *         The element that was created.
18722     *
18723     * @private
18724     */
18725
18726
18727    TextTrackSettings.prototype.createElControls_ = function createElControls_() {
18728      var defaultsDescription = this.localize('restore all settings to the default values');
18729
18730      return createEl('div', {
18731        className: 'vjs-track-settings-controls',
18732        innerHTML: ['<button class="vjs-default-button" title="' + defaultsDescription + '">', this.localize('Reset'), '<span class="vjs-control-text"> ' + defaultsDescription + '</span>', '</button>', '<button class="vjs-done-button">' + this.localize('Done') + '</button>'].join('')
18733      });
18734    };
18735
18736    TextTrackSettings.prototype.content = function content() {
18737      return [this.createElColors_(), this.createElFont_(), this.createElControls_()];
18738    };
18739
18740    TextTrackSettings.prototype.label = function label() {
18741      return this.localize('Caption Settings Dialog');
18742    };
18743
18744    TextTrackSettings.prototype.description = function description() {
18745      return this.localize('Beginning of dialog window. Escape will cancel and close the window.');
18746    };
18747
18748    TextTrackSettings.prototype.buildCSSClass = function buildCSSClass() {
18749      return _ModalDialog.prototype.buildCSSClass.call(this) + ' vjs-text-track-settings';
18750    };
18751
18752    /**
18753     * Gets an object of text track settings (or null).
18754     *
18755     * @return {Object}
18756     *         An object with config values parsed from the DOM or localStorage.
18757     */
18758
18759
18760    TextTrackSettings.prototype.getValues = function getValues() {
18761      var _this3 = this;
18762
18763      return reduce(selectConfigs, function (accum, config, key) {
18764        var value = getSelectedOptionValue(_this3.$(config.selector), config.parser);
18765
18766        if (value !== undefined) {
18767          accum[key] = value;
18768        }
18769
18770        return accum;
18771      }, {});
18772    };
18773
18774    /**
18775     * Sets text track settings from an object of values.
18776     *
18777     * @param {Object} values
18778     *        An object with config values parsed from the DOM or localStorage.
18779     */
18780
18781
18782    TextTrackSettings.prototype.setValues = function setValues(values) {
18783      var _this4 = this;
18784
18785      each(selectConfigs, function (config, key) {
18786        setSelectedOption(_this4.$(config.selector), values[key], config.parser);
18787      });
18788    };
18789
18790    /**
18791     * Sets all `<select>` elements to their default values.
18792     */
18793
18794
18795    TextTrackSettings.prototype.setDefaults = function setDefaults() {
18796      var _this5 = this;
18797
18798      each(selectConfigs, function (config) {
18799        var index = config.hasOwnProperty('default') ? config.default : 0;
18800
18801        _this5.$(config.selector).selectedIndex = index;
18802      });
18803    };
18804
18805    /**
18806     * Restore texttrack settings from localStorage
18807     */
18808
18809
18810    TextTrackSettings.prototype.restoreSettings = function restoreSettings() {
18811      var values = void 0;
18812
18813      try {
18814        values = JSON.parse(window_1.localStorage.getItem(LOCAL_STORAGE_KEY));
18815      } catch (err) {
18816        log$1.warn(err);
18817      }
18818
18819      if (values) {
18820        this.setValues(values);
18821      }
18822    };
18823
18824    /**
18825     * Save text track settings to localStorage
18826     */
18827
18828
18829    TextTrackSettings.prototype.saveSettings = function saveSettings() {
18830      if (!this.options_.persistTextTrackSettings) {
18831        return;
18832      }
18833
18834      var values = this.getValues();
18835
18836      try {
18837        if (Object.keys(values).length) {
18838          window_1.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(values));
18839        } else {
18840          window_1.localStorage.removeItem(LOCAL_STORAGE_KEY);
18841        }
18842      } catch (err) {
18843        log$1.warn(err);
18844      }
18845    };
18846
18847    /**
18848     * Update display of text track settings
18849     */
18850
18851
18852    TextTrackSettings.prototype.updateDisplay = function updateDisplay() {
18853      var ttDisplay = this.player_.getChild('textTrackDisplay');
18854
18855      if (ttDisplay) {
18856        ttDisplay.updateDisplay();
18857      }
18858    };
18859
18860    /**
18861     * conditionally blur the element and refocus the captions button
18862     *
18863     * @private
18864     */
18865
18866
18867    TextTrackSettings.prototype.conditionalBlur_ = function conditionalBlur_() {
18868      this.previouslyActiveEl_ = null;
18869      this.off(document_1, 'keydown', this.handleKeyDown);
18870
18871      var cb = this.player_.controlBar;
18872      var subsCapsBtn = cb && cb.subsCapsButton;
18873      var ccBtn = cb && cb.captionsButton;
18874
18875      if (subsCapsBtn) {
18876        subsCapsBtn.focus();
18877      } else if (ccBtn) {
18878        ccBtn.focus();
18879      }
18880    };
18881
18882    return TextTrackSettings;
18883  }(ModalDialog);
18884
18885  Component.registerComponent('TextTrackSettings', TextTrackSettings);
18886
18887  /**
18888   * @file resize-manager.js
18889   */
18890
18891  /**
18892   * A Resize Manager. It is in charge of triggering `playerresize` on the player in the right conditions.
18893   *
18894   * It'll either create an iframe and use a debounced resize handler on it or use the new {@link https://wicg.github.io/ResizeObserver/|ResizeObserver}.
18895   *
18896   * If the ResizeObserver is available natively, it will be used. A polyfill can be passed in as an option.
18897   * If a `playerresize` event is not needed, the ResizeManager component can be removed from the player, see the example below.
18898   * @example <caption>How to disable the resize manager</caption>
18899   * const player = videojs('#vid', {
18900   *   resizeManager: false
18901   * });
18902   *
18903   * @see {@link https://wicg.github.io/ResizeObserver/|ResizeObserver specification}
18904   *
18905   * @extends Component
18906   */
18907
18908  var ResizeManager = function (_Component) {
18909    inherits(ResizeManager, _Component);
18910
18911    /**
18912     * Create the ResizeManager.
18913     *
18914     * @param {Object} player
18915     *        The `Player` that this class should be attached to.
18916     *
18917     * @param {Object} [options]
18918     *        The key/value store of ResizeManager options.
18919     *
18920     * @param {Object} [options.ResizeObserver]
18921     *        A polyfill for ResizeObserver can be passed in here.
18922     *        If this is set to null it will ignore the native ResizeObserver and fall back to the iframe fallback.
18923     */
18924    function ResizeManager(player, options) {
18925      classCallCheck(this, ResizeManager);
18926
18927      var RESIZE_OBSERVER_AVAILABLE = options.ResizeObserver || window_1.ResizeObserver;
18928
18929      // if `null` was passed, we want to disable the ResizeObserver
18930      if (options.ResizeObserver === null) {
18931        RESIZE_OBSERVER_AVAILABLE = false;
18932      }
18933
18934      // Only create an element when ResizeObserver isn't available
18935      var options_ = mergeOptions({ createEl: !RESIZE_OBSERVER_AVAILABLE }, options);
18936
18937      var _this = possibleConstructorReturn(this, _Component.call(this, player, options_));
18938
18939      _this.ResizeObserver = options.ResizeObserver || window_1.ResizeObserver;
18940      _this.loadListener_ = null;
18941      _this.resizeObserver_ = null;
18942      _this.debouncedHandler_ = debounce(function () {
18943        _this.resizeHandler();
18944      }, 100, false, player);
18945
18946      if (RESIZE_OBSERVER_AVAILABLE) {
18947        _this.resizeObserver_ = new _this.ResizeObserver(_this.debouncedHandler_);
18948        _this.resizeObserver_.observe(player.el());
18949      } else {
18950        _this.loadListener_ = function () {
18951          if (_this.el_.contentWindow) {
18952            on(_this.el_.contentWindow, 'resize', _this.debouncedHandler_);
18953          }
18954          _this.off('load', _this.loadListener_);
18955        };
18956
18957        _this.on('load', _this.loadListener_);
18958      }
18959      return _this;
18960    }
18961
18962    ResizeManager.prototype.createEl = function createEl() {
18963      return _Component.prototype.createEl.call(this, 'iframe', {
18964        className: 'vjs-resize-manager'
18965      });
18966    };
18967
18968    /**
18969     * Called when a resize is triggered on the iframe or a resize is observed via the ResizeObserver
18970     *
18971     * @fires Player#playerresize
18972     */
18973
18974
18975    ResizeManager.prototype.resizeHandler = function resizeHandler() {
18976      /**
18977       * Called when the player size has changed
18978       *
18979       * @event Player#playerresize
18980       * @type {EventTarget~Event}
18981       */
18982      this.player_.trigger('playerresize');
18983    };
18984
18985    ResizeManager.prototype.dispose = function dispose() {
18986      if (this.resizeObserver_) {
18987        if (this.player_.el()) {
18988          this.resizeObserver_.unobserve(this.player_.el());
18989        }
18990        this.resizeObserver_.disconnect();
18991      }
18992
18993      if (this.el_ && this.el_.contentWindow) {
18994        off(this.el_.contentWindow, 'resize', this.debouncedHandler_);
18995      }
18996
18997      if (this.loadListener_) {
18998        this.off('load', this.loadListener_);
18999      }
19000
19001      this.ResizeObserver = null;
19002      this.resizeObserver = null;
19003      this.debouncedHandler_ = null;
19004      this.loadListener_ = null;
19005    };
19006
19007    return ResizeManager;
19008  }(Component);
19009
19010  Component.registerComponent('ResizeManager', ResizeManager);
19011
19012  /**
19013   * This function is used to fire a sourceset when there is something
19014   * similar to `mediaEl.load()` being called. It will try to find the source via
19015   * the `src` attribute and then the `<source>` elements. It will then fire `sourceset`
19016   * with the source that was found or empty string if we cannot know. If it cannot
19017   * find a source then `sourceset` will not be fired.
19018   *
19019   * @param {Html5} tech
19020   *        The tech object that sourceset was setup on
19021   *
19022   * @return {boolean}
19023   *         returns false if the sourceset was not fired and true otherwise.
19024   */
19025  var sourcesetLoad = function sourcesetLoad(tech) {
19026    var el = tech.el();
19027
19028    // if `el.src` is set, that source will be loaded.
19029    if (el.hasAttribute('src')) {
19030      tech.triggerSourceset(el.src);
19031      return true;
19032    }
19033
19034    /**
19035     * Since there isn't a src property on the media element, source elements will be used for
19036     * implementing the source selection algorithm. This happens asynchronously and
19037     * for most cases were there is more than one source we cannot tell what source will
19038     * be loaded, without re-implementing the source selection algorithm. At this time we are not
19039     * going to do that. There are three special cases that we do handle here though:
19040     *
19041     * 1. If there are no sources, do not fire `sourceset`.
19042     * 2. If there is only one `<source>` with a `src` property/attribute that is our `src`
19043     * 3. If there is more than one `<source>` but all of them have the same `src` url.
19044     *    That will be our src.
19045     */
19046    var sources = tech.$$('source');
19047    var srcUrls = [];
19048    var src = '';
19049
19050    // if there are no sources, do not fire sourceset
19051    if (!sources.length) {
19052      return false;
19053    }
19054
19055    // only count valid/non-duplicate source elements
19056    for (var i = 0; i < sources.length; i++) {
19057      var url = sources[i].src;
19058
19059      if (url && srcUrls.indexOf(url) === -1) {
19060        srcUrls.push(url);
19061      }
19062    }
19063
19064    // there were no valid sources
19065    if (!srcUrls.length) {
19066      return false;
19067    }
19068
19069    // there is only one valid source element url
19070    // use that
19071    if (srcUrls.length === 1) {
19072      src = srcUrls[0];
19073    }
19074
19075    tech.triggerSourceset(src);
19076    return true;
19077  };
19078
19079  /**
19080   * our implementation of an `innerHTML` descriptor for browsers
19081   * that do not have one.
19082   */
19083  var innerHTMLDescriptorPolyfill = Object.defineProperty({}, 'innerHTML', {
19084    get: function get() {
19085      return this.cloneNode(true).innerHTML;
19086    },
19087    set: function set(v) {
19088      // make a dummy node to use innerHTML on
19089      var dummy = document_1.createElement(this.nodeName.toLowerCase());
19090
19091      // set innerHTML to the value provided
19092      dummy.innerHTML = v;
19093
19094      // make a document fragment to hold the nodes from dummy
19095      var docFrag = document_1.createDocumentFragment();
19096
19097      // copy all of the nodes created by the innerHTML on dummy
19098      // to the document fragment
19099      while (dummy.childNodes.length) {
19100        docFrag.appendChild(dummy.childNodes[0]);
19101      }
19102
19103      // remove content
19104      this.innerText = '';
19105
19106      // now we add all of that html in one by appending the
19107      // document fragment. This is how innerHTML does it.
19108      window_1.Element.prototype.appendChild.call(this, docFrag);
19109
19110      // then return the result that innerHTML's setter would
19111      return this.innerHTML;
19112    }
19113  });
19114
19115  /**
19116   * Get a property descriptor given a list of priorities and the
19117   * property to get.
19118   */
19119  var getDescriptor = function getDescriptor(priority, prop) {
19120    var descriptor = {};
19121
19122    for (var i = 0; i < priority.length; i++) {
19123      descriptor = Object.getOwnPropertyDescriptor(priority[i], prop);
19124
19125      if (descriptor && descriptor.set && descriptor.get) {
19126        break;
19127      }
19128    }
19129
19130    descriptor.enumerable = true;
19131    descriptor.configurable = true;
19132
19133    return descriptor;
19134  };
19135
19136  var getInnerHTMLDescriptor = function getInnerHTMLDescriptor(tech) {
19137    return getDescriptor([tech.el(), window_1.HTMLMediaElement.prototype, window_1.Element.prototype, innerHTMLDescriptorPolyfill], 'innerHTML');
19138  };
19139
19140  /**
19141   * Patches browser internal functions so that we can tell synchronously
19142   * if a `<source>` was appended to the media element. For some reason this
19143   * causes a `sourceset` if the the media element is ready and has no source.
19144   * This happens when:
19145   * - The page has just loaded and the media element does not have a source.
19146   * - The media element was emptied of all sources, then `load()` was called.
19147   *
19148   * It does this by patching the following functions/properties when they are supported:
19149   *
19150   * - `append()` - can be used to add a `<source>` element to the media element
19151   * - `appendChild()` - can be used to add a `<source>` element to the media element
19152   * - `insertAdjacentHTML()` -  can be used to add a `<source>` element to the media element
19153   * - `innerHTML` -  can be used to add a `<source>` element to the media element
19154   *
19155   * @param {Html5} tech
19156   *        The tech object that sourceset is being setup on.
19157   */
19158  var firstSourceWatch = function firstSourceWatch(tech) {
19159    var el = tech.el();
19160
19161    // make sure firstSourceWatch isn't setup twice.
19162    if (el.resetSourceWatch_) {
19163      return;
19164    }
19165
19166    var old = {};
19167    var innerDescriptor = getInnerHTMLDescriptor(tech);
19168    var appendWrapper = function appendWrapper(appendFn) {
19169      return function () {
19170        for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
19171          args[_key] = arguments[_key];
19172        }
19173
19174        var retval = appendFn.apply(el, args);
19175
19176        sourcesetLoad(tech);
19177
19178        return retval;
19179      };
19180    };
19181
19182    ['append', 'appendChild', 'insertAdjacentHTML'].forEach(function (k) {
19183      if (!el[k]) {
19184        return;
19185      }
19186
19187      // store the old function
19188      old[k] = el[k];
19189
19190      // call the old function with a sourceset if a source
19191      // was loaded
19192      el[k] = appendWrapper(old[k]);
19193    });
19194
19195    Object.defineProperty(el, 'innerHTML', mergeOptions(innerDescriptor, {
19196      set: appendWrapper(innerDescriptor.set)
19197    }));
19198
19199    el.resetSourceWatch_ = function () {
19200      el.resetSourceWatch_ = null;
19201      Object.keys(old).forEach(function (k) {
19202        el[k] = old[k];
19203      });
19204
19205      Object.defineProperty(el, 'innerHTML', innerDescriptor);
19206    };
19207
19208    // on the first sourceset, we need to revert our changes
19209    tech.one('sourceset', el.resetSourceWatch_);
19210  };
19211
19212  /**
19213   * our implementation of a `src` descriptor for browsers
19214   * that do not have one.
19215   */
19216  var srcDescriptorPolyfill = Object.defineProperty({}, 'src', {
19217    get: function get() {
19218      if (this.hasAttribute('src')) {
19219        return getAbsoluteURL(window_1.Element.prototype.getAttribute.call(this, 'src'));
19220      }
19221
19222      return '';
19223    },
19224    set: function set(v) {
19225      window_1.Element.prototype.setAttribute.call(this, 'src', v);
19226
19227      return v;
19228    }
19229  });
19230
19231  var getSrcDescriptor = function getSrcDescriptor(tech) {
19232    return getDescriptor([tech.el(), window_1.HTMLMediaElement.prototype, srcDescriptorPolyfill], 'src');
19233  };
19234
19235  /**
19236   * setup `sourceset` handling on the `Html5` tech. This function
19237   * patches the following element properties/functions:
19238   *
19239   * - `src` - to determine when `src` is set
19240   * - `setAttribute()` - to determine when `src` is set
19241   * - `load()` - this re-triggers the source selection algorithm, and can
19242   *              cause a sourceset.
19243   *
19244   * If there is no source when we are adding `sourceset` support or during a `load()`
19245   * we also patch the functions listed in `firstSourceWatch`.
19246   *
19247   * @param {Html5} tech
19248   *        The tech to patch
19249   */
19250  var setupSourceset = function setupSourceset(tech) {
19251    if (!tech.featuresSourceset) {
19252      return;
19253    }
19254
19255    var el = tech.el();
19256
19257    // make sure sourceset isn't setup twice.
19258    if (el.resetSourceset_) {
19259      return;
19260    }
19261
19262    var srcDescriptor = getSrcDescriptor(tech);
19263    var oldSetAttribute = el.setAttribute;
19264    var oldLoad = el.load;
19265
19266    Object.defineProperty(el, 'src', mergeOptions(srcDescriptor, {
19267      set: function set(v) {
19268        var retval = srcDescriptor.set.call(el, v);
19269
19270        // we use the getter here to get the actual value set on src
19271        tech.triggerSourceset(el.src);
19272
19273        return retval;
19274      }
19275    }));
19276
19277    el.setAttribute = function (n, v) {
19278      var retval = oldSetAttribute.call(el, n, v);
19279
19280      if (/src/i.test(n)) {
19281        tech.triggerSourceset(el.src);
19282      }
19283
19284      return retval;
19285    };
19286
19287    el.load = function () {
19288      var retval = oldLoad.call(el);
19289
19290      // if load was called, but there was no source to fire
19291      // sourceset on. We have to watch for a source append
19292      // as that can trigger a `sourceset` when the media element
19293      // has no source
19294      if (!sourcesetLoad(tech)) {
19295        tech.triggerSourceset('');
19296        firstSourceWatch(tech);
19297      }
19298
19299      return retval;
19300    };
19301
19302    if (el.currentSrc) {
19303      tech.triggerSourceset(el.currentSrc);
19304    } else if (!sourcesetLoad(tech)) {
19305      firstSourceWatch(tech);
19306    }
19307
19308    el.resetSourceset_ = function () {
19309      el.resetSourceset_ = null;
19310      el.load = oldLoad;
19311      el.setAttribute = oldSetAttribute;
19312      Object.defineProperty(el, 'src', srcDescriptor);
19313      if (el.resetSourceWatch_) {
19314        el.resetSourceWatch_();
19315      }
19316    };
19317  };
19318
19319  var _templateObject$1 = taggedTemplateLiteralLoose(['Text Tracks are being loaded from another origin but the crossorigin attribute isn\'t used.\n            This may prevent text tracks from loading.'], ['Text Tracks are being loaded from another origin but the crossorigin attribute isn\'t used.\n            This may prevent text tracks from loading.']);
19320
19321  /**
19322   * HTML5 Media Controller - Wrapper for HTML5 Media API
19323   *
19324   * @mixes Tech~SourceHandlerAdditions
19325   * @extends Tech
19326   */
19327
19328  var Html5 = function (_Tech) {
19329    inherits(Html5, _Tech);
19330
19331    /**
19332     * Create an instance of this Tech.
19333     *
19334     * @param {Object} [options]
19335     *        The key/value store of player options.
19336     *
19337     * @param {Component~ReadyCallback} ready
19338     *        Callback function to call when the `HTML5` Tech is ready.
19339     */
19340    function Html5(options, ready) {
19341      classCallCheck(this, Html5);
19342
19343      var _this = possibleConstructorReturn(this, _Tech.call(this, options, ready));
19344
19345      var source = options.source;
19346      var crossoriginTracks = false;
19347
19348      // Set the source if one is provided
19349      // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted)
19350      // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source
19351      // anyway so the error gets fired.
19352      if (source && (_this.el_.currentSrc !== source.src || options.tag && options.tag.initNetworkState_ === 3)) {
19353        _this.setSource(source);
19354      } else {
19355        _this.handleLateInit_(_this.el_);
19356      }
19357
19358      // setup sourceset after late sourceset/init
19359      if (options.enableSourceset) {
19360        _this.setupSourcesetHandling_();
19361      }
19362
19363      if (_this.el_.hasChildNodes()) {
19364
19365        var nodes = _this.el_.childNodes;
19366        var nodesLength = nodes.length;
19367        var removeNodes = [];
19368
19369        while (nodesLength--) {
19370          var node = nodes[nodesLength];
19371          var nodeName = node.nodeName.toLowerCase();
19372
19373          if (nodeName === 'track') {
19374            if (!_this.featuresNativeTextTracks) {
19375              // Empty video tag tracks so the built-in player doesn't use them also.
19376              // This may not be fast enough to stop HTML5 browsers from reading the tags
19377              // so we'll need to turn off any default tracks if we're manually doing
19378              // captions and subtitles. videoElement.textTracks
19379              removeNodes.push(node);
19380            } else {
19381              // store HTMLTrackElement and TextTrack to remote list
19382              _this.remoteTextTrackEls().addTrackElement_(node);
19383              _this.remoteTextTracks().addTrack(node.track);
19384              _this.textTracks().addTrack(node.track);
19385              if (!crossoriginTracks && !_this.el_.hasAttribute('crossorigin') && isCrossOrigin(node.src)) {
19386                crossoriginTracks = true;
19387              }
19388            }
19389          }
19390        }
19391
19392        for (var i = 0; i < removeNodes.length; i++) {
19393          _this.el_.removeChild(removeNodes[i]);
19394        }
19395      }
19396
19397      _this.proxyNativeTracks_();
19398      if (_this.featuresNativeTextTracks && crossoriginTracks) {
19399        log$1.warn(tsml(_templateObject$1));
19400      }
19401
19402      // prevent iOS Safari from disabling metadata text tracks during native playback
19403      _this.restoreMetadataTracksInIOSNativePlayer_();
19404
19405      // Determine if native controls should be used
19406      // Our goal should be to get the custom controls on mobile solid everywhere
19407      // so we can remove this all together. Right now this will block custom
19408      // controls on touch enabled laptops like the Chrome Pixel
19409      if ((TOUCH_ENABLED || IS_IPHONE || IS_NATIVE_ANDROID) && options.nativeControlsForTouch === true) {
19410        _this.setControls(true);
19411      }
19412
19413      // on iOS, we want to proxy `webkitbeginfullscreen` and `webkitendfullscreen`
19414      // into a `fullscreenchange` event
19415      _this.proxyWebkitFullscreen_();
19416
19417      _this.triggerReady();
19418      return _this;
19419    }
19420
19421    /**
19422     * Dispose of `HTML5` media element and remove all tracks.
19423     */
19424
19425
19426    Html5.prototype.dispose = function dispose() {
19427      if (this.el_ && this.el_.resetSourceset_) {
19428        this.el_.resetSourceset_();
19429      }
19430      Html5.disposeMediaElement(this.el_);
19431      this.options_ = null;
19432
19433      // tech will handle clearing of the emulated track list
19434      _Tech.prototype.dispose.call(this);
19435    };
19436
19437    /**
19438     * Modify the media element so that we can detect when
19439     * the source is changed. Fires `sourceset` just after the source has changed
19440     */
19441
19442
19443    Html5.prototype.setupSourcesetHandling_ = function setupSourcesetHandling_() {
19444      setupSourceset(this);
19445    };
19446
19447    /**
19448     * When a captions track is enabled in the iOS Safari native player, all other
19449     * tracks are disabled (including metadata tracks), which nulls all of their
19450     * associated cue points. This will restore metadata tracks to their pre-fullscreen
19451     * state in those cases so that cue points are not needlessly lost.
19452     *
19453     * @private
19454     */
19455
19456
19457    Html5.prototype.restoreMetadataTracksInIOSNativePlayer_ = function restoreMetadataTracksInIOSNativePlayer_() {
19458      var textTracks = this.textTracks();
19459      var metadataTracksPreFullscreenState = void 0;
19460
19461      // captures a snapshot of every metadata track's current state
19462      var takeMetadataTrackSnapshot = function takeMetadataTrackSnapshot() {
19463        metadataTracksPreFullscreenState = [];
19464
19465        for (var i = 0; i < textTracks.length; i++) {
19466          var track = textTracks[i];
19467
19468          if (track.kind === 'metadata') {
19469            metadataTracksPreFullscreenState.push({
19470              track: track,
19471              storedMode: track.mode
19472            });
19473          }
19474        }
19475      };
19476
19477      // snapshot each metadata track's initial state, and update the snapshot
19478      // each time there is a track 'change' event
19479      takeMetadataTrackSnapshot();
19480      textTracks.addEventListener('change', takeMetadataTrackSnapshot);
19481
19482      this.on('dispose', function () {
19483        return textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
19484      });
19485
19486      var restoreTrackMode = function restoreTrackMode() {
19487        for (var i = 0; i < metadataTracksPreFullscreenState.length; i++) {
19488          var storedTrack = metadataTracksPreFullscreenState[i];
19489
19490          if (storedTrack.track.mode === 'disabled' && storedTrack.track.mode !== storedTrack.storedMode) {
19491            storedTrack.track.mode = storedTrack.storedMode;
19492          }
19493        }
19494        // we only want this handler to be executed on the first 'change' event
19495        textTracks.removeEventListener('change', restoreTrackMode);
19496      };
19497
19498      // when we enter fullscreen playback, stop updating the snapshot and
19499      // restore all track modes to their pre-fullscreen state
19500      this.on('webkitbeginfullscreen', function () {
19501        textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
19502
19503        // remove the listener before adding it just in case it wasn't previously removed
19504        textTracks.removeEventListener('change', restoreTrackMode);
19505        textTracks.addEventListener('change', restoreTrackMode);
19506      });
19507
19508      // start updating the snapshot again after leaving fullscreen
19509      this.on('webkitendfullscreen', function () {
19510        // remove the listener before adding it just in case it wasn't previously removed
19511        textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
19512        textTracks.addEventListener('change', takeMetadataTrackSnapshot);
19513
19514        // remove the restoreTrackMode handler in case it wasn't triggered during fullscreen playback
19515        textTracks.removeEventListener('change', restoreTrackMode);
19516      });
19517    };
19518
19519    /**
19520     * Attempt to force override of tracks for the given type
19521     *
19522     * @param {String} type - Track type to override, possible values include 'Audio',
19523     * 'Video', and 'Text'.
19524     * @param {Boolean} override - If set to true native audio/video will be overridden,
19525     * otherwise native audio/video will potentially be used.
19526     * @private
19527     */
19528
19529
19530    Html5.prototype.overrideNative_ = function overrideNative_(type, override) {
19531      var _this2 = this;
19532
19533      // If there is no behavioral change don't add/remove listeners
19534      if (override !== this['featuresNative' + type + 'Tracks']) {
19535        return;
19536      }
19537
19538      var lowerCaseType = type.toLowerCase();
19539
19540      if (this[lowerCaseType + 'TracksListeners_']) {
19541        Object.keys(this[lowerCaseType + 'TracksListeners_']).forEach(function (eventName) {
19542          var elTracks = _this2.el()[lowerCaseType + 'Tracks'];
19543
19544          elTracks.removeEventListener(eventName, _this2[lowerCaseType + 'TracksListeners_'][eventName]);
19545        });
19546      }
19547
19548      this['featuresNative' + type + 'Tracks'] = !override;
19549      this[lowerCaseType + 'TracksListeners_'] = null;
19550
19551      this.proxyNativeTracksForType_(lowerCaseType);
19552    };
19553
19554    /**
19555     * Attempt to force override of native audio tracks.
19556     *
19557     * @param {Boolean} override - If set to true native audio will be overridden,
19558     * otherwise native audio will potentially be used.
19559     */
19560
19561
19562    Html5.prototype.overrideNativeAudioTracks = function overrideNativeAudioTracks(override) {
19563      this.overrideNative_('Audio', override);
19564    };
19565
19566    /**
19567     * Attempt to force override of native video tracks.
19568     *
19569     * @param {Boolean} override - If set to true native video will be overridden,
19570     * otherwise native video will potentially be used.
19571     */
19572
19573
19574    Html5.prototype.overrideNativeVideoTracks = function overrideNativeVideoTracks(override) {
19575      this.overrideNative_('Video', override);
19576    };
19577
19578    /**
19579      * Proxy native track list events for the given type to our track
19580      * lists if the browser we are playing in supports that type of track list.
19581      *
19582      * @param {string} name - Track type; values include 'audio', 'video', and 'text'
19583      * @private
19584      */
19585
19586
19587    Html5.prototype.proxyNativeTracksForType_ = function proxyNativeTracksForType_(name) {
19588      var _this3 = this;
19589
19590      var props = NORMAL[name];
19591      var elTracks = this.el()[props.getterName];
19592      var techTracks = this[props.getterName]();
19593
19594      if (!this['featuresNative' + props.capitalName + 'Tracks'] || !elTracks || !elTracks.addEventListener) {
19595        return;
19596      }
19597      var listeners = {
19598        change: function change(e) {
19599          techTracks.trigger({
19600            type: 'change',
19601            target: techTracks,
19602            currentTarget: techTracks,
19603            srcElement: techTracks
19604          });
19605        },
19606        addtrack: function addtrack(e) {
19607          techTracks.addTrack(e.track);
19608        },
19609        removetrack: function removetrack(e) {
19610          techTracks.removeTrack(e.track);
19611        }
19612      };
19613      var removeOldTracks = function removeOldTracks() {
19614        var removeTracks = [];
19615
19616        for (var i = 0; i < techTracks.length; i++) {
19617          var found = false;
19618
19619          for (var j = 0; j < elTracks.length; j++) {
19620            if (elTracks[j] === techTracks[i]) {
19621              found = true;
19622              break;
19623            }
19624          }
19625
19626          if (!found) {
19627            removeTracks.push(techTracks[i]);
19628          }
19629        }
19630
19631        while (removeTracks.length) {
19632          techTracks.removeTrack(removeTracks.shift());
19633        }
19634      };
19635
19636      this[props.getterName + 'Listeners_'] = listeners;
19637
19638      Object.keys(listeners).forEach(function (eventName) {
19639        var listener = listeners[eventName];
19640
19641        elTracks.addEventListener(eventName, listener);
19642        _this3.on('dispose', function (e) {
19643          return elTracks.removeEventListener(eventName, listener);
19644        });
19645      });
19646
19647      // Remove (native) tracks that are not used anymore
19648      this.on('loadstart', removeOldTracks);
19649      this.on('dispose', function (e) {
19650        return _this3.off('loadstart', removeOldTracks);
19651      });
19652    };
19653
19654    /**
19655     * Proxy all native track list events to our track lists if the browser we are playing
19656     * in supports that type of track list.
19657     *
19658     * @private
19659     */
19660
19661
19662    Html5.prototype.proxyNativeTracks_ = function proxyNativeTracks_() {
19663      var _this4 = this;
19664
19665      NORMAL.names.forEach(function (name) {
19666        _this4.proxyNativeTracksForType_(name);
19667      });
19668    };
19669
19670    /**
19671     * Create the `Html5` Tech's DOM element.
19672     *
19673     * @return {Element}
19674     *         The element that gets created.
19675     */
19676
19677
19678    Html5.prototype.createEl = function createEl$$1() {
19679      var el = this.options_.tag;
19680
19681      // Check if this browser supports moving the element into the box.
19682      // On the iPhone video will break if you move the element,
19683      // So we have to create a brand new element.
19684      // If we ingested the player div, we do not need to move the media element.
19685      if (!el || !(this.options_.playerElIngest || this.movingMediaElementInDOM)) {
19686
19687        // If the original tag is still there, clone and remove it.
19688        if (el) {
19689          var clone = el.cloneNode(true);
19690
19691          if (el.parentNode) {
19692            el.parentNode.insertBefore(clone, el);
19693          }
19694          Html5.disposeMediaElement(el);
19695          el = clone;
19696        } else {
19697          el = document_1.createElement('video');
19698
19699          // determine if native controls should be used
19700          var tagAttributes = this.options_.tag && getAttributes(this.options_.tag);
19701          var attributes = mergeOptions({}, tagAttributes);
19702
19703          if (!TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) {
19704            delete attributes.controls;
19705          }
19706
19707          setAttributes(el, assign(attributes, {
19708            id: this.options_.techId,
19709            class: 'vjs-tech'
19710          }));
19711        }
19712
19713        el.playerId = this.options_.playerId;
19714      }
19715
19716      if (typeof this.options_.preload !== 'undefined') {
19717        setAttribute(el, 'preload', this.options_.preload);
19718      }
19719
19720      // Update specific tag settings, in case they were overridden
19721      // `autoplay` has to be *last* so that `muted` and `playsinline` are present
19722      // when iOS/Safari or other browsers attempt to autoplay.
19723      var settingsAttrs = ['loop', 'muted', 'playsinline', 'autoplay'];
19724
19725      for (var i = 0; i < settingsAttrs.length; i++) {
19726        var attr = settingsAttrs[i];
19727        var value = this.options_[attr];
19728
19729        if (typeof value !== 'undefined') {
19730          if (value) {
19731            setAttribute(el, attr, attr);
19732          } else {
19733            removeAttribute(el, attr);
19734          }
19735          el[attr] = value;
19736        }
19737      }
19738
19739      return el;
19740    };
19741
19742    /**
19743     * This will be triggered if the loadstart event has already fired, before videojs was
19744     * ready. Two known examples of when this can happen are:
19745     * 1. If we're loading the playback object after it has started loading
19746     * 2. The media is already playing the (often with autoplay on) then
19747     *
19748     * This function will fire another loadstart so that videojs can catchup.
19749     *
19750     * @fires Tech#loadstart
19751     *
19752     * @return {undefined}
19753     *         returns nothing.
19754     */
19755
19756
19757    Html5.prototype.handleLateInit_ = function handleLateInit_(el) {
19758      if (el.networkState === 0 || el.networkState === 3) {
19759        // The video element hasn't started loading the source yet
19760        // or didn't find a source
19761        return;
19762      }
19763
19764      if (el.readyState === 0) {
19765        // NetworkState is set synchronously BUT loadstart is fired at the
19766        // end of the current stack, usually before setInterval(fn, 0).
19767        // So at this point we know loadstart may have already fired or is
19768        // about to fire, and either way the player hasn't seen it yet.
19769        // We don't want to fire loadstart prematurely here and cause a
19770        // double loadstart so we'll wait and see if it happens between now
19771        // and the next loop, and fire it if not.
19772        // HOWEVER, we also want to make sure it fires before loadedmetadata
19773        // which could also happen between now and the next loop, so we'll
19774        // watch for that also.
19775        var loadstartFired = false;
19776        var setLoadstartFired = function setLoadstartFired() {
19777          loadstartFired = true;
19778        };
19779
19780        this.on('loadstart', setLoadstartFired);
19781
19782        var triggerLoadstart = function triggerLoadstart() {
19783          // We did miss the original loadstart. Make sure the player
19784          // sees loadstart before loadedmetadata
19785          if (!loadstartFired) {
19786            this.trigger('loadstart');
19787          }
19788        };
19789
19790        this.on('loadedmetadata', triggerLoadstart);
19791
19792        this.ready(function () {
19793          this.off('loadstart', setLoadstartFired);
19794          this.off('loadedmetadata', triggerLoadstart);
19795
19796          if (!loadstartFired) {
19797            // We did miss the original native loadstart. Fire it now.
19798            this.trigger('loadstart');
19799          }
19800        });
19801
19802        return;
19803      }
19804
19805      // From here on we know that loadstart already fired and we missed it.
19806      // The other readyState events aren't as much of a problem if we double
19807      // them, so not going to go to as much trouble as loadstart to prevent
19808      // that unless we find reason to.
19809      var eventsToTrigger = ['loadstart'];
19810
19811      // loadedmetadata: newly equal to HAVE_METADATA (1) or greater
19812      eventsToTrigger.push('loadedmetadata');
19813
19814      // loadeddata: newly increased to HAVE_CURRENT_DATA (2) or greater
19815      if (el.readyState >= 2) {
19816        eventsToTrigger.push('loadeddata');
19817      }
19818
19819      // canplay: newly increased to HAVE_FUTURE_DATA (3) or greater
19820      if (el.readyState >= 3) {
19821        eventsToTrigger.push('canplay');
19822      }
19823
19824      // canplaythrough: newly equal to HAVE_ENOUGH_DATA (4)
19825      if (el.readyState >= 4) {
19826        eventsToTrigger.push('canplaythrough');
19827      }
19828
19829      // We still need to give the player time to add event listeners
19830      this.ready(function () {
19831        eventsToTrigger.forEach(function (type) {
19832          this.trigger(type);
19833        }, this);
19834      });
19835    };
19836
19837    /**
19838     * Set current time for the `HTML5` tech.
19839     *
19840     * @param {number} seconds
19841     *        Set the current time of the media to this.
19842     */
19843
19844
19845    Html5.prototype.setCurrentTime = function setCurrentTime(seconds) {
19846      try {
19847        this.el_.currentTime = seconds;
19848      } catch (e) {
19849        log$1(e, 'Video is not ready. (Video.js)');
19850        // this.warning(VideoJS.warnings.videoNotReady);
19851      }
19852    };
19853
19854    /**
19855     * Get the current duration of the HTML5 media element.
19856     *
19857     * @return {number}
19858     *         The duration of the media or 0 if there is no duration.
19859     */
19860
19861
19862    Html5.prototype.duration = function duration() {
19863      var _this5 = this;
19864
19865      // Android Chrome will report duration as Infinity for VOD HLS until after
19866      // playback has started, which triggers the live display erroneously.
19867      // Return NaN if playback has not started and trigger a durationupdate once
19868      // the duration can be reliably known.
19869      if (this.el_.duration === Infinity && IS_ANDROID && IS_CHROME && this.el_.currentTime === 0) {
19870        // Wait for the first `timeupdate` with currentTime > 0 - there may be
19871        // several with 0
19872        var checkProgress = function checkProgress() {
19873          if (_this5.el_.currentTime > 0) {
19874            // Trigger durationchange for genuinely live video
19875            if (_this5.el_.duration === Infinity) {
19876              _this5.trigger('durationchange');
19877            }
19878            _this5.off('timeupdate', checkProgress);
19879          }
19880        };
19881
19882        this.on('timeupdate', checkProgress);
19883        return NaN;
19884      }
19885      return this.el_.duration || NaN;
19886    };
19887
19888    /**
19889     * Get the current width of the HTML5 media element.
19890     *
19891     * @return {number}
19892     *         The width of the HTML5 media element.
19893     */
19894
19895
19896    Html5.prototype.width = function width() {
19897      return this.el_.offsetWidth;
19898    };
19899
19900    /**
19901     * Get the current height of the HTML5 media element.
19902     *
19903     * @return {number}
19904     *         The height of the HTML5 media element.
19905     */
19906
19907
19908    Html5.prototype.height = function height() {
19909      return this.el_.offsetHeight;
19910    };
19911
19912    /**
19913     * Proxy iOS `webkitbeginfullscreen` and `webkitendfullscreen` into
19914     * `fullscreenchange` event.
19915     *
19916     * @private
19917     * @fires fullscreenchange
19918     * @listens webkitendfullscreen
19919     * @listens webkitbeginfullscreen
19920     * @listens webkitbeginfullscreen
19921     */
19922
19923
19924    Html5.prototype.proxyWebkitFullscreen_ = function proxyWebkitFullscreen_() {
19925      var _this6 = this;
19926
19927      if (!('webkitDisplayingFullscreen' in this.el_)) {
19928        return;
19929      }
19930
19931      var endFn = function endFn() {
19932        this.trigger('fullscreenchange', { isFullscreen: false });
19933      };
19934
19935      var beginFn = function beginFn() {
19936        if ('webkitPresentationMode' in this.el_ && this.el_.webkitPresentationMode !== 'picture-in-picture') {
19937          this.one('webkitendfullscreen', endFn);
19938
19939          this.trigger('fullscreenchange', { isFullscreen: true });
19940        }
19941      };
19942
19943      this.on('webkitbeginfullscreen', beginFn);
19944      this.on('dispose', function () {
19945        _this6.off('webkitbeginfullscreen', beginFn);
19946        _this6.off('webkitendfullscreen', endFn);
19947      });
19948    };
19949
19950    /**
19951     * Check if fullscreen is supported on the current playback device.
19952     *
19953     * @return {boolean}
19954     *         - True if fullscreen is supported.
19955     *         - False if fullscreen is not supported.
19956     */
19957
19958
19959    Html5.prototype.supportsFullScreen = function supportsFullScreen() {
19960      if (typeof this.el_.webkitEnterFullScreen === 'function') {
19961        var userAgent = window_1.navigator && window_1.navigator.userAgent || '';
19962
19963        // Seems to be broken in Chromium/Chrome && Safari in Leopard
19964        if (/Android/.test(userAgent) || !/Chrome|Mac OS X 10.5/.test(userAgent)) {
19965          return true;
19966        }
19967      }
19968      return false;
19969    };
19970
19971    /**
19972     * Request that the `HTML5` Tech enter fullscreen.
19973     */
19974
19975
19976    Html5.prototype.enterFullScreen = function enterFullScreen() {
19977      var video = this.el_;
19978
19979      if (video.paused && video.networkState <= video.HAVE_METADATA) {
19980        // attempt to prime the video element for programmatic access
19981        // this isn't necessary on the desktop but shouldn't hurt
19982        this.el_.play();
19983
19984        // playing and pausing synchronously during the transition to fullscreen
19985        // can get iOS ~6.1 devices into a play/pause loop
19986        this.setTimeout(function () {
19987          video.pause();
19988          video.webkitEnterFullScreen();
19989        }, 0);
19990      } else {
19991        video.webkitEnterFullScreen();
19992      }
19993    };
19994
19995    /**
19996     * Request that the `HTML5` Tech exit fullscreen.
19997     */
19998
19999
20000    Html5.prototype.exitFullScreen = function exitFullScreen() {
20001      this.el_.webkitExitFullScreen();
20002    };
20003
20004    /**
20005     * A getter/setter for the `Html5` Tech's source object.
20006     * > Note: Please use {@link Html5#setSource}
20007     *
20008     * @param {Tech~SourceObject} [src]
20009     *        The source object you want to set on the `HTML5` techs element.
20010     *
20011     * @return {Tech~SourceObject|undefined}
20012     *         - The current source object when a source is not passed in.
20013     *         - undefined when setting
20014     *
20015     * @deprecated Since version 5.
20016     */
20017
20018
20019    Html5.prototype.src = function src(_src) {
20020      if (_src === undefined) {
20021        return this.el_.src;
20022      }
20023
20024      // Setting src through `src` instead of `setSrc` will be deprecated
20025      this.setSrc(_src);
20026    };
20027
20028    /**
20029     * Reset the tech by removing all sources and then calling
20030     * {@link Html5.resetMediaElement}.
20031     */
20032
20033
20034    Html5.prototype.reset = function reset() {
20035      Html5.resetMediaElement(this.el_);
20036    };
20037
20038    /**
20039     * Get the current source on the `HTML5` Tech. Falls back to returning the source from
20040     * the HTML5 media element.
20041     *
20042     * @return {Tech~SourceObject}
20043     *         The current source object from the HTML5 tech. With a fallback to the
20044     *         elements source.
20045     */
20046
20047
20048    Html5.prototype.currentSrc = function currentSrc() {
20049      if (this.currentSource_) {
20050        return this.currentSource_.src;
20051      }
20052      return this.el_.currentSrc;
20053    };
20054
20055    /**
20056     * Set controls attribute for the HTML5 media Element.
20057     *
20058     * @param {string} val
20059     *        Value to set the controls attribute to
20060     */
20061
20062
20063    Html5.prototype.setControls = function setControls(val) {
20064      this.el_.controls = !!val;
20065    };
20066
20067    /**
20068     * Create and returns a remote {@link TextTrack} object.
20069     *
20070     * @param {string} kind
20071     *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
20072     *
20073     * @param {string} [label]
20074     *        Label to identify the text track
20075     *
20076     * @param {string} [language]
20077     *        Two letter language abbreviation
20078     *
20079     * @return {TextTrack}
20080     *         The TextTrack that gets created.
20081     */
20082
20083
20084    Html5.prototype.addTextTrack = function addTextTrack(kind, label, language) {
20085      if (!this.featuresNativeTextTracks) {
20086        return _Tech.prototype.addTextTrack.call(this, kind, label, language);
20087      }
20088
20089      return this.el_.addTextTrack(kind, label, language);
20090    };
20091
20092    /**
20093     * Creates either native TextTrack or an emulated TextTrack depending
20094     * on the value of `featuresNativeTextTracks`
20095     *
20096     * @param {Object} options
20097     *        The object should contain the options to initialize the TextTrack with.
20098     *
20099     * @param {string} [options.kind]
20100     *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
20101     *
20102     * @param {string} [options.label]
20103     *        Label to identify the text track
20104     *
20105     * @param {string} [options.language]
20106     *        Two letter language abbreviation.
20107     *
20108     * @param {boolean} [options.default]
20109     *        Default this track to on.
20110     *
20111     * @param {string} [options.id]
20112     *        The internal id to assign this track.
20113     *
20114     * @param {string} [options.src]
20115     *        A source url for the track.
20116     *
20117     * @return {HTMLTrackElement}
20118     *         The track element that gets created.
20119     */
20120
20121
20122    Html5.prototype.createRemoteTextTrack = function createRemoteTextTrack(options) {
20123      if (!this.featuresNativeTextTracks) {
20124        return _Tech.prototype.createRemoteTextTrack.call(this, options);
20125      }
20126      var htmlTrackElement = document_1.createElement('track');
20127
20128      if (options.kind) {
20129        htmlTrackElement.kind = options.kind;
20130      }
20131      if (options.label) {
20132        htmlTrackElement.label = options.label;
20133      }
20134      if (options.language || options.srclang) {
20135        htmlTrackElement.srclang = options.language || options.srclang;
20136      }
20137      if (options.default) {
20138        htmlTrackElement.default = options.default;
20139      }
20140      if (options.id) {
20141        htmlTrackElement.id = options.id;
20142      }
20143      if (options.src) {
20144        htmlTrackElement.src = options.src;
20145      }
20146
20147      return htmlTrackElement;
20148    };
20149
20150    /**
20151     * Creates a remote text track object and returns an html track element.
20152     *
20153     * @param {Object} options The object should contain values for
20154     * kind, language, label, and src (location of the WebVTT file)
20155     * @param {Boolean} [manualCleanup=true] if set to false, the TextTrack will be
20156     * automatically removed from the video element whenever the source changes
20157     * @return {HTMLTrackElement} An Html Track Element.
20158     * This can be an emulated {@link HTMLTrackElement} or a native one.
20159     * @deprecated The default value of the "manualCleanup" parameter will default
20160     * to "false" in upcoming versions of Video.js
20161     */
20162
20163
20164    Html5.prototype.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
20165      var htmlTrackElement = _Tech.prototype.addRemoteTextTrack.call(this, options, manualCleanup);
20166
20167      if (this.featuresNativeTextTracks) {
20168        this.el().appendChild(htmlTrackElement);
20169      }
20170
20171      return htmlTrackElement;
20172    };
20173
20174    /**
20175     * Remove remote `TextTrack` from `TextTrackList` object
20176     *
20177     * @param {TextTrack} track
20178     *        `TextTrack` object to remove
20179     */
20180
20181
20182    Html5.prototype.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
20183      _Tech.prototype.removeRemoteTextTrack.call(this, track);
20184
20185      if (this.featuresNativeTextTracks) {
20186        var tracks = this.$$('track');
20187
20188        var i = tracks.length;
20189
20190        while (i--) {
20191          if (track === tracks[i] || track === tracks[i].track) {
20192            this.el().removeChild(tracks[i]);
20193          }
20194        }
20195      }
20196    };
20197
20198    /**
20199     * Gets available media playback quality metrics as specified by the W3C's Media
20200     * Playback Quality API.
20201     *
20202     * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
20203     *
20204     * @return {Object}
20205     *         An object with supported media playback quality metrics
20206     */
20207
20208
20209    Html5.prototype.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
20210      if (typeof this.el().getVideoPlaybackQuality === 'function') {
20211        return this.el().getVideoPlaybackQuality();
20212      }
20213
20214      var videoPlaybackQuality = {};
20215
20216      if (typeof this.el().webkitDroppedFrameCount !== 'undefined' && typeof this.el().webkitDecodedFrameCount !== 'undefined') {
20217        videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount;
20218        videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount;
20219      }
20220
20221      if (window_1.performance && typeof window_1.performance.now === 'function') {
20222        videoPlaybackQuality.creationTime = window_1.performance.now();
20223      } else if (window_1.performance && window_1.performance.timing && typeof window_1.performance.timing.navigationStart === 'number') {
20224        videoPlaybackQuality.creationTime = window_1.Date.now() - window_1.performance.timing.navigationStart;
20225      }
20226
20227      return videoPlaybackQuality;
20228    };
20229
20230    return Html5;
20231  }(Tech);
20232
20233  /* HTML5 Support Testing ---------------------------------------------------- */
20234
20235  if (isReal()) {
20236
20237    /**
20238     * Element for testing browser HTML5 media capabilities
20239     *
20240     * @type {Element}
20241     * @constant
20242     * @private
20243     */
20244    Html5.TEST_VID = document_1.createElement('video');
20245    var track = document_1.createElement('track');
20246
20247    track.kind = 'captions';
20248    track.srclang = 'en';
20249    track.label = 'English';
20250    Html5.TEST_VID.appendChild(track);
20251  }
20252
20253  /**
20254   * Check if HTML5 media is supported by this browser/device.
20255   *
20256   * @return {boolean}
20257   *         - True if HTML5 media is supported.
20258   *         - False if HTML5 media is not supported.
20259   */
20260  Html5.isSupported = function () {
20261    // IE with no Media Player is a LIAR! (#984)
20262    try {
20263      Html5.TEST_VID.volume = 0.5;
20264    } catch (e) {
20265      return false;
20266    }
20267
20268    return !!(Html5.TEST_VID && Html5.TEST_VID.canPlayType);
20269  };
20270
20271  /**
20272   * Check if the tech can support the given type
20273   *
20274   * @param {string} type
20275   *        The mimetype to check
20276   * @return {string} 'probably', 'maybe', or '' (empty string)
20277   */
20278  Html5.canPlayType = function (type) {
20279    return Html5.TEST_VID.canPlayType(type);
20280  };
20281
20282  /**
20283   * Check if the tech can support the given source
20284   * @param {Object} srcObj
20285   *        The source object
20286   * @param {Object} options
20287   *        The options passed to the tech
20288   * @return {string} 'probably', 'maybe', or '' (empty string)
20289   */
20290  Html5.canPlaySource = function (srcObj, options) {
20291    return Html5.canPlayType(srcObj.type);
20292  };
20293
20294  /**
20295   * Check if the volume can be changed in this browser/device.
20296   * Volume cannot be changed in a lot of mobile devices.
20297   * Specifically, it can't be changed from 1 on iOS.
20298   *
20299   * @return {boolean}
20300   *         - True if volume can be controlled
20301   *         - False otherwise
20302   */
20303  Html5.canControlVolume = function () {
20304    // IE will error if Windows Media Player not installed #3315
20305    try {
20306      var volume = Html5.TEST_VID.volume;
20307
20308      Html5.TEST_VID.volume = volume / 2 + 0.1;
20309      return volume !== Html5.TEST_VID.volume;
20310    } catch (e) {
20311      return false;
20312    }
20313  };
20314
20315  /**
20316   * Check if the playback rate can be changed in this browser/device.
20317   *
20318   * @return {boolean}
20319   *         - True if playback rate can be controlled
20320   *         - False otherwise
20321   */
20322  Html5.canControlPlaybackRate = function () {
20323    // Playback rate API is implemented in Android Chrome, but doesn't do anything
20324    // https://github.com/videojs/video.js/issues/3180
20325    if (IS_ANDROID && IS_CHROME && CHROME_VERSION < 58) {
20326      return false;
20327    }
20328    // IE will error if Windows Media Player not installed #3315
20329    try {
20330      var playbackRate = Html5.TEST_VID.playbackRate;
20331
20332      Html5.TEST_VID.playbackRate = playbackRate / 2 + 0.1;
20333      return playbackRate !== Html5.TEST_VID.playbackRate;
20334    } catch (e) {
20335      return false;
20336    }
20337  };
20338
20339  /**
20340   * Check if we can override a video/audio elements attributes, with
20341   * Object.defineProperty.
20342   *
20343   * @return {boolean}
20344   *         - True if builtin attributes can be overridden
20345   *         - False otherwise
20346   */
20347  Html5.canOverrideAttributes = function () {
20348    // if we cannot overwrite the src/innerHTML property, there is no support
20349    // iOS 7 safari for instance cannot do this.
20350    try {
20351      var noop = function noop() {};
20352
20353      Object.defineProperty(document_1.createElement('video'), 'src', { get: noop, set: noop });
20354      Object.defineProperty(document_1.createElement('audio'), 'src', { get: noop, set: noop });
20355      Object.defineProperty(document_1.createElement('video'), 'innerHTML', { get: noop, set: noop });
20356      Object.defineProperty(document_1.createElement('audio'), 'innerHTML', { get: noop, set: noop });
20357    } catch (e) {
20358      return false;
20359    }
20360
20361    return true;
20362  };
20363
20364  /**
20365   * Check to see if native `TextTrack`s are supported by this browser/device.
20366   *
20367   * @return {boolean}
20368   *         - True if native `TextTrack`s are supported.
20369   *         - False otherwise
20370   */
20371  Html5.supportsNativeTextTracks = function () {
20372    return IS_ANY_SAFARI;
20373  };
20374
20375  /**
20376   * Check to see if native `VideoTrack`s are supported by this browser/device
20377   *
20378   * @return {boolean}
20379   *        - True if native `VideoTrack`s are supported.
20380   *        - False otherwise
20381   */
20382  Html5.supportsNativeVideoTracks = function () {
20383    return !!(Html5.TEST_VID && Html5.TEST_VID.videoTracks);
20384  };
20385
20386  /**
20387   * Check to see if native `AudioTrack`s are supported by this browser/device
20388   *
20389   * @return {boolean}
20390   *        - True if native `AudioTrack`s are supported.
20391   *        - False otherwise
20392   */
20393  Html5.supportsNativeAudioTracks = function () {
20394    return !!(Html5.TEST_VID && Html5.TEST_VID.audioTracks);
20395  };
20396
20397  /**
20398   * An array of events available on the Html5 tech.
20399   *
20400   * @private
20401   * @type {Array}
20402   */
20403  Html5.Events = ['loadstart', 'suspend', 'abort', 'error', 'emptied', 'stalled', 'loadedmetadata', 'loadeddata', 'canplay', 'canplaythrough', 'playing', 'waiting', 'seeking', 'seeked', 'ended', 'durationchange', 'timeupdate', 'progress', 'play', 'pause', 'ratechange', 'resize', 'volumechange'];
20404
20405  /**
20406   * Boolean indicating whether the `Tech` supports volume control.
20407   *
20408   * @type {boolean}
20409   * @default {@link Html5.canControlVolume}
20410   */
20411  Html5.prototype.featuresVolumeControl = Html5.canControlVolume();
20412
20413  /**
20414   * Boolean indicating whether the `Tech` supports changing the speed at which the media
20415   * plays. Examples:
20416   *   - Set player to play 2x (twice) as fast
20417   *   - Set player to play 0.5x (half) as fast
20418   *
20419   * @type {boolean}
20420   * @default {@link Html5.canControlPlaybackRate}
20421   */
20422  Html5.prototype.featuresPlaybackRate = Html5.canControlPlaybackRate();
20423
20424  /**
20425   * Boolean indicating whether the `Tech` supports the `sourceset` event.
20426   *
20427   * @type {boolean}
20428   * @default
20429   */
20430  Html5.prototype.featuresSourceset = Html5.canOverrideAttributes();
20431
20432  /**
20433   * Boolean indicating whether the `HTML5` tech currently supports the media element
20434   * moving in the DOM. iOS breaks if you move the media element, so this is set this to
20435   * false there. Everywhere else this should be true.
20436   *
20437   * @type {boolean}
20438   * @default
20439   */
20440  Html5.prototype.movingMediaElementInDOM = !IS_IOS;
20441
20442  // TODO: Previous comment: No longer appears to be used. Can probably be removed.
20443  //       Is this true?
20444  /**
20445   * Boolean indicating whether the `HTML5` tech currently supports automatic media resize
20446   * when going into fullscreen.
20447   *
20448   * @type {boolean}
20449   * @default
20450   */
20451  Html5.prototype.featuresFullscreenResize = true;
20452
20453  /**
20454   * Boolean indicating whether the `HTML5` tech currently supports the progress event.
20455   * If this is false, manual `progress` events will be triggered instead.
20456   *
20457   * @type {boolean}
20458   * @default
20459   */
20460  Html5.prototype.featuresProgressEvents = true;
20461
20462  /**
20463   * Boolean indicating whether the `HTML5` tech currently supports the timeupdate event.
20464   * If this is false, manual `timeupdate` events will be triggered instead.
20465   *
20466   * @default
20467   */
20468  Html5.prototype.featuresTimeupdateEvents = true;
20469
20470  /**
20471   * Boolean indicating whether the `HTML5` tech currently supports native `TextTrack`s.
20472   *
20473   * @type {boolean}
20474   * @default {@link Html5.supportsNativeTextTracks}
20475   */
20476  Html5.prototype.featuresNativeTextTracks = Html5.supportsNativeTextTracks();
20477
20478  /**
20479   * Boolean indicating whether the `HTML5` tech currently supports native `VideoTrack`s.
20480   *
20481   * @type {boolean}
20482   * @default {@link Html5.supportsNativeVideoTracks}
20483   */
20484  Html5.prototype.featuresNativeVideoTracks = Html5.supportsNativeVideoTracks();
20485
20486  /**
20487   * Boolean indicating whether the `HTML5` tech currently supports native `AudioTrack`s.
20488   *
20489   * @type {boolean}
20490   * @default {@link Html5.supportsNativeAudioTracks}
20491   */
20492  Html5.prototype.featuresNativeAudioTracks = Html5.supportsNativeAudioTracks();
20493
20494  // HTML5 Feature detection and Device Fixes --------------------------------- //
20495  var canPlayType = Html5.TEST_VID && Html5.TEST_VID.constructor.prototype.canPlayType;
20496  var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i;
20497
20498  Html5.patchCanPlayType = function () {
20499
20500    // Android 4.0 and above can play HLS to some extent but it reports being unable to do so
20501    // Firefox and Chrome report correctly
20502    if (ANDROID_VERSION >= 4.0 && !IS_FIREFOX && !IS_CHROME) {
20503      Html5.TEST_VID.constructor.prototype.canPlayType = function (type) {
20504        if (type && mpegurlRE.test(type)) {
20505          return 'maybe';
20506        }
20507        return canPlayType.call(this, type);
20508      };
20509    }
20510  };
20511
20512  Html5.unpatchCanPlayType = function () {
20513    var r = Html5.TEST_VID.constructor.prototype.canPlayType;
20514
20515    Html5.TEST_VID.constructor.prototype.canPlayType = canPlayType;
20516    return r;
20517  };
20518
20519  // by default, patch the media element
20520  Html5.patchCanPlayType();
20521
20522  Html5.disposeMediaElement = function (el) {
20523    if (!el) {
20524      return;
20525    }
20526
20527    if (el.parentNode) {
20528      el.parentNode.removeChild(el);
20529    }
20530
20531    // remove any child track or source nodes to prevent their loading
20532    while (el.hasChildNodes()) {
20533      el.removeChild(el.firstChild);
20534    }
20535
20536    // remove any src reference. not setting `src=''` because that causes a warning
20537    // in firefox
20538    el.removeAttribute('src');
20539
20540    // force the media element to update its loading state by calling load()
20541    // however IE on Windows 7N has a bug that throws an error so need a try/catch (#793)
20542    if (typeof el.load === 'function') {
20543      // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
20544      (function () {
20545        try {
20546          el.load();
20547        } catch (e) {
20548          // not supported
20549        }
20550      })();
20551    }
20552  };
20553
20554  Html5.resetMediaElement = function (el) {
20555    if (!el) {
20556      return;
20557    }
20558
20559    var sources = el.querySelectorAll('source');
20560    var i = sources.length;
20561
20562    while (i--) {
20563      el.removeChild(sources[i]);
20564    }
20565
20566    // remove any src reference.
20567    // not setting `src=''` because that throws an error
20568    el.removeAttribute('src');
20569
20570    if (typeof el.load === 'function') {
20571      // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
20572      (function () {
20573        try {
20574          el.load();
20575        } catch (e) {
20576          // satisfy linter
20577        }
20578      })();
20579    }
20580  };
20581
20582  /* Native HTML5 element property wrapping ----------------------------------- */
20583  // Wrap native boolean attributes with getters that check both property and attribute
20584  // The list is as followed:
20585  // muted, defaultMuted, autoplay, controls, loop, playsinline
20586  [
20587  /**
20588   * Get the value of `muted` from the media element. `muted` indicates
20589   * that the volume for the media should be set to silent. This does not actually change
20590   * the `volume` attribute.
20591   *
20592   * @method Html5#muted
20593   * @return {boolean}
20594   *         - True if the value of `volume` should be ignored and the audio set to silent.
20595   *         - False if the value of `volume` should be used.
20596   *
20597   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
20598   */
20599  'muted',
20600
20601  /**
20602   * Get the value of `defaultMuted` from the media element. `defaultMuted` indicates
20603   * whether the media should start muted or not. Only changes the default state of the
20604   * media. `muted` and `defaultMuted` can have different values. {@link Html5#muted} indicates the
20605   * current state.
20606   *
20607   * @method Html5#defaultMuted
20608   * @return {boolean}
20609   *         - The value of `defaultMuted` from the media element.
20610   *         - True indicates that the media should start muted.
20611   *         - False indicates that the media should not start muted
20612   *
20613   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
20614   */
20615  'defaultMuted',
20616
20617  /**
20618   * Get the value of `autoplay` from the media element. `autoplay` indicates
20619   * that the media should start to play as soon as the page is ready.
20620   *
20621   * @method Html5#autoplay
20622   * @return {boolean}
20623   *         - The value of `autoplay` from the media element.
20624   *         - True indicates that the media should start as soon as the page loads.
20625   *         - False indicates that the media should not start as soon as the page loads.
20626   *
20627   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
20628   */
20629  'autoplay',
20630
20631  /**
20632   * Get the value of `controls` from the media element. `controls` indicates
20633   * whether the native media controls should be shown or hidden.
20634   *
20635   * @method Html5#controls
20636   * @return {boolean}
20637   *         - The value of `controls` from the media element.
20638   *         - True indicates that native controls should be showing.
20639   *         - False indicates that native controls should be hidden.
20640   *
20641   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-controls}
20642   */
20643  'controls',
20644
20645  /**
20646   * Get the value of `loop` from the media element. `loop` indicates
20647   * that the media should return to the start of the media and continue playing once
20648   * it reaches the end.
20649   *
20650   * @method Html5#loop
20651   * @return {boolean}
20652   *         - The value of `loop` from the media element.
20653   *         - True indicates that playback should seek back to start once
20654   *           the end of a media is reached.
20655   *         - False indicates that playback should not loop back to the start when the
20656   *           end of the media is reached.
20657   *
20658   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
20659   */
20660  'loop',
20661
20662  /**
20663   * Get the value of `playsinline` from the media element. `playsinline` indicates
20664   * to the browser that non-fullscreen playback is preferred when fullscreen
20665   * playback is the native default, such as in iOS Safari.
20666   *
20667   * @method Html5#playsinline
20668   * @return {boolean}
20669   *         - The value of `playsinline` from the media element.
20670   *         - True indicates that the media should play inline.
20671   *         - False indicates that the media should not play inline.
20672   *
20673   * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
20674   */
20675  'playsinline'].forEach(function (prop) {
20676    Html5.prototype[prop] = function () {
20677      return this.el_[prop] || this.el_.hasAttribute(prop);
20678    };
20679  });
20680
20681  // Wrap native boolean attributes with setters that set both property and attribute
20682  // The list is as followed:
20683  // setMuted, setDefaultMuted, setAutoplay, setLoop, setPlaysinline
20684  // setControls is special-cased above
20685  [
20686  /**
20687   * Set the value of `muted` on the media element. `muted` indicates that the current
20688   * audio level should be silent.
20689   *
20690   * @method Html5#setMuted
20691   * @param {boolean} muted
20692   *        - True if the audio should be set to silent
20693   *        - False otherwise
20694   *
20695   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
20696   */
20697  'muted',
20698
20699  /**
20700   * Set the value of `defaultMuted` on the media element. `defaultMuted` indicates that the current
20701   * audio level should be silent, but will only effect the muted level on intial playback..
20702   *
20703   * @method Html5.prototype.setDefaultMuted
20704   * @param {boolean} defaultMuted
20705   *        - True if the audio should be set to silent
20706   *        - False otherwise
20707   *
20708   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
20709   */
20710  'defaultMuted',
20711
20712  /**
20713   * Set the value of `autoplay` on the media element. `autoplay` indicates
20714   * that the media should start to play as soon as the page is ready.
20715   *
20716   * @method Html5#setAutoplay
20717   * @param {boolean} autoplay
20718   *         - True indicates that the media should start as soon as the page loads.
20719   *         - False indicates that the media should not start as soon as the page loads.
20720   *
20721   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
20722   */
20723  'autoplay',
20724
20725  /**
20726   * Set the value of `loop` on the media element. `loop` indicates
20727   * that the media should return to the start of the media and continue playing once
20728   * it reaches the end.
20729   *
20730   * @method Html5#setLoop
20731   * @param {boolean} loop
20732   *         - True indicates that playback should seek back to start once
20733   *           the end of a media is reached.
20734   *         - False indicates that playback should not loop back to the start when the
20735   *           end of the media is reached.
20736   *
20737   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
20738   */
20739  'loop',
20740
20741  /**
20742   * Set the value of `playsinline` from the media element. `playsinline` indicates
20743   * to the browser that non-fullscreen playback is preferred when fullscreen
20744   * playback is the native default, such as in iOS Safari.
20745   *
20746   * @method Html5#setPlaysinline
20747   * @param {boolean} playsinline
20748   *         - True indicates that the media should play inline.
20749   *         - False indicates that the media should not play inline.
20750   *
20751   * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
20752   */
20753  'playsinline'].forEach(function (prop) {
20754    Html5.prototype['set' + toTitleCase(prop)] = function (v) {
20755      this.el_[prop] = v;
20756
20757      if (v) {
20758        this.el_.setAttribute(prop, prop);
20759      } else {
20760        this.el_.removeAttribute(prop);
20761      }
20762    };
20763  });
20764
20765  // Wrap native properties with a getter
20766  // The list is as followed
20767  // paused, currentTime, buffered, volume, poster, preload, error, seeking
20768  // seekable, ended, playbackRate, defaultPlaybackRate, played, networkState
20769  // readyState, videoWidth, videoHeight
20770  [
20771  /**
20772   * Get the value of `paused` from the media element. `paused` indicates whether the media element
20773   * is currently paused or not.
20774   *
20775   * @method Html5#paused
20776   * @return {boolean}
20777   *         The value of `paused` from the media element.
20778   *
20779   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-paused}
20780   */
20781  'paused',
20782
20783  /**
20784   * Get the value of `currentTime` from the media element. `currentTime` indicates
20785   * the current second that the media is at in playback.
20786   *
20787   * @method Html5#currentTime
20788   * @return {number}
20789   *         The value of `currentTime` from the media element.
20790   *
20791   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-currenttime}
20792   */
20793  'currentTime',
20794
20795  /**
20796   * Get the value of `buffered` from the media element. `buffered` is a `TimeRange`
20797   * object that represents the parts of the media that are already downloaded and
20798   * available for playback.
20799   *
20800   * @method Html5#buffered
20801   * @return {TimeRange}
20802   *         The value of `buffered` from the media element.
20803   *
20804   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-buffered}
20805   */
20806  'buffered',
20807
20808  /**
20809   * Get the value of `volume` from the media element. `volume` indicates
20810   * the current playback volume of audio for a media. `volume` will be a value from 0
20811   * (silent) to 1 (loudest and default).
20812   *
20813   * @method Html5#volume
20814   * @return {number}
20815   *         The value of `volume` from the media element. Value will be between 0-1.
20816   *
20817   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
20818   */
20819  'volume',
20820
20821  /**
20822   * Get the value of `poster` from the media element. `poster` indicates
20823   * that the url of an image file that can/will be shown when no media data is available.
20824   *
20825   * @method Html5#poster
20826   * @return {string}
20827   *         The value of `poster` from the media element. Value will be a url to an
20828   *         image.
20829   *
20830   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-video-poster}
20831   */
20832  'poster',
20833
20834  /**
20835   * Get the value of `preload` from the media element. `preload` indicates
20836   * what should download before the media is interacted with. It can have the following
20837   * values:
20838   * - none: nothing should be downloaded
20839   * - metadata: poster and the first few frames of the media may be downloaded to get
20840   *   media dimensions and other metadata
20841   * - auto: allow the media and metadata for the media to be downloaded before
20842   *    interaction
20843   *
20844   * @method Html5#preload
20845   * @return {string}
20846   *         The value of `preload` from the media element. Will be 'none', 'metadata',
20847   *         or 'auto'.
20848   *
20849   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
20850   */
20851  'preload',
20852
20853  /**
20854   * Get the value of the `error` from the media element. `error` indicates any
20855   * MediaError that may have occurred during playback. If error returns null there is no
20856   * current error.
20857   *
20858   * @method Html5#error
20859   * @return {MediaError|null}
20860   *         The value of `error` from the media element. Will be `MediaError` if there
20861   *         is a current error and null otherwise.
20862   *
20863   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-error}
20864   */
20865  'error',
20866
20867  /**
20868   * Get the value of `seeking` from the media element. `seeking` indicates whether the
20869   * media is currently seeking to a new position or not.
20870   *
20871   * @method Html5#seeking
20872   * @return {boolean}
20873   *         - The value of `seeking` from the media element.
20874   *         - True indicates that the media is currently seeking to a new position.
20875   *         - False indicates that the media is not seeking to a new position at this time.
20876   *
20877   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seeking}
20878   */
20879  'seeking',
20880
20881  /**
20882   * Get the value of `seekable` from the media element. `seekable` returns a
20883   * `TimeRange` object indicating ranges of time that can currently be `seeked` to.
20884   *
20885   * @method Html5#seekable
20886   * @return {TimeRange}
20887   *         The value of `seekable` from the media element. A `TimeRange` object
20888   *         indicating the current ranges of time that can be seeked to.
20889   *
20890   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seekable}
20891   */
20892  'seekable',
20893
20894  /**
20895   * Get the value of `ended` from the media element. `ended` indicates whether
20896   * the media has reached the end or not.
20897   *
20898   * @method Html5#ended
20899   * @return {boolean}
20900   *         - The value of `ended` from the media element.
20901   *         - True indicates that the media has ended.
20902   *         - False indicates that the media has not ended.
20903   *
20904   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-ended}
20905   */
20906  'ended',
20907
20908  /**
20909   * Get the value of `playbackRate` from the media element. `playbackRate` indicates
20910   * the rate at which the media is currently playing back. Examples:
20911   *   - if playbackRate is set to 2, media will play twice as fast.
20912   *   - if playbackRate is set to 0.5, media will play half as fast.
20913   *
20914   * @method Html5#playbackRate
20915   * @return {number}
20916   *         The value of `playbackRate` from the media element. A number indicating
20917   *         the current playback speed of the media, where 1 is normal speed.
20918   *
20919   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
20920   */
20921  'playbackRate',
20922
20923  /**
20924   * Get the value of `defaultPlaybackRate` from the media element. `defaultPlaybackRate` indicates
20925   * the rate at which the media is currently playing back. This value will not indicate the current
20926   * `playbackRate` after playback has started, use {@link Html5#playbackRate} for that.
20927   *
20928   * Examples:
20929   *   - if defaultPlaybackRate is set to 2, media will play twice as fast.
20930   *   - if defaultPlaybackRate is set to 0.5, media will play half as fast.
20931   *
20932   * @method Html5.prototype.defaultPlaybackRate
20933   * @return {number}
20934   *         The value of `defaultPlaybackRate` from the media element. A number indicating
20935   *         the current playback speed of the media, where 1 is normal speed.
20936   *
20937   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
20938   */
20939  'defaultPlaybackRate',
20940
20941  /**
20942   * Get the value of `played` from the media element. `played` returns a `TimeRange`
20943   * object representing points in the media timeline that have been played.
20944   *
20945   * @method Html5#played
20946   * @return {TimeRange}
20947   *         The value of `played` from the media element. A `TimeRange` object indicating
20948   *         the ranges of time that have been played.
20949   *
20950   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-played}
20951   */
20952  'played',
20953
20954  /**
20955   * Get the value of `networkState` from the media element. `networkState` indicates
20956   * the current network state. It returns an enumeration from the following list:
20957   * - 0: NETWORK_EMPTY
20958   * - 1: NETWORK_IDLE
20959   * - 2: NETWORK_LOADING
20960   * - 3: NETWORK_NO_SOURCE
20961   *
20962   * @method Html5#networkState
20963   * @return {number}
20964   *         The value of `networkState` from the media element. This will be a number
20965   *         from the list in the description.
20966   *
20967   * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-networkstate}
20968   */
20969  'networkState',
20970
20971  /**
20972   * Get the value of `readyState` from the media element. `readyState` indicates
20973   * the current state of the media element. It returns an enumeration from the
20974   * following list:
20975   * - 0: HAVE_NOTHING
20976   * - 1: HAVE_METADATA
20977   * - 2: HAVE_CURRENT_DATA
20978   * - 3: HAVE_FUTURE_DATA
20979   * - 4: HAVE_ENOUGH_DATA
20980   *
20981   * @method Html5#readyState
20982   * @return {number}
20983   *         The value of `readyState` from the media element. This will be a number
20984   *         from the list in the description.
20985   *
20986   * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#ready-states}
20987   */
20988  'readyState',
20989
20990  /**
20991   * Get the value of `videoWidth` from the video element. `videoWidth` indicates
20992   * the current width of the video in css pixels.
20993   *
20994   * @method Html5#videoWidth
20995   * @return {number}
20996   *         The value of `videoWidth` from the video element. This will be a number
20997   *         in css pixels.
20998   *
20999   * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
21000   */
21001  'videoWidth',
21002
21003  /**
21004   * Get the value of `videoHeight` from the video element. `videoHeight` indicates
21005   * the current height of the video in css pixels.
21006   *
21007   * @method Html5#videoHeight
21008   * @return {number}
21009   *         The value of `videoHeight` from the video element. This will be a number
21010   *         in css pixels.
21011   *
21012   * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
21013   */
21014  'videoHeight'].forEach(function (prop) {
21015    Html5.prototype[prop] = function () {
21016      return this.el_[prop];
21017    };
21018  });
21019
21020  // Wrap native properties with a setter in this format:
21021  // set + toTitleCase(name)
21022  // The list is as follows:
21023  // setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate
21024  [
21025  /**
21026   * Set the value of `volume` on the media element. `volume` indicates the current
21027   * audio level as a percentage in decimal form. This means that 1 is 100%, 0.5 is 50%, and
21028   * so on.
21029   *
21030   * @method Html5#setVolume
21031   * @param {number} percentAsDecimal
21032   *        The volume percent as a decimal. Valid range is from 0-1.
21033   *
21034   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
21035   */
21036  'volume',
21037
21038  /**
21039   * Set the value of `src` on the media element. `src` indicates the current
21040   * {@link Tech~SourceObject} for the media.
21041   *
21042   * @method Html5#setSrc
21043   * @param {Tech~SourceObject} src
21044   *        The source object to set as the current source.
21045   *
21046   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-src}
21047   */
21048  'src',
21049
21050  /**
21051   * Set the value of `poster` on the media element. `poster` is the url to
21052   * an image file that can/will be shown when no media data is available.
21053   *
21054   * @method Html5#setPoster
21055   * @param {string} poster
21056   *        The url to an image that should be used as the `poster` for the media
21057   *        element.
21058   *
21059   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-poster}
21060   */
21061  'poster',
21062
21063  /**
21064   * Set the value of `preload` on the media element. `preload` indicates
21065   * what should download before the media is interacted with. It can have the following
21066   * values:
21067   * - none: nothing should be downloaded
21068   * - metadata: poster and the first few frames of the media may be downloaded to get
21069   *   media dimensions and other metadata
21070   * - auto: allow the media and metadata for the media to be downloaded before
21071   *    interaction
21072   *
21073   * @method Html5#setPreload
21074   * @param {string} preload
21075   *         The value of `preload` to set on the media element. Must be 'none', 'metadata',
21076   *         or 'auto'.
21077   *
21078   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
21079   */
21080  'preload',
21081
21082  /**
21083   * Set the value of `playbackRate` on the media element. `playbackRate` indicates
21084   * the rate at which the media should play back. Examples:
21085   *   - if playbackRate is set to 2, media will play twice as fast.
21086   *   - if playbackRate is set to 0.5, media will play half as fast.
21087   *
21088   * @method Html5#setPlaybackRate
21089   * @return {number}
21090   *         The value of `playbackRate` from the media element. A number indicating
21091   *         the current playback speed of the media, where 1 is normal speed.
21092   *
21093   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
21094   */
21095  'playbackRate',
21096
21097  /**
21098   * Set the value of `defaultPlaybackRate` on the media element. `defaultPlaybackRate` indicates
21099   * the rate at which the media should play back upon initial startup. Changing this value
21100   * after a video has started will do nothing. Instead you should used {@link Html5#setPlaybackRate}.
21101   *
21102   * Example Values:
21103   *   - if playbackRate is set to 2, media will play twice as fast.
21104   *   - if playbackRate is set to 0.5, media will play half as fast.
21105   *
21106   * @method Html5.prototype.setDefaultPlaybackRate
21107   * @return {number}
21108   *         The value of `defaultPlaybackRate` from the media element. A number indicating
21109   *         the current playback speed of the media, where 1 is normal speed.
21110   *
21111   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate}
21112   */
21113  'defaultPlaybackRate'].forEach(function (prop) {
21114    Html5.prototype['set' + toTitleCase(prop)] = function (v) {
21115      this.el_[prop] = v;
21116    };
21117  });
21118
21119  // wrap native functions with a function
21120  // The list is as follows:
21121  // pause, load, play
21122  [
21123  /**
21124   * A wrapper around the media elements `pause` function. This will call the `HTML5`
21125   * media elements `pause` function.
21126   *
21127   * @method Html5#pause
21128   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause}
21129   */
21130  'pause',
21131
21132  /**
21133   * A wrapper around the media elements `load` function. This will call the `HTML5`s
21134   * media element `load` function.
21135   *
21136   * @method Html5#load
21137   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load}
21138   */
21139  'load',
21140
21141  /**
21142   * A wrapper around the media elements `play` function. This will call the `HTML5`s
21143   * media element `play` function.
21144   *
21145   * @method Html5#play
21146   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play}
21147   */
21148  'play'].forEach(function (prop) {
21149    Html5.prototype[prop] = function () {
21150      return this.el_[prop]();
21151    };
21152  });
21153
21154  Tech.withSourceHandlers(Html5);
21155
21156  /**
21157   * Native source handler for Html5, simply passes the source to the media element.
21158   *
21159   * @property {Tech~SourceObject} source
21160   *        The source object
21161   *
21162   * @property {Html5} tech
21163   *        The instance of the HTML5 tech.
21164   */
21165  Html5.nativeSourceHandler = {};
21166
21167  /**
21168   * Check if the media element can play the given mime type.
21169   *
21170   * @param {string} type
21171   *        The mimetype to check
21172   *
21173   * @return {string}
21174   *         'probably', 'maybe', or '' (empty string)
21175   */
21176  Html5.nativeSourceHandler.canPlayType = function (type) {
21177    // IE without MediaPlayer throws an error (#519)
21178    try {
vendor: 1,664 bytes, lines 21179-21239
21179      return Html5.TEST_VID.canPlayType(type);
21180    } catch (e) {
21181      return '';
21182    }
21183  };
21184
21185  /**
21186   * Check if the media element can handle a source natively.
21187   *
21188   * @param {Tech~SourceObject} source
21189   *         The source object
21190   *
21191   * @param {Object} [options]
21192   *         Options to be passed to the tech.
21193   *
21194   * @return {string}
21195   *         'probably', 'maybe', or '' (empty string).
21196   */
21197  Html5.nativeSourceHandler.canHandleSource = function (source, options) {
21198
21199    // If a type was provided we should rely on that
21200    if (source.type) {
21201      return Html5.nativeSourceHandler.canPlayType(source.type);
21202
21203      // If no type, fall back to checking 'video/[EXTENSION]'
21204    } else if (source.src) {
21205      var ext = getFileExtension(source.src);
21206
21207      return Html5.nativeSourceHandler.canPlayType('video/' + ext);
21208    }
21209
21210    return '';
21211  };
21212
21213  /**
21214   * Pass the source to the native media element.
21215   *
21216   * @param {Tech~SourceObject} source
21217   *        The source object
21218   *
21219   * @param {Html5} tech
21220   *        The instance of the Html5 tech
21221   *
21222   * @param {Object} [options]
21223   *        The options to pass to the source
21224   */
21225  Html5.nativeSourceHandler.handleSource = function (source, tech, options) {
21226    tech.setSrc(source.src);
21227  };
21228
21229  /**
21230   * A noop for the native dispose function, as cleanup is not needed.
21231   */
21232  Html5.nativeSourceHandler.dispose = function () {};
21233
21234  // Register the native source handler
21235  Html5.registerSourceHandler(Html5.nativeSourceHandler);
21236
21237  Tech.registerTech('Html5', Html5);
21238
21239  var _templateObject$2 = taggedTemplateLiteralLoose(['\n        Using the tech directly can be dangerous. I hope you know 
21239what you\'re doing.\n        See https://github.com/videojs/video.js/issues/2617 for more info.\n      '], ['\n        Using the tech directly can be dangerous. I hope you know what you\'re doing.\n        See https://github.com/videojs/video.js/issues/2617 for more info.\n      ']);
21240
21241  // The following tech events are simply re-triggered
21242  // on the player when they happen
21243  var TECH_EVENTS_RETRIGGER = [
21244  /**
21245   * Fired while the user agent is downloading media data.
21246   *
21247   * @event Player#progress
21248   * @type {EventTarget~Event}
21249   */
21250  /**
21251   * Retrigger the `progress` event that was triggered by the {@link Tech}.
21252   *
21253   * @private
21254   * @method Player#handleTechProgress_
21255   * @fires Player#progress
21256   * @listens Tech#progress
21257   */
21258  'progress',
21259
21260  /**
21261   * Fires when the loading of an audio/video is aborted.
21262   *
21263   * @event Player#abort
21264   * @type {EventTarget~Event}
21265   */
21266  /**
21267   * Retrigger the `abort` event that was triggered by the {@link Tech}.
21268   *
21269   * @private
21270   * @method Player#handleTechAbort_
21271   * @fires Player#abort
21272   * @listens Tech#abort
21273   */
21274  'abort',
21275
21276  /**
21277   * Fires when the browser is intentionally not getting media data.
21278   *
21279   * @event Player#suspend
21280   * @type {EventTarget~Event}
21281   */
21282  /**
21283   * Retrigger the `suspend` event that was triggered by the {@link Tech}.
21284   *
21285   * @private
21286   * @method Player#handleTechSuspend_
21287   * @fires Player#suspend
21288   * @listens Tech#suspend
21289   */
21290  'suspend',
21291
21292  /**
21293   * Fires when the current playlist is empty.
21294   *
21295   * @event Player#emptied
21296   * @type {EventTarget~Event}
21297   */
21298  /**
21299   * Retrigger the `emptied` event that was triggered by the {@link Tech}.
21300   *
21301   * @private
21302   * @method Player#handleTechEmptied_
21303   * @fires Player#emptied
21304   * @listens Tech#emptied
21305   */
21306  'emptied',
21307  /**
21308   * Fires when the browser is trying to get media data, but data is not available.
21309   *
21310   * @event Player#stalled
21311   * @type {EventTarget~Event}
21312   */
21313  /**
21314   * Retrigger the `stalled` event that was triggered by the {@link Tech}.
21315   *
21316   * @private
21317   * @method Player#handleTechStalled_
21318   * @fires Player#stalled
21319   * @listens Tech#stalled
21320   */
21321  'stalled',
21322
21323  /**
21324   * Fires when the browser has loaded meta data for the audio/video.
21325   *
21326   * @event Player#loadedmetadata
21327   * @type {EventTarget~Event}
21328   */
21329  /**
21330   * Retrigger the `stalled` event that was triggered by the {@link Tech}.
21331   *
21332   * @private
21333   * @method Player#handleTechLoadedmetadata_
21334   * @fires Player#loadedmetadata
21335   * @listens Tech#loadedmetadata
21336   */
21337  'loadedmetadata',
21338
21339  /**
21340   * Fires when the browser has loaded the current frame of the audio/video.
21341   *
21342   * @event Player#loadeddata
21343   * @type {event}
21344   */
21345  /**
21346   * Retrigger the `loadeddata` event that was triggered by the {@link Tech}.
21347   *
21348   * @private
21349   * @method Player#handleTechLoaddeddata_
21350   * @fires Player#loadeddata
21351   * @listens Tech#loadeddata
21352   */
21353  'loadeddata',
21354
21355  /**
21356   * Fires when the current playback position has changed.
21357   *
21358   * @event Player#timeupdate
21359   * @type {event}
21360   */
21361  /**
21362   * Retrigger the `timeupdate` event that was triggered by the {@link Tech}.
21363   *
21364   * @private
21365   * @method Player#handleTechTimeUpdate_
21366   * @fires Player#timeupdate
21367   * @listens Tech#timeupdate
21368   */
21369  'timeupdate',
21370
21371  /**
21372   * Fires when the video's intrinsic dimensions change
21373   *
21374   * @event Player#resize
21375   * @type {event}
21376   */
21377  /**
21378   * Retrigger the `resize` event that was triggered by the {@link Tech}.
21379   *
21380   * @private
21381   * @method Player#handleTechResize_
21382   * @fires Player#resize
21383   * @listens Tech#resize
21384   */
21385  'resize',
21386
21387  /**
21388   * Fires when the volume has been changed
21389   *
21390   * @event Player#volumechange
21391   * @type {event}
21392   */
21393  /**
21394   * Retrigger the `volumechange` event that was triggered by the {@link Tech}.
21395   *
21396   * @private
21397   * @method Player#handleTechVolumechange_
21398   * @fires Player#volumechange
21399   * @listens Tech#volumechange
21400   */
21401  'volumechange',
21402
21403  /**
21404   * Fires when the text track has been changed
21405   *
21406   * @event Player#texttrackchange
21407   * @type {event}
21408   */
21409  /**
21410   * Retrigger the `texttrackchange` event that was triggered by the {@link Tech}.
21411   *
21412   * @private
21413   * @method Player#handleTechTexttrackchange_
21414   * @fires Player#texttrackchange
21415   * @listens Tech#texttrackchange
21416   */
21417  'texttrackchange'];
21418
21419  // events to queue when playback rate is zero
21420  // this is a hash for the sole purpose of mapping non-camel-cased event names
21421  // to camel-cased function names
21422  var TECH_EVENTS_QUEUE = {
21423    canplay: 'CanPlay',
21424    canplaythrough: 'CanPlayThrough',
21425    playing: 'Playing',
21426    seeked: 'Seeked'
21427  };
21428
21429  /**
21430   * An instance of the `Player` class is created when any of the Video.js setup methods
21431   * are used to initialize a video.
21432   *
21433   * After an instance has been created it can be accessed globally in two ways:
21434   * 1. By calling `videojs('example_video_1');`
21435   * 2. By using it directly via  `videojs.players.example_video_1;`
21436   *
21437   * @extends Component
21438   */
21439
21440  var Player = function (_Component) {
21441    inherits(Player, _Component);
21442
21443    /**
21444     * Create an instance of this class.
21445     *
21446     * @param {Element} tag
vendor: 35,174 bytes, lines 21447-22490
21447     *        The original video DOM element used for configuring options.
21448     *
21449     * @param {Object} [options]
21450     *        Object of option names and values.
21451     *
21452     * @param {Component~ReadyCallback} [ready]
21453     *        Ready callback function.
21454     */
21455    function Player(tag, options, ready) {
21456      classCallCheck(this, Player);
21457
21458      // Make sure tag ID exists
21459      tag.id = tag.id || options.id || 'vjs_video_' + newGUID();
21460
21461      // Set Options
21462      // The options argument overrides options set in the video tag
21463      // which overrides globally set options.
21464      // This latter part coincides with the load order
21465      // (tag must exist before Player)
21466      options = assign(Player.getTagSettings(tag), options);
21467
21468      // Delay the initialization of children because we need to set up
21469      // player properties first, and can't use `this` before `super()`
21470      options.initChildren = false;
21471
21472      // Same with creating the element
21473      options.createEl = false;
21474
21475      // don't auto mixin the evented mixin
21476      options.evented = false;
21477
21478      // we don't want the player to report touch activity on itself
21479      // see enableTouchActivity in Component
21480      options.reportTouchActivity = false;
21481
21482      // If language is not set, get the closest lang attribute
21483      if (!options.language) {
21484        if (typeof tag.closest === 'function') {
21485          var closest = tag.closest('[lang]');
21486
21487          if (closest && closest.getAttribute) {
21488            options.language = closest.getAttribute('lang');
21489          }
21490        } else {
21491          var element = tag;
21492
21493          while (element && element.nodeType === 1) {
21494            if (getAttributes(element).hasOwnProperty('lang')) {
21495              options.language = element.getAttribute('lang');
21496              break;
21497            }
21498            element = element.parentNode;
21499          }
21500        }
21501      }
21502
21503      // Run base component initializing with new options
21504
21505      // Tracks when a tech changes the poster
21506      var _this = possibleConstructorReturn(this, _Component.call(this, null, options, ready));
21507
21508      _this.isPosterFromTech_ = false;
21509
21510      // Holds callback info that gets queued when playback rate is zero
21511      // and a seek is happening
21512      _this.queuedCallbacks_ = [];
21513
21514      // Turn off API access because we're loading a new tech that might load asynchronously
21515      _this.isReady_ = false;
21516
21517      // Init state hasStarted_
21518      _this.hasStarted_ = false;
21519
21520      // Init state userActive_
21521      _this.userActive_ = false;
21522
21523      // if the global option object was accidentally blown away by
21524      // someone, bail early with an informative error
21525      if (!_this.options_ || !_this.options_.techOrder || !_this.options_.techOrder.length) {
21526        throw new Error('No techOrder specified. Did you overwrite ' + 'videojs.options instead of just changing the ' + 'properties you want to override?');
21527      }
21528
21529      // Store the original tag used to set options
21530      _this.tag = tag;
21531
21532      // Store the tag attributes used to restore html5 element
21533      _this.tagAttributes = tag && getAttributes(tag);
21534
21535      // Update current language
21536      _this.language(_this.options_.language);
21537
21538      // Update Supported Languages
21539      if (options.languages) {
21540        // Normalise player option languages to lowercase
21541        var languagesToLower = {};
21542
21543        Object.getOwnPropertyNames(options.languages).forEach(function (name$$1) {
21544          languagesToLower[name$$1.toLowerCase()] = options.languages[name$$1];
21545        });
21546        _this.languages_ = languagesToLower;
21547      } else {
21548        _this.languages_ = Player.prototype.options_.languages;
21549      }
21550
21551      // Cache for video property values.
21552      _this.cache_ = {};
21553
21554      // Set poster
21555      _this.poster_ = options.poster || '';
21556
21557      // Set controls
21558      _this.controls_ = !!options.controls;
21559
21560      // Set default values for lastVolume
21561      _this.cache_.lastVolume = 1;
21562
21563      // Original tag settings stored in options
21564      // now remove immediately so native controls don't flash.
21565      // May be turned back on by HTML5 tech if nativeControlsForTouch is true
21566      tag.controls = false;
21567      tag.removeAttribute('controls');
21568
21569      /*
21570       * Store the internal state of scrubbing
21571       *
21572       * @private
21573       * @return {Boolean} True if the user is scrubbing
21574       */
21575      _this.scrubbing_ = false;
21576
21577      _this.el_ = _this.createEl();
21578
21579      // Set default value for lastPlaybackRate
21580      _this.cache_.lastPlaybackRate = _this.defaultPlaybackRate();
21581
21582      // Make this an evented object and use `el_` as its event bus.
21583      evented(_this, { eventBusKey: 'el_' });
21584
21585      // We also want to pass the original player options to each component and plugin
21586      // as well so they don't need to reach back into the player for options later.
21587      // We also need to do another copy of this.options_ so we don't end up with
21588      // an infinite loop.
21589      var playerOptionsCopy = mergeOptions(_this.options_);
21590
21591      // Load plugins
21592      if (options.plugins) {
21593        var plugins = options.plugins;
21594
21595        Object.keys(plugins).forEach(function (name$$1) {
21596          if (typeof this[name$$1] === 'function') {
21597            this[name$$1](plugins[name$$1]);
21598          } else {
21599            throw new Error('plugin "' + name$$1 + '" does not exist');
21600          }
21601        }, _this);
21602      }
21603
21604      _this.options_.playerOptions = playerOptionsCopy;
21605
21606      _this.middleware_ = [];
21607
21608      _this.initChildren();
21609
21610      // Set isAudio based on whether or not an audio tag was used
21611      _this.isAudio(tag.nodeName.toLowerCase() === 'audio');
21612
21613      // Update controls className. Can't do this when the controls are initially
21614      // set because the element doesn't exist yet.
21615      if (_this.controls()) {
21616        _this.addClass('vjs-controls-enabled');
21617      } else {
21618        _this.addClass('vjs-controls-disabled');
21619      }
21620
21621      // Set ARIA label and region role depending on player type
21622      _this.el_.setAttribute('role', 'region');
21623      if (_this.isAudio()) {
21624        _this.el_.setAttribute('aria-label', _this.localize('Audio Player'));
21625      } else {
21626        _this.el_.setAttribute('aria-label', _this.localize('Video Player'));
21627      }
21628
21629      if (_this.isAudio()) {
21630        _this.addClass('vjs-audio');
21631      }
21632
21633      if (_this.flexNotSupported_()) {
21634        _this.addClass('vjs-no-flex');
21635      }
21636
21637      // TODO: Make this smarter. Toggle user state between touching/mousing
21638      // using events, since devices can have both touch and mouse events.
21639      // if (browser.TOUCH_ENABLED) {
21640      //   this.addClass('vjs-touch-enabled');
21641      // }
21642
21643      // iOS Safari has broken hover handling
21644      if (!IS_IOS) {
21645        _this.addClass('vjs-workinghover');
21646      }
21647
21648      // Make player easily findable by ID
21649      Player.players[_this.id_] = _this;
21650
21651      // Add a major version class to aid css in plugins
21652      var majorVersion = version.split('.')[0];
21653
21654      _this.addClass('vjs-v' + majorVersion);
21655
21656      // When the player is first initialized, trigger activity so components
21657      // like the control bar show themselves if needed
21658      _this.userActive(true);
21659      _this.reportUserActivity();
21660
21661      _this.one('play', _this.listenForUserActivity_);
21662      _this.on('fullscreenchange', _this.handleFullscreenChange_);
21663      _this.on('stageclick', _this.handleStageClick_);
21664
21665      _this.changingSrc_ = false;
21666      _this.playWaitingForReady_ = false;
21667      _this.playOnLoadstart_ = null;
21668      return _this;
21669    }
21670
21671    /**
21672     * Destroys the video player and does any necessary cleanup.
21673     *
21674     * This is especially helpful if you are dynamically adding and removing videos
21675     * to/from the DOM.
21676     *
21677     * @fires Player#dispose
21678     */
21679
21680
21681    Player.prototype.dispose = function dispose() {
21682      /**
21683       * Called when the player is being disposed of.
21684       *
21685       * @event Player#dispose
21686       * @type {EventTarget~Event}
21687       */
21688      this.trigger('dispose');
21689      // prevent dispose from being called twice
21690      this.off('dispose');
21691
21692      if (this.styleEl_ && this.styleEl_.parentNode) {
21693        this.styleEl_.parentNode.removeChild(this.styleEl_);
21694        this.styleEl_ = null;
21695      }
21696
21697      // Kill reference to this player
21698      Player.players[this.id_] = null;
21699
21700      if (this.tag && this.tag.player) {
21701        this.tag.player = null;
21702      }
21703
21704      if (this.el_ && this.el_.player) {
21705        this.el_.player = null;
21706      }
21707
21708      if (this.tech_) {
21709        this.tech_.dispose();
21710        this.isPosterFromTech_ = false;
21711        this.poster_ = '';
21712      }
21713
21714      if (this.playerElIngest_) {
21715        this.playerElIngest_ = null;
21716      }
21717
21718      if (this.tag) {
21719        this.tag = null;
21720      }
21721
21722      clearCacheForPlayer(this);
21723
21724      // the actual .el_ is removed here
21725      _Component.prototype.dispose.call(this);
21726    };
21727
21728    /**
21729     * Create the `Player`'s DOM element.
21730     *
21731     * @return {Element}
21732     *         The DOM element that gets created.
21733     */
21734
21735
21736    Player.prototype.createEl = function createEl$$1() {
21737      var tag = this.tag;
21738      var el = void 0;
21739      var playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player');
21740      var divEmbed = this.tag.tagName.toLowerCase() === 'video-js';
21741
21742      if (playerElIngest) {
21743        el = this.el_ = tag.parentNode;
21744      } else if (!divEmbed) {
21745        el = this.el_ = _Component.prototype.createEl.call(this, 'div');
21746      }
21747
21748      // Copy over all the attributes from the tag, including ID and class
21749      // ID will now reference player box, not the video tag
21750      var attrs = getAttributes(tag);
21751
21752      if (divEmbed) {
21753        el = this.el_ = tag;
21754        tag = this.tag = document_1.createElement('video');
21755        while (el.children.length) {
21756          tag.appendChild(el.firstChild);
21757        }
21758
21759        if (!hasClass(el, 'video-js')) {
21760          addClass(el, 'video-js');
21761        }
21762
21763        el.appendChild(tag);
21764
21765        playerElIngest = this.playerElIngest_ = el;
21766        // move properties over from our custom `video-js` element
21767        // to our new `video` element. This will move things like
21768        // `src` or `controls` that were set via js before the player
21769        // was initialized.
21770        Object.keys(el).forEach(function (k) {
21771          tag[k] = el[k];
21772        });
21773      }
21774
21775      // set tabindex to -1 to remove the video element from the focus order
21776      tag.setAttribute('tabindex', '-1');
21777      // Workaround for #4583 (JAWS+IE doesn't announce BPB or play button)
21778      // See https://github.com/FreedomScientific/VFO-standards-support/issues/78
21779      // Note that we can't detect if JAWS is being used, but this ARIA attribute
21780      //  doesn't change behavior of IE11 if JAWS is not being used
21781      if (IE_VERSION) {
21782        tag.setAttribute('role', 'application');
21783      }
21784
21785      // Remove width/height attrs from tag so CSS can make it 100% width/height
21786      tag.removeAttribute('width');
21787      tag.removeAttribute('height');
21788
21789      Object.getOwnPropertyNames(attrs).forEach(function (attr) {
21790        // don't copy over the class attribute to the player element when we're in a div embed
21791        // the class is already set up properly in the divEmbed case
21792        // and we want to make sure that the `video-js` class doesn't get lost
21793        if (!(divEmbed && attr === 'class')) {
21794          el.setAttribute(attr, attrs[attr]);
21795        }
21796
21797        if (divEmbed) {
21798          tag.setAttribute(attr, attrs[attr]);
21799        }
21800      });
21801
21802      // Update tag id/class for use as HTML5 playback tech
21803      // Might think we should do this after embedding in container so .vjs-tech class
21804      // doesn't flash 100% width/height, but class only applies with .video-js parent
21805      tag.playerId = tag.id;
21806      tag.id += '_html5_api';
21807      tag.className = 'vjs-tech';
21808
21809      // Make player findable on elements
21810      tag.player = el.player = this;
21811      // Default state of video is paused
21812      this.addClass('vjs-paused');
21813
21814      // Add a style element in the player that we'll use to set the width/height
21815      // of the player in a way that's still overrideable by CSS, just like the
21816      // video element
21817      if (window_1.VIDEOJS_NO_DYNAMIC_STYLE !== true) {
21818        this.styleEl_ = createStyleElement('vjs-styles-dimensions');
21819        var defaultsStyleEl = $('.vjs-styles-defaults');
21820        var head = $('head');
21821
21822        head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild);
21823      }
21824
21825      // Pass in the width/height/aspectRatio options which will update the style el
21826      this.width(this.options_.width);
21827      this.height(this.options_.height);
21828      this.fluid(this.options_.fluid);
21829      this.aspectRatio(this.options_.aspectRatio);
21830
21831      // Hide any links within the video/audio tag,
21832      // because IE doesn't hide them completely from screen readers.
21833      var links = tag.getElementsByTagName('a');
21834
21835      for (var i = 0; i < links.length; i++) {
21836        var linkEl = links.item(i);
21837
21838        addClass(linkEl, 'vjs-hidden');
21839        linkEl.setAttribute('hidden', 'hidden');
21840      }
21841
21842      // insertElFirst seems to cause the networkState to flicker from 3 to 2, so
21843      // keep track of the original for later so we can know if the source originally failed
21844      tag.initNetworkState_ = tag.networkState;
21845
21846      // Wrap video tag in div (el/box) container
21847      if (tag.parentNode && !playerElIngest) {
21848        tag.parentNode.insertBefore(el, tag);
21849      }
21850
21851      // insert the tag as the first child of the player element
21852      // then manually add it to the children array so that this.addChild
21853      // will work properly for other components
21854      //
21855      // Breaks iPhone, fixed in HTML5 setup.
21856      prependTo(tag, el);
21857      this.children_.unshift(tag);
21858
21859      // Set lang attr on player to ensure CSS :lang() in consistent with player
21860      // if it's been set to something different to the doc
21861      this.el_.setAttribute('lang', this.language_);
21862
21863      this.el_ = el;
21864
21865      return el;
21866    };
21867
21868    /**
21869     * A getter/setter for the `Player`'s width. Returns the player's configured value.
21870     * To get the current width use `currentWidth()`.
21871     *
21872     * @param {number} [value]
21873     *        The value to set the `Player`'s width to.
21874     *
21875     * @return {number}
21876     *         The current width of the `Player` when getting.
21877     */
21878
21879
21880    Player.prototype.width = function width(value) {
21881      return this.dimension('width', value);
21882    };
21883
21884    /**
21885     * A getter/setter for the `Player`'s height. Returns the player's configured value.
21886     * To get the current height use `currentheight()`.
21887     *
21888     * @param {number} [value]
21889     *        The value to set the `Player`'s heigth to.
21890     *
21891     * @return {number}
21892     *         The current height of the `Player` when getting.
21893     */
21894
21895
21896    Player.prototype.height = function height(value) {
21897      return this.dimension('height', value);
21898    };
21899
21900    /**
21901     * A getter/setter for the `Player`'s width & height.
21902     *
21903     * @param {string} dimension
21904     *        This string can be:
21905     *        - 'width'
21906     *        - 'height'
21907     *
21908     * @param {number} [value]
21909     *        Value for dimension specified in the first argument.
21910     *
21911     * @return {number}
21912     *         The dimension arguments value when getting (width/height).
21913     */
21914
21915
21916    Player.prototype.dimension = function dimension(_dimension, value) {
21917      var privDimension = _dimension + '_';
21918
21919      if (value === undefined) {
21920        return this[privDimension] || 0;
21921      }
21922
21923      if (value === '') {
21924        // If an empty string is given, reset the dimension to be automatic
21925        this[privDimension] = undefined;
21926        this.updateStyleEl_();
21927        return;
21928      }
21929
21930      var parsedVal = parseFloat(value);
21931
21932      if (isNaN(parsedVal)) {
21933        log$1.error('Improper value "' + value + '" supplied for for ' + _dimension);
21934        return;
21935      }
21936
21937      this[privDimension] = parsedVal;
21938      this.updateStyleEl_();
21939    };
21940
21941    /**
21942     * A getter/setter/toggler for the vjs-fluid `className` on the `Player`.
21943     *
21944     * @param {boolean} [bool]
21945     *        - A value of true adds the class.
21946     *        - A value of false removes the class.
21947     *        - No value will toggle the fluid class.
21948     *
21949     * @return {boolean|undefined}
21950     *         - The value of fluid when getting.
21951     *         - `undefined` when setting.
21952     */
21953
21954
21955    Player.prototype.fluid = function fluid(bool) {
21956      if (bool === undefined) {
21957        return !!this.fluid_;
21958      }
21959
21960      this.fluid_ = !!bool;
21961
21962      if (bool) {
21963        this.addClass('vjs-fluid');
21964      } else {
21965        this.removeClass('vjs-fluid');
21966      }
21967
21968      this.updateStyleEl_();
21969    };
21970
21971    /**
21972     * Get/Set the aspect ratio
21973     *
21974     * @param {string} [ratio]
21975     *        Aspect ratio for player
21976     *
21977     * @return {string|undefined}
21978     *         returns the current aspect ratio when getting
21979     */
21980
21981    /**
21982     * A getter/setter for the `Player`'s aspect ratio.
21983     *
21984     * @param {string} [ratio]
21985     *        The value to set the `Player's aspect ratio to.
21986     *
21987     * @return {string|undefined}
21988     *         - The current aspect ratio of the `Player` when getting.
21989     *         - undefined when setting
21990     */
21991
21992
21993    Player.prototype.aspectRatio = function aspectRatio(ratio) {
21994      if (ratio === undefined) {
21995        return this.aspectRatio_;
21996      }
21997
21998      // Check for width:height format
21999      if (!/^\d+\:\d+$/.test(ratio)) {
22000        throw new Error('Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.');
22001      }
22002      this.aspectRatio_ = ratio;
22003
22004      // We're assuming if you set an aspect ratio you want fluid mode,
22005      // because in fixed mode you could calculate width and height yourself.
22006      this.fluid(true);
22007
22008      this.updateStyleEl_();
22009    };
22010
22011    /**
22012     * Update styles of the `Player` element (height, width and aspect ratio).
22013     *
22014     * @private
22015     * @listens Tech#loadedmetadata
22016     */
22017
22018
22019    Player.prototype.updateStyleEl_ = function updateStyleEl_() {
22020      if (window_1.VIDEOJS_NO_DYNAMIC_STYLE === true) {
22021        var _width = typeof this.width_ === 'number' ? this.width_ : this.options_.width;
22022        var _height = typeof this.height_ === 'number' ? this.height_ : this.options_.height;
22023        var techEl = this.tech_ && this.tech_.el();
22024
22025        if (techEl) {
22026          if (_width >= 0) {
22027            techEl.width = _width;
22028          }
22029          if (_height >= 0) {
22030            techEl.height = _height;
22031          }
22032        }
22033
22034        return;
22035      }
22036
22037      var width = void 0;
22038      var height = void 0;
22039      var aspectRatio = void 0;
22040      var idClass = void 0;
22041
22042      // The aspect ratio is either used directly or to calculate width and height.
22043      if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') {
22044        // Use any aspectRatio that's been specifically set
22045        aspectRatio = this.aspectRatio_;
22046      } else if (this.videoWidth() > 0) {
22047        // Otherwise try to get the aspect ratio from the video metadata
22048        aspectRatio = this.videoWidth() + ':' + this.videoHeight();
22049      } else {
22050        // Or use a default. The video element's is 2:1, but 16:9 is more common.
22051        aspectRatio = '16:9';
22052      }
22053
22054      // Get the ratio as a decimal we can use to calculate dimensions
22055      var ratioParts = aspectRatio.split(':');
22056      var ratioMultiplier = ratioParts[1] / ratioParts[0];
22057
22058      if (this.width_ !== undefined) {
22059        // Use any width that's been specifically set
22060        width = this.width_;
22061      } else if (this.height_ !== undefined) {
22062        // Or calulate the width from the aspect ratio if a height has been set
22063        width = this.height_ / ratioMultiplier;
22064      } else {
22065        // Or use the video's metadata, or use the video el's default of 300
22066        width = this.videoWidth() || 300;
22067      }
22068
22069      if (this.height_ !== undefined) {
22070        // Use any height that's been specifically set
22071        height = this.height_;
22072      } else {
22073        // Otherwise calculate the height from the ratio and the width
22074        height = width * ratioMultiplier;
22075      }
22076
22077      // Ensure the CSS class is valid by starting with an alpha character
22078      if (/^[^a-zA-Z]/.test(this.id())) {
22079        idClass = 'dimensions-' + this.id();
22080      } else {
22081        idClass = this.id() + '-dimensions';
22082      }
22083
22084      // Ensure the right class is still on the player for the style element
22085      this.addClass(idClass);
22086
22087      setTextContent(this.styleEl_, '\n      .' + idClass + ' {\n        width: ' + width + 'px;\n        height: ' + height + 'px;\n      }\n\n      .' + idClass + '.vjs-fluid {\n        padding-top: ' + ratioMultiplier * 100 + '%;\n      }\n    ');
22088    };
22089
22090    /**
22091     * Load/Create an instance of playback {@link Tech} including element
22092     * and API methods. Then append the `Tech` element in `Player` as a child.
22093     *
22094     * @param {string} techName
22095     *        name of the playback technology
22096     *
22097     * @param {string} source
22098     *        video source
22099     *
22100     * @private
22101     */
22102
22103
22104    Player.prototype.loadTech_ = function loadTech_(techName, source) {
22105      var _this2 = this;
22106
22107      // Pause and remove current playback technology
22108      if (this.tech_) {
22109        this.unloadTech_();
22110      }
22111
22112      var titleTechName = toTitleCase(techName);
22113      var camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1);
22114
22115      // get rid of the HTML5 video tag as soon as we are using another tech
22116      if (titleTechName !== 'Html5' && this.tag) {
22117        Tech.getTech('Html5').disposeMediaElement(this.tag);
22118        this.tag.player = null;
22119        this.tag = null;
22120      }
22121
22122      this.techName_ = titleTechName;
22123
22124      // Turn off API access because we're loading a new tech that might load asynchronously
22125      this.isReady_ = false;
22126
22127      // Grab tech-specific options from player options and add source and parent element to use.
22128      var techOptions = {
22129        source: source,
22130        'nativeControlsForTouch': this.options_.nativeControlsForTouch,
22131        'playerId': this.id(),
22132        'techId': this.id() + '_' + titleTechName + '_api',
22133        'autoplay': this.options_.autoplay,
22134        'playsinline': this.options_.playsinline,
22135        'preload': this.options_.preload,
22136        'loop': this.options_.loop,
22137        'muted': this.options_.muted,
22138        'poster': this.poster(),
22139        'language': this.language(),
22140        'playerElIngest': this.playerElIngest_ || false,
22141        'vtt.js': this.options_['vtt.js'],
22142        'canOverridePoster': !!this.options_.techCanOverridePoster,
22143        'enableSourceset': this.options_.enableSourceset
22144      };
22145
22146      ALL.names.forEach(function (name$$1) {
22147        var props = ALL[name$$1];
22148
22149        techOptions[props.getterName] = _this2[props.privateName];
22150      });
22151
22152      assign(techOptions, this.options_[titleTechName]);
22153      assign(techOptions, this.options_[camelTechName]);
22154      assign(techOptions, this.options_[techName.toLowerCase()]);
22155
22156      if (this.tag) {
22157        techOptions.tag = this.tag;
22158      }
22159
22160      if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) {
22161        techOptions.startTime = this.cache_.currentTime;
22162      }
22163
22164      // Initialize tech instance
22165      var TechClass = Tech.getTech(techName);
22166
22167      if (!TechClass) {
22168        throw new Error('No Tech named \'' + titleTechName + '\' exists! \'' + titleTechName + '\' should be registered using videojs.registerTech()\'');
22169      }
22170
22171      this.tech_ = new TechClass(techOptions);
22172
22173      // player.triggerReady is always async, so don't need this to be async
22174      this.tech_.ready(bind(this, this.handleTechReady_), true);
22175
22176      textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech_);
22177
22178      // Listen to all HTML5-defined events and trigger them on the player
22179      TECH_EVENTS_RETRIGGER.forEach(function (event) {
22180        _this2.on(_this2.tech_, event, _this2['handleTech' + toTitleCase(event) + '_']);
22181      });
22182
22183      Object.keys(TECH_EVENTS_QUEUE).forEach(function (event) {
22184        _this2.on(_this2.tech_, event, function (eventObj) {
22185          if (_this2.tech_.playbackRate() === 0 && _this2.tech_.seeking()) {
22186            _this2.queuedCallbacks_.push({
22187              callback: _this2['handleTech' + TECH_EVENTS_QUEUE[event] + '_'].bind(_this2),
22188              event: eventObj
22189            });
22190            return;
22191          }
22192          _this2['handleTech' + TECH_EVENTS_QUEUE[event] + '_'](eventObj);
22193        });
22194      });
22195
22196      this.on(this.tech_, 'loadstart', this.handleTechLoadStart_);
22197      this.on(this.tech_, 'sourceset', this.handleTechSourceset_);
22198      this.on(this.tech_, 'waiting', this.handleTechWaiting_);
22199      this.on(this.tech_, 'ended', this.handleTechEnded_);
22200      this.on(this.tech_, 'seeking', this.handleTechSeeking_);
22201      this.on(this.tech_, 'play', this.handleTechPlay_);
22202      this.on(this.tech_, 'firstplay', this.handleTechFirstPlay_);
22203      this.on(this.tech_, 'pause', this.handleTechPause_);
22204      this.on(this.tech_, 'durationchange', this.handleTechDurationChange_);
22205      this.on(this.tech_, 'fullscreenchange', this.handleTechFullscreenChange_);
22206      this.on(this.tech_, 'error', this.handleTechError_);
22207      this.on(this.tech_, 'loadedmetadata', this.updateStyleEl_);
22208      this.on(this.tech_, 'posterchange', this.handleTechPosterChange_);
22209      this.on(this.tech_, 'textdata', this.handleTechTextData_);
22210      this.on(this.tech_, 'ratechange', this.handleTechRateChange_);
22211
22212      this.usingNativeControls(this.techGet_('controls'));
22213
22214      if (this.controls() && !this.usingNativeControls()) {
22215        this.addTechControlsListeners_();
22216      }
22217
22218      // Add the tech element in the DOM if it was not already there
22219      // Make sure to not insert the original video element if using Html5
22220      if (this.tech_.el().parentNode !== this.el() && (titleTechName !== 'Html5' || !this.tag)) {
22221        prependTo(this.tech_.el(), this.el());
22222      }
22223
22224      // Get rid of the original video tag reference after the first tech is loaded
22225      if (this.tag) {
22226        this.tag.player = null;
22227        this.tag = null;
22228      }
22229    };
22230
22231    /**
22232     * Unload and dispose of the current playback {@link Tech}.
22233     *
22234     * @private
22235     */
22236
22237
22238    Player.prototype.unloadTech_ = function unloadTech_() {
22239      var _this3 = this;
22240
22241      // Save the current text tracks so that we can reuse the same text tracks with the next tech
22242      ALL.names.forEach(function (name$$1) {
22243        var props = ALL[name$$1];
22244
22245        _this3[props.privateName] = _this3[props.getterName]();
22246      });
22247      this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_);
22248
22249      this.isReady_ = false;
22250
22251      this.tech_.dispose();
22252
22253      this.tech_ = false;
22254
22255      if (this.isPosterFromTech_) {
22256        this.poster_ = '';
22257        this.trigger('posterchange');
22258      }
22259
22260      this.isPosterFromTech_ = false;
22261    };
22262
22263    /**
22264     * Return a reference to the current {@link Tech}.
22265     * It will print a warning by default about the danger of using the tech directly
22266     * but any argument that is passed in will silence the warning.
22267     *
22268     * @param {*} [safety]
22269     *        Anything passed in to silence the warning
22270     *
22271     * @return {Tech}
22272     *         The Tech
22273     */
22274
22275
22276    Player.prototype.tech = function tech(safety) {
22277      if (safety === undefined) {
22278        log$1.warn(tsml(_templateObject$2));
22279      }
22280
22281      return this.tech_;
22282    };
22283
22284    /**
22285     * Set up click and touch listeners for the playback element
22286     *
22287     * - On desktops: a click on the video itself will toggle playback
22288     * - On mobile devices: a click on the video toggles controls
22289     *   which is done by toggling the user state between active and
22290     *   inactive
22291     * - A tap can signal that a user has become active or has become inactive
22292     *   e.g. a quick tap on an iPhone movie should reveal the controls. Another
22293     *   quick tap should hide them again (signaling the user is in an inactive
22294     *   viewing state)
22295     * - In addition to this, we still want the user to be considered inactive after
22296     *   a few seconds of inactivity.
22297     *
22298     * > Note: the only part of iOS interaction we can't mimic with this setup
22299     * is a touch and hold on the video element counting as activity in order to
22300     * keep the controls showing, but that shouldn't be an issue. A touch and hold
22301     * on any controls will still keep the user active
22302     *
22303     * @private
22304     */
22305
22306
22307    Player.prototype.addTechControlsListeners_ = function addTechControlsListeners_() {
22308      // Make sure to remove all the previous listeners in case we are called multiple times.
22309      this.removeTechControlsListeners_();
22310
22311      // Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do
22312      // trigger mousedown/up.
22313      // http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object
22314      // Any touch events are set to block the mousedown event from happening
22315      this.on(this.tech_, 'mousedown', this.handleTechClick_);
22316
22317      // If the controls were hidden we don't want that to change without a tap event
22318      // so we'll check if the controls were already showing before reporting user
22319      // activity
22320      this.on(this.tech_, 'touchstart', this.handleTechTouchStart_);
22321      this.on(this.tech_, 'touchmove', this.handleTechTouchMove_);
22322      this.on(this.tech_, 'touchend', this.handleTechTouchEnd_);
22323
22324      // The tap listener needs to come after the touchend listener because the tap
22325      // listener cancels out any reportedUserActivity when setting userActive(false)
22326      this.on(this.tech_, 'tap', this.handleTechTap_);
22327    };
22328
22329    /**
22330     * Remove the listeners used for click and tap controls. This is needed for
22331     * toggling to controls disabled, where a tap/touch should do nothing.
22332     *
22333     * @private
22334     */
22335
22336
22337    Player.prototype.removeTechControlsListeners_ = function removeTechControlsListeners_() {
22338      // We don't want to just use `this.off()` because there might be other needed
22339      // listeners added by techs that extend this.
22340      this.off(this.tech_, 'tap', this.handleTechTap_);
22341      this.off(this.tech_, 'touchstart', this.handleTechTouchStart_);
22342      this.off(this.tech_, 'touchmove', this.handleTechTouchMove_);
22343      this.off(this.tech_, 'touchend', this.handleTechTouchEnd_);
22344      this.off(this.tech_, 'mousedown', this.handleTechClick_);
22345    };
22346
22347    /**
22348     * Player waits for the tech to be ready
22349     *
22350     * @private
22351     */
22352
22353
22354    Player.prototype.handleTechReady_ = function handleTechReady_() {
22355      this.triggerReady();
22356
22357      // Keep the same volume as before
22358      if (this.cache_.volume) {
22359        this.techCall_('setVolume', this.cache_.volume);
22360      }
22361
22362      // Look if the tech found a higher resolution poster while loading
22363      this.handleTechPosterChange_();
22364
22365      // Update the duration if available
22366      this.handleTechDurationChange_();
22367
22368      // Chrome and Safari both have issues with autoplay.
22369      // In Safari (5.1.1), when we move the video element into the container div, autoplay doesn't work.
22370      // In Chrome (15), if you have autoplay + a poster + no controls, the video gets hidden (but audio plays)
22371      // This fixes both issues. Need to wait for API, so it updates displays correctly
22372      if ((this.src() || this.currentSrc()) && this.tag && this.options_.autoplay && this.paused()) {
22373        try {
22374          // Chrome Fix. Fixed in Chrome v16.
22375          delete this.tag.poster;
22376        } catch (e) {
22377          log$1('deleting tag.poster throws in some browsers', e);
22378        }
22379      }
22380    };
22381
22382    /**
22383     * Retrigger the `loadstart` event that was triggered by the {@link Tech}. This
22384     * function will also trigger {@link Player#firstplay} if it is the first loadstart
22385     * for a video.
22386     *
22387     * @fires Player#loadstart
22388     * @fires Player#firstplay
22389     * @listens Tech#loadstart
22390     * @private
22391     */
22392
22393
22394    Player.prototype.handleTechLoadStart_ = function handleTechLoadStart_() {
22395      // TODO: Update to use `emptied` event instead. See #1277.
22396
22397      this.removeClass('vjs-ended');
22398      this.removeClass('vjs-seeking');
22399
22400      // reset the error state
22401      this.error(null);
22402
22403      // If it's already playing we want to trigger a firstplay event now.
22404      // The firstplay event relies on both the play and loadstart events
22405      // which can happen in any order for a new source
22406      if (!this.paused()) {
22407        /**
22408         * Fired when the user agent begins looking for media data
22409         *
22410         * @event Player#loadstart
22411         * @type {EventTarget~Event}
22412         */
22413        this.trigger('loadstart');
22414        this.trigger('firstplay');
22415      } else {
22416        // reset the hasStarted state
22417        this.hasStarted(false);
22418        this.trigger('loadstart');
22419      }
22420    };
22421
22422    /**
22423     * Update the internal source caches so that we return the correct source from
22424     * `src()`, `currentSource()`, and `currentSources()`.
22425     *
22426     * > Note: `currentSources` will not be updated if the source that is passed in exists
22427     *         in the current `currentSources` cache.
22428     *
22429     *
22430     * @param {Tech~SourceObject} srcObj
22431     *        A string or object source to update our caches to.
22432     */
22433
22434
22435    Player.prototype.updateSourceCaches_ = function updateSourceCaches_() {
22436      var srcObj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
22437
22438
22439      var src = srcObj;
22440      var type = '';
22441
22442      if (typeof src !== 'string') {
22443        src = srcObj.src;
22444        type = srcObj.type;
22445      }
22446      // make sure all the caches are set to default values
22447      // to prevent null checking
22448      this.cache_.source = this.cache_.source || {};
22449      this.cache_.sources = this.cache_.sources || [];
22450
22451      // try to get the type of the src that was passed in
22452      if (src && !type) {
22453        type = findMimetype(this, src);
22454      }
22455
22456      // update `currentSource` cache always
22457      this.cache_.source = mergeOptions({}, srcObj, { src: src, type: type });
22458
22459      var matchingSources = this.cache_.sources.filter(function (s) {
22460        return s.src && s.src === src;
22461      });
22462      var sourceElSources = [];
22463      var sourceEls = this.$$('source');
22464      var matchingSourceEls = [];
22465
22466      for (var i = 0; i < sourceEls.length; i++) {
22467        var sourceObj = getAttributes(sourceEls[i]);
22468
22469        sourceElSources.push(sourceObj);
22470
22471        if (sourceObj.src && sourceObj.src === src) {
22472          matchingSourceEls.push(sourceObj.src);
22473        }
22474      }
22475
22476      // if we have matching source els but not matching sources
22477      // the current source cache is not up to date
22478      if (matchingSourceEls.length && !matchingSources.length) {
22479        this.cache_.sources = sourceElSources;
22480        // if we don't have matching source or source els set the
22481        // sources cache to the `currentSource` cache
22482      } else if (!matchingSources.length) {
22483        this.cache_.sources = [this.cache_.source];
22484      }
22485
22486      // update the tech `src` cache
22487      this.cache_.src = src;
22488    };
22489
22490    /**
22491     * *EXPERIMENTAL* Fired when the source is set or changed on the {@link Tech}
22492     * causing the media element to reload.
22493     *
22494     * It will fire for the initial source and each subsequent source.
22495     * This event is a custom event from Video.js and is triggered by the {@link Tech}.
22496     *
22497     * The event object for this event contains a `src` property that will contain the source
22498     * that was available when the event was triggered. This is generally only necessary if Video.js
22499     * is switching techs while the source was being changed.
22500     *
22501     * It is also fired when `load` is called on the player (or media element)
22502     * because the {@link https://html.spec.whatwg.org/multipage/media.html#dom-media-load|specification for `load`}
22503     * says that the resource selection algorithm needs to be aborted and restarted.
22504     * In this case, it is very likely that the `src` property will be set to the
22505     * empty string `""` to indicate we do not know what the source will be but
22506     * that it is changing.
22507     *
22508     * *This event is currently still experimental and may change in minor releases.*
22509     * __To use this, pass `enableSourceset` option to the player.__
22510     *
22511     * @event Player#sourceset
22512     * @type {EventTarget~Event}
22513     * @prop {string} src
22514     *                The source url available when the `sourceset` was triggered.
22515     *                It will be an empty string if we cannot know what the source is
22516     *                but know that the source will change.
22517     */
22518    /**
22519     * Retrigger the `sourceset` event that was triggered by the {@link Tech}.
22520     *
22521     * @fires Player#sourceset
22522     * @listens Tech#sourceset
22523     * @private
22524     */
22525
22526
22527    Player.prototype.handleTechSourceset_ = function handleTechSourceset_(event) {
22528      var _this4 = this;
22529
22530      // only update the source cache when the source
22531      // was not updated using the player api
22532      if (!this.changingSrc_) {
22533        // update the source to the intial source right away
22534        // in some cases this will be empty string
22535        this.updateSourceCaches_(event.src);
22536
22537        // if the `sourceset` `src` was an empty string
22538        // wait for a `loadstart` to update the cache to `currentSrc`.
22539        // If a sourceset happens before a `loadstart`, we reset the state
22540        // as this function will be called again.
22541        if (!event.src) {
22542          var updateCache = function updateCache(e) {
22543            if (e.type !== 'sourceset') {
22544              _this4.updateSourceCaches_(_this4.techGet_('currentSrc'));
22545            }
22546
22547            _this4.tech_.off(['sourceset', 'loadstart'], updateCache);
22548          };
22549
22550          this.tech_.one(['sourceset', 'loadstart'], updateCache);
22551        }
22552      }
22553
22554      this.trigger({
22555        src: event.src,
22556        type: 'sourceset'
22557      });
22558    };
22559
22560    /**
22561     * Add/remove the vjs-has-started class
22562     *
22563     * @fires Player#firstplay
22564     *
22565     * @param {boolean} request
22566     *        - true: adds the class
22567     *        - false: remove the class
22568     *
22569     * @return {boolean}
22570     *         the boolean value of hasStarted_
22571     */
22572
22573
22574    Player.prototype.hasStarted = function hasStarted(request) {
22575      if (request === undefined) {
22576        // act as getter, if we have no request to change
22577        return this.hasStarted_;
22578      }
22579
22580      if (request === this.hasStarted_) {
22581        return;
22582      }
22583
22584      this.hasStarted_ = request;
22585
22586      if (this.hasStarted_) {
22587        this.addClass('vjs-has-started');
22588        this.trigger('firstplay');
22589      } else {
22590        this.removeClass('vjs-has-started');
22591      }
22592    };
22593
22594    /**
22595     * Fired whenever the media begins or resumes playback
22596     *
22597     * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play}
22598     * @fires Player#play
22599     * @listens Tech#play
22600     * @private
22601     */
22602
22603
22604    Player.prototype.handleTechPlay_ = function handleTechPlay_() {
22605      this.removeClass('vjs-ended');
22606      this.removeClass('vjs-paused');
22607      this.addClass('vjs-playing');
22608
22609      // hide the poster when the user hits play
22610      this.hasStarted(true);
22611      /**
22612       * Triggered whenever an {@link Tech#play} event happens. Indicates that
22613       * playback has started or resumed.
22614       *
22615       * @event Player#play
22616       * @type {EventTarget~Event}
22617       */
22618      this.trigger('play');
22619    };
22620
22621    /**
22622     * Retrigger the `ratechange` event that was triggered by the {@link Tech}.
22623     *
22624     * If there were any events queued while the playback rate was zero, fire
22625     * those events now.
22626     *
22627     * @private
22628     * @method Player#handleTechRateChange_
22629     * @fires Player#ratechange
22630     * @listens Tech#ratechange
22631     */
22632
22633
22634    Player.prototype.handleTechRateChange_ = function handleTechRateChange_() {
22635      if (this.tech_.playbackRate() > 0 && this.cache_.lastPlaybackRate === 0) {
22636        this.queuedCallbacks_.forEach(function (queued) {
22637          return queued.callback(queued.event);
22638        });
22639        this.queuedCallbacks_ = [];
22640      }
22641      this.cache_.lastPlaybackRate = this.tech_.playbackRate();
22642      /**
22643       * Fires when the playing speed of the audio/video is changed
22644       *
22645       * @event Player#ratechange
22646       * @type {event}
22647       */
22648      this.trigger('ratechange');
22649    };
22650
22651    /**
22652     * Retrigger the `waiting` event that was triggered by the {@link Tech}.
22653     *
22654     * @fires Player#waiting
22655     * @listens Tech#waiting
22656     * @private
22657     */
22658
22659
22660    Player.prototype.handleTechWaiting_ = function handleTechWaiting_() {
22661      var _this5 = this;
22662
22663      this.addClass('vjs-waiting');
22664      /**
22665       * A readyState change on the DOM element has caused playback to stop.
22666       *
22667       * @event Player#waiting
22668       * @type {EventTarget~Event}
22669       */
22670      this.trigger('waiting');
22671      this.one('timeupdate', function () {
22672        return _this5.removeClass('vjs-waiting');
22673      });
22674    };
22675
22676    /**
22677     * Retrigger the `canplay` event that was triggered by the {@link Tech}.
22678     * > Note: This is not consistent between browsers. See #1351
22679     *
22680     * @fires Player#canplay
22681     * @listens Tech#canplay
22682     * @private
22683     */
22684
22685
22686    Player.prototype.handleTechCanPlay_ = function handleTechCanPlay_() {
22687      this.removeClass('vjs-waiting');
22688      /**
22689       * The media has a readyState of HAVE_FUTURE_DATA or greater.
22690       *
22691       * @event Player#canplay
22692       * @type {EventTarget~Event}
22693       */
22694      this.trigger('canplay');
22695    };
22696
22697    /**
22698     * Retrigger the `canplaythrough` event that was triggered by the {@link Tech}.
22699     *
22700     * @fires Player#canplaythrough
22701     * @listens Tech#canplaythrough
22702     * @private
22703     */
22704
22705
22706    Player.prototype.handleTechCanPlayThrough_ = function handleTechCanPlayThrough_() {
22707      this.removeClass('vjs-waiting');
22708      /**
22709       * The media has a readyState of HAVE_ENOUGH_DATA or greater. This means that the
22710       * entire media file can be played without buffering.
22711       *
22712       * @event Player#canplaythrough
22713       * @type {EventTarget~Event}
22714       */
22715      this.trigger('canplaythrough');
22716    };
22717
22718    /**
22719     * Retrigger the `playing` event that was triggered by the {@link Tech}.
22720     *
22721     * @fires Player#playing
22722     * @listens Tech#playing
22723     * @private
22724     */
22725
22726
22727    Player.prototype.handleTechPlaying_ = function handleTechPlaying_() {
22728      this.removeClass('vjs-waiting');
22729      /**
22730       * The media is no longer blocked from playback, and has started playing.
22731       *
22732       * @event Player#playing
22733       * @type {EventTarget~Event}
22734       */
22735      this.trigger('playing');
22736    };
22737
22738    /**
22739     * Retrigger the `seeking` event that was triggered by the {@link Tech}.
22740     *
22741     * @fires Player#seeking
22742     * @listens Tech#seeking
22743     * @private
22744     */
22745
22746
22747    Player.prototype.handleTechSeeking_ = function handleTechSeeking_() {
22748      this.addClass('vjs-seeking');
22749      /**
22750       * Fired whenever the player is jumping to a new time
22751       *
22752       * @event Player#seeking
22753       * @type {EventTarget~Event}
22754       */
22755      this.trigger('seeking');
22756    };
22757
22758    /**
22759     * Retrigger the `seeked` event that was triggered by the {@link Tech}.
22760     *
22761     * @fires Player#seeked
22762     * @listens Tech#seeked
22763     * @private
22764     */
22765
22766
22767    Player.prototype.handleTechSeeked_ = function handleTechSeeked_() {
22768      this.removeClass('vjs-seeking');
22769      /**
22770       * Fired when the player has finished jumping to a new time
22771       *
22772       * @event Player#seeked
22773       * @type {EventTarget~Event}
22774       */
22775      this.trigger('seeked');
22776    };
22777
22778    /**
22779     * Retrigger the `firstplay` event that was triggered by the {@link Tech}.
22780     *
22781     * @fires Player#firstplay
22782     * @listens Tech#firstplay
22783     * @deprecated As of 6.0 firstplay event is deprecated.
22784     * @deprecated As of 6.0 passing the `starttime` option to the player and the firstplay event are deprecated.
22785     * @private
22786     */
22787
22788
22789    Player.prototype.handleTechFirstPlay_ = function handleTechFirstPlay_() {
22790      // If the first starttime attribute is specified
22791      // then we will start at the given offset in seconds
22792      if (this.options_.starttime) {
22793        log$1.warn('Passing the `starttime` option to the player will be deprecated 
vendor: 18,934 bytes, lines 22793-23493
22793in 6.0');
22794        this.currentTime(this.options_.starttime);
22795      }
22796
22797      this.addClass('vjs-has-started');
22798      /**
22799       * Fired the first time a video is played. Not part of the HLS spec, and this is
22800       * probably not the best implementation yet, so use sparingly. If you don't have a
22801       * reason to prevent playback, use `myPlayer.one('play');` instead.
22802       *
22803       * @event Player#firstplay
22804       * @deprecated As of 6.0 firstplay event is deprecated.
22805       * @type {EventTarget~Event}
22806       */
22807      this.trigger('firstplay');
22808    };
22809
22810    /**
22811     * Retrigger the `pause` event that was triggered by the {@link Tech}.
22812     *
22813     * @fires Player#pause
22814     * @listens Tech#pause
22815     * @private
22816     */
22817
22818
22819    Player.prototype.handleTechPause_ = function handleTechPause_() {
22820      this.removeClass('vjs-playing');
22821      this.addClass('vjs-paused');
22822      /**
22823       * Fired whenever the media has been paused
22824       *
22825       * @event Player#pause
22826       * @type {EventTarget~Event}
22827       */
22828      this.trigger('pause');
22829    };
22830
22831    /**
22832     * Retrigger the `ended` event that was triggered by the {@link Tech}.
22833     *
22834     * @fires Player#ended
22835     * @listens Tech#ended
22836     * @private
22837     */
22838
22839
22840    Player.prototype.handleTechEnded_ = function handleTechEnded_() {
22841      this.addClass('vjs-ended');
22842      if (this.options_.loop) {
22843        this.currentTime(0);
22844        this.play();
22845      } else if (!this.paused()) {
22846        this.pause();
22847      }
22848
22849      /**
22850       * Fired when the end of the media resource is reached (currentTime == duration)
22851       *
22852       * @event Player#ended
22853       * @type {EventTarget~Event}
22854       */
22855      this.trigger('ended');
22856    };
22857
22858    /**
22859     * Fired when the duration of the media resource is first known or changed
22860     *
22861     * @listens Tech#durationchange
22862     * @private
22863     */
22864
22865
22866    Player.prototype.handleTechDurationChange_ = function handleTechDurationChange_() {
22867      this.duration(this.techGet_('duration'));
22868    };
22869
22870    /**
22871     * Handle a click on the media element to play/pause
22872     *
22873     * @param {EventTarget~Event} event
22874     *        the event that caused this function to trigger
22875     *
22876     * @listens Tech#mousedown
22877     * @private
22878     */
22879
22880
22881    Player.prototype.handleTechClick_ = function handleTechClick_(event) {
22882      if (!isSingleLeftClick(event)) {
22883        return;
22884      }
22885
22886      // When controls are disabled a click should not toggle playback because
22887      // the click is considered a control
22888      if (!this.controls_) {
22889        return;
22890      }
22891
22892      if (this.paused()) {
22893        silencePromise(this.play());
22894      } else {
22895        this.pause();
22896      }
22897    };
22898
22899    /**
22900     * Handle a tap on the media element. It will toggle the user
22901     * activity state, which hides and shows the controls.
22902     *
22903     * @listens Tech#tap
22904     * @private
22905     */
22906
22907
22908    Player.prototype.handleTechTap_ = function handleTechTap_() {
22909      this.userActive(!this.userActive());
22910    };
22911
22912    /**
22913     * Handle touch to start
22914     *
22915     * @listens Tech#touchstart
22916     * @private
22917     */
22918
22919
22920    Player.prototype.handleTechTouchStart_ = function handleTechTouchStart_() {
22921      this.userWasActive = this.userActive();
22922    };
22923
22924    /**
22925     * Handle touch to move
22926     *
22927     * @listens Tech#touchmove
22928     * @private
22929     */
22930
22931
22932    Player.prototype.handleTechTouchMove_ = function handleTechTouchMove_() {
22933      if (this.userWasActive) {
22934        this.reportUserActivity();
22935      }
22936    };
22937
22938    /**
22939     * Handle touch to end
22940     *
22941     * @param {EventTarget~Event} event
22942     *        the touchend event that triggered
22943     *        this function
22944     *
22945     * @listens Tech#touchend
22946     * @private
22947     */
22948
22949
22950    Player.prototype.handleTechTouchEnd_ = function handleTechTouchEnd_(event) {
22951      // Stop the mouse events from also happening
22952      event.preventDefault();
22953    };
22954
22955    /**
22956     * Fired when the player switches in or out of fullscreen mode
22957     *
22958     * @private
22959     * @listens Player#fullscreenchange
22960     */
22961
22962
22963    Player.prototype.handleFullscreenChange_ = function handleFullscreenChange_() {
22964      if (this.isFullscreen()) {
22965        this.addClass('vjs-fullscreen');
22966      } else {
22967        this.removeClass('vjs-fullscreen');
22968      }
22969    };
22970
22971    /**
22972     * native click events on the SWF aren't triggered on IE11, Win8.1RT
22973     * use stageclick events triggered from inside the SWF instead
22974     *
22975     * @private
22976     * @listens stageclick
22977     */
22978
22979
22980    Player.prototype.handleStageClick_ = function handleStageClick_() {
22981      this.reportUserActivity();
22982    };
22983
22984    /**
22985     * Handle Tech Fullscreen Change
22986     *
22987     * @param {EventTarget~Event} event
22988     *        the fullscreenchange event that triggered this function
22989     *
22990     * @param {Object} data
22991     *        the data that was sent with the event
22992     *
22993     * @private
22994     * @listens Tech#fullscreenchange
22995     * @fires Player#fullscreenchange
22996     */
22997
22998
22999    Player.prototype.handleTechFullscreenChange_ = function handleTechFullscreenChange_(event, data) {
23000      if (data) {
23001        this.isFullscreen(data.isFullscreen);
23002      }
23003      /**
23004       * Fired when going in and out of fullscreen.
23005       *
23006       * @event Player#fullscreenchange
23007       * @type {EventTarget~Event}
23008       */
23009      this.trigger('fullscreenchange');
23010    };
23011
23012    /**
23013     * Fires when an error occurred during the loading of an audio/video.
23014     *
23015     * @private
23016     * @listens Tech#error
23017     */
23018
23019
23020    Player.prototype.handleTechError_ = function handleTechError_() {
23021      var error = this.tech_.error();
23022
23023      this.error(error);
23024    };
23025
23026    /**
23027     * Retrigger the `textdata` event that was triggered by the {@link Tech}.
23028     *
23029     * @fires Player#textdata
23030     * @listens Tech#textdata
23031     * @private
23032     */
23033
23034
23035    Player.prototype.handleTechTextData_ = function handleTechTextData_() {
23036      var data = null;
23037
23038      if (arguments.length > 1) {
23039        data = arguments[1];
23040      }
23041
23042      /**
23043       * Fires when we get a textdata event from tech
23044       *
23045       * @event Player#textdata
23046       * @type {EventTarget~Event}
23047       */
23048      this.trigger('textdata', data);
23049    };
23050
23051    /**
23052     * Get object for cached values.
23053     *
23054     * @return {Object}
23055     *         get the current object cache
23056     */
23057
23058
23059    Player.prototype.getCache = function getCache() {
23060      return this.cache_;
23061    };
23062
23063    /**
23064     * Pass values to the playback tech
23065     *
23066     * @param {string} [method]
23067     *        the method to call
23068     *
23069     * @param {Object} arg
23070     *        the argument to pass
23071     *
23072     * @private
23073     */
23074
23075
23076    Player.prototype.techCall_ = function techCall_(method, arg) {
23077      // If it's not ready yet, call method when it is
23078
23079      this.ready(function () {
23080        if (method in allowedSetters) {
23081          return set$1(this.middleware_, this.tech_, method, arg);
23082        } else if (method in allowedMediators) {
23083          return mediate(this.middleware_, this.tech_, method, arg);
23084        }
23085
23086        try {
23087          if (this.tech_) {
23088            this.tech_[method](arg);
23089          }
23090        } catch (e) {
23091          log$1(e);
23092          throw e;
23093        }
23094      }, true);
23095    };
23096
23097    /**
23098     * Get calls can't wait for the tech, and sometimes don't need to.
23099     *
23100     * @param {string} method
23101     *        Tech method
23102     *
23103     * @return {Function|undefined}
23104     *         the method or undefined
23105     *
23106     * @private
23107     */
23108
23109
23110    Player.prototype.techGet_ = function techGet_(method) {
23111      if (!this.tech_ || !this.tech_.isReady_) {
23112        return;
23113      }
23114
23115      if (method in allowedGetters) {
23116        return get$1(this.middleware_, this.tech_, method);
23117      } else if (method in allowedMediators) {
23118        return mediate(this.middleware_, this.tech_, method);
23119      }
23120
23121      // Flash likes to die and reload when you hide or reposition it.
23122      // In these cases the object methods go away and we get errors.
23123      // When that happens we'll catch the errors and inform tech that it's not ready any more.
23124      try {
23125        return this.tech_[method]();
23126      } catch (e) {
23127
23128        // When building additional tech libs, an expected method may not be defined yet
23129        if (this.tech_[method] === undefined) {
23130          log$1('Video.js: ' + method + ' method not defined for ' + this.techName_ + ' playback technology.', e);
23131          throw e;
23132        }
23133
23134        // When a method isn't available on the object it throws a TypeError
23135        if (e.name === 'TypeError') {
23136          log$1('Video.js: ' + method + ' unavailable on ' + this.techName_ + ' playback technology element.', e);
23137          this.tech_.isReady_ = false;
23138          throw e;
23139        }
23140
23141        // If error unknown, just log and throw
23142        log$1(e);
23143        throw e;
23144      }
23145    };
23146
23147    /**
23148     * Attempt to begin playback at the first opportunity.
23149     *
23150     * @return {Promise|undefined}
23151     *         Returns a `Promise` only if the browser returns one and the player
23152     *         is ready to begin playback. For some browsers and all non-ready
23153     *         situations, this will return `undefined`.
23154     */
23155
23156
23157    Player.prototype.play = function play() {
23158      var _this6 = this;
23159
23160      // If this is called while we have a play queued up on a loadstart, remove
23161      // that listener to avoid getting in a potentially bad state.
23162      if (this.playOnLoadstart_) {
23163        this.off('loadstart', this.playOnLoadstart_);
23164      }
23165
23166      // If the player/tech is not ready, queue up another call to `play()` for
23167      // when it is. This will loop back into this method for another attempt at
23168      // playback when the tech is ready.
23169      if (!this.isReady_) {
23170
23171        // Bail out if we're already waiting for `ready`!
23172        if (this.playWaitingForReady_) {
23173          return;
23174        }
23175
23176        this.playWaitingForReady_ = true;
23177        this.ready(function () {
23178          _this6.playWaitingForReady_ = false;
23179          silencePromise(_this6.play());
23180        });
23181
23182        // If the player/tech is ready and we have a source, we can attempt playback.
23183      } else if (!this.changingSrc_ && (this.src() || this.currentSrc())) {
23184        return this.techGet_('play');
23185
23186        // If the tech is ready, but we do not have a source, we'll need to wait
23187        // for both the `ready` and a `loadstart` when the source is finally
23188        // resolved by middleware and set on the player.
23189        //
23190        // This can happen if `play()` is called while changing sources or before
23191        // one has been set on the player.
23192      } else {
23193
23194        this.playOnLoadstart_ = function () {
23195          _this6.playOnLoadstart_ = null;
23196          silencePromise(_this6.play());
23197        };
23198
23199        this.one('loadstart', this.playOnLoadstart_);
23200      }
23201    };
23202
23203    /**
23204     * Pause the video playback
23205     *
23206     * @return {Player}
23207     *         A reference to the player object this function was called on
23208     */
23209
23210
23211    Player.prototype.pause = function pause() {
23212      this.techCall_('pause');
23213    };
23214
23215    /**
23216     * Check if the player is paused or has yet to play
23217     *
23218     * @return {boolean}
23219     *         - false: if the media is currently playing
23220     *         - true: if media is not currently playing
23221     */
23222
23223
23224    Player.prototype.paused = function paused() {
23225      // The initial state of paused should be true (in Safari it's actually false)
23226      return this.techGet_('paused') === false ? false : true;
23227    };
23228
23229    /**
23230     * Get a TimeRange object representing the current ranges of time that the user
23231     * has played.
23232     *
23233     * @return {TimeRange}
23234     *         A time range object that represents all the increments of time that have
23235     *         been played.
23236     */
23237
23238
23239    Player.prototype.played = function played() {
23240      return this.techGet_('played') || createTimeRanges(0, 0);
23241    };
23242
23243    /**
23244     * Returns whether or not the user is "scrubbing". Scrubbing is
23245     * when the user has clicked the progress bar handle and is
23246     * dragging it along the progress bar.
23247     *
23248     * @param {boolean} [isScrubbing]
23249     *        whether the user is or is not scrubbing
23250     *
23251     * @return {boolean}
23252     *         The value of scrubbing when getting
23253     */
23254
23255
23256    Player.prototype.scrubbing = function scrubbing(isScrubbing) {
23257      if (typeof isScrubbing === 'undefined') {
23258        return this.scrubbing_;
23259      }
23260      this.scrubbing_ = !!isScrubbing;
23261
23262      if (isScrubbing) {
23263        this.addClass('vjs-scrubbing');
23264      } else {
23265        this.removeClass('vjs-scrubbing');
23266      }
23267    };
23268
23269    /**
23270     * Get or set the current time (in seconds)
23271     *
23272     * @param {number|string} [seconds]
23273     *        The time to seek to in seconds
23274     *
23275     * @return {number}
23276     *         - the current time in seconds when getting
23277     */
23278
23279
23280    Player.prototype.currentTime = function currentTime(seconds) {
23281      if (typeof seconds !== 'undefined') {
23282        if (seconds < 0) {
23283          seconds = 0;
23284        }
23285        this.techCall_('setCurrentTime', seconds);
23286        return;
23287      }
23288
23289      // cache last currentTime and return. default to 0 seconds
23290      //
23291      // Caching the currentTime is meant to prevent a massive amount of reads on the tech's
23292      // currentTime when scrubbing, but may not provide much performance benefit afterall.
23293      // Should be tested. Also something has to read the actual current time or the cache will
23294      // never get updated.
23295      this.cache_.currentTime = this.techGet_('currentTime') || 0;
23296      return this.cache_.currentTime;
23297    };
23298
23299    /**
23300     * Normally gets the length in time of the video in seconds;
23301     * in all but the rarest use cases an argument will NOT be passed to the method
23302     *
23303     * > **NOTE**: The video must have started loading before the duration can be
23304     * known, and in the case of Flash, may not be known until the video starts
23305     * playing.
23306     *
23307     * @fires Player#durationchange
23308     *
23309     * @param {number} [seconds]
23310     *        The duration of the video to set in seconds
23311     *
23312     * @return {number}
23313     *         - The duration of the video in seconds when getting
23314     */
23315
23316
23317    Player.prototype.duration = function duration(seconds) {
23318      if (seconds === undefined) {
23319        // return NaN if the duration is not known
23320        return this.cache_.duration !== undefined ? this.cache_.duration : NaN;
23321      }
23322
23323      seconds = parseFloat(seconds);
23324
23325      // Standardize on Infinity for signaling video is live
23326      if (seconds < 0) {
23327        seconds = Infinity;
23328      }
23329
23330      if (seconds !== this.cache_.duration) {
23331        // Cache the last set value for optimized scrubbing (esp. Flash)
23332        this.cache_.duration = seconds;
23333
23334        if (seconds === Infinity) {
23335          this.addClass('vjs-live');
23336        } else {
23337          this.removeClass('vjs-live');
23338        }
23339        /**
23340         * @event Player#durationchange
23341         * @type {EventTarget~Event}
23342         */
23343        this.trigger('durationchange');
23344      }
23345    };
23346
23347    /**
23348     * Calculates how much time is left in the video. Not part
23349     * of the native video API.
23350     *
23351     * @return {number}
23352     *         The time remaining in seconds
23353     */
23354
23355
23356    Player.prototype.remainingTime = function remainingTime() {
23357      return this.duration() - this.currentTime();
23358    };
23359
23360    /**
23361     * A remaining time function that is intented to be used when
23362     * the time is to be displayed directly to the user.
23363     *
23364     * @return {number}
23365     *         The rounded time remaining in seconds
23366     */
23367
23368
23369    Player.prototype.remainingTimeDisplay = function remainingTimeDisplay() {
23370      return Math.floor(this.duration()) - Math.floor(this.currentTime());
23371    };
23372
23373    //
23374    // Kind of like an array of portions of the video that have been downloaded.
23375
23376    /**
23377     * Get a TimeRange object with an array of the times of the video
23378     * that have been downloaded. If you just want the percent of the
23379     * video that's been downloaded, use bufferedPercent.
23380     *
23381     * @see [Buffered Spec]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered}
23382     *
23383     * @return {TimeRange}
23384     *         A mock TimeRange object (following HTML spec)
23385     */
23386
23387
23388    Player.prototype.buffered = function buffered() {
23389      var buffered = this.techGet_('buffered');
23390
23391      if (!buffered || !buffered.length) {
23392        buffered = createTimeRanges(0, 0);
23393      }
23394
23395      return buffered;
23396    };
23397
23398    /**
23399     * Get the percent (as a decimal) of the video that's been downloaded.
23400     * This method is not a part of the native HTML video API.
23401     *
23402     * @return {number}
23403     *         A decimal between 0 and 1 representing the percent
23404     *         that is buffered 0 being 0% and 1 being 100%
23405     */
23406
23407
23408    Player.prototype.bufferedPercent = function bufferedPercent$$1() {
23409      return bufferedPercent(this.buffered(), this.duration());
23410    };
23411
23412    /**
23413     * Get the ending time of the last buffered time range
23414     * This is used in the progress bar to encapsulate all time ranges.
23415     *
23416     * @return {number}
23417     *         The end of the last buffered time range
23418     */
23419
23420
23421    Player.prototype.bufferedEnd = function bufferedEnd() {
23422      var buffered = this.buffered();
23423      var duration = this.duration();
23424      var end = buffered.end(buffered.length - 1);
23425
23426      if (end > duration) {
23427        end = duration;
23428      }
23429
23430      return end;
23431    };
23432
23433    /**
23434     * Get or set the current volume of the media
23435     *
23436     * @param  {number} [percentAsDecimal]
23437     *         The new volume as a decimal percent:
23438     *         - 0 is muted/0%/off
23439     *         - 1.0 is 100%/full
23440     *         - 0.5 is half volume or 50%
23441     *
23442     * @return {number}
23443     *         The current volume as a percent when getting
23444     */
23445
23446
23447    Player.prototype.volume = function volume(percentAsDecimal) {
23448      var vol = void 0;
23449
23450      if (percentAsDecimal !== undefined) {
23451        // Force value to between 0 and 1
23452        vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal)));
23453        this.cache_.volume = vol;
23454        this.techCall_('setVolume', vol);
23455
23456        if (vol > 0) {
23457          this.lastVolume_(vol);
23458        }
23459
23460        return;
23461      }
23462
23463      // Default to 1 when returning current volume.
23464      vol = parseFloat(this.techGet_('volume'));
23465      return isNaN(vol) ? 1 : vol;
23466    };
23467
23468    /**
23469     * Get the current muted state, or turn mute on or off
23470     *
23471     * @param {boolean} [muted]
23472     *        - true to mute
23473     *        - false to unmute
23474     *
23475     * @return {boolean}
23476     *         - true if mute is on and getting
23477     *         - false if mute is off and getting
23478     */
23479
23480
23481    Player.prototype.muted = function muted(_muted) {
23482      if (_muted !== undefined) {
23483        this.techCall_('setMuted', _muted);
23484        return;
23485      }
23486      return this.techGet_('muted') || false;
23487    };
23488
23489    /**
23490     * Get the current defaultMuted state, or turn defaultMuted on or off. defaultMuted
23491     * indicates the state of muted on initial playback.
23492     *
23493     * ```js
23494     *   var myPlayer = videojs('some-player-id');
23495     *
23496     *   myPlayer.src("http://www.example.com/path/to/video.mp4");
23497     *
23498     *   // get, should be false
23499     *   console.log(myPlayer.defaultMuted());
23500     *   // set to true
23501     *   myPlayer.defaultMuted(true);
23502     *   // get should be true
23503     *   console.log(myPlayer.defaultMuted());
23504     * ```
23505     *
23506     * @param {boolean} [defaultMuted]
23507     *        - true to mute
23508     *        - false to unmute
23509     *
23510     * @return {boolean|Player}
23511     *         - true if defaultMuted is on and getting
23512     *         - false if defaultMuted is off and getting
23513     *         - A reference to the current player when setting
23514     */
23515
23516
23517    Player.prototype.defaultMuted = function defaultMuted(_defaultMuted) {
23518      if (_defaultMuted !== undefined) {
23519        return this.techCall_('setDefaultMuted', _defaultMuted);
23520      }
23521      return this.techGet_('defaultMuted') || false;
23522    };
23523
23524    /**
23525     * Get the last volume, or set it
23526     *
23527     * @param  {number} [percentAsDecimal]
23528     *         The new last volume as a decimal percent:
23529     *         - 0 is muted/0%/off
23530     *         - 1.0 is 100%/full
23531     *         - 0.5 is half volume or 50%
23532     *
23533     * @return {number}
23534     *         the current value of lastVolume as a percent when getting
23535     *
23536     * @private
23537     */
23538
23539
23540    Player.prototype.lastVolume_ = function lastVolume_(percentAsDecimal) {
23541      if (percentAsDecimal !== undefined && percentAsDecimal !== 0) {
23542        this.cache_.lastVolume = percentAsDecimal;
23543        return;
23544      }
23545      return this.cache_.lastVolume;
23546    };
23547
23548    /**
23549     * Check if current tech can support native fullscreen
23550     * (e.g. with built in controls like iOS, so not our flash swf)
23551     *
23552     * @return {boolean}
23553     *         if native fullscreen is supported
23554     */
23555
23556
23557    Player.prototype.supportsFullScreen = function supportsFullScreen() {
23558      return this.techGet_('supportsFullScreen') || false;
23559    };
23560
23561    /**
23562     * Check if the player is in fullscreen mode or tell the player that it
23563     * is or is not in fullscreen mode.
23564     *
23565     * > NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official
23566     * property and instead document.fullscreenElement is used. But isFullscreen is
23567     * still a valuable property for internal player workings.
23568     *
23569     * @param  {boolean} [isFS]
23570     *         Set the players current fullscreen state
23571     *
23572     * @return {boolean}
23573     *         - true if fullscreen is on and getting
23574     *         - false if fullscreen is off and getting
23575     */
23576
23577
23578    Player.prototype.isFullscreen = function isFullscreen(isFS) {
23579      if (isFS !== undefined) {
23580        this.isFullscreen_ = !!isFS;
23581        return;
23582      }
23583      return !!this.isFullscreen_;
23584    };
23585
23586    /**
23587     * Increase the size of the video to full screen
23588     * In some browsers, full screen is not supported natively, so it enters
23589     * "full window mode", where the video fills the browser window.
23590     * In browsers and devices that support native full screen, sometimes the
23591     * browser's default controls will be shown, and not the Video.js custom skin.
23592     * This includes most mobile devices (iOS, Android) and older versions of
23593     * Safari.
23594     *
23595     * @fires Player#fullscreenchange
23596     */
23597
23598
23599    Player.prototype.requestFullscreen = function requestFullscreen() {
23600      var fsApi = FullscreenApi;
23601
23602      this.isFullscreen(true);
23603
23604      if (fsApi.requestFullscreen) {
23605        // the browser supports going fullscreen at the element level so we can
23606        // take the controls fullscreen as well as the video
23607
23608        // Trigger fullscreenchange event after change
23609        // We have to specifically add this each time, and remove
23610        // when canceling fullscreen. Otherwise if there's multiple
23611        // players on a page, they would all be reacting to the same fullscreen
23612        // events
23613        on(document_1, fsApi.fullscreenchange, bind(this, function documentFullscreenChange(e) {
23614          this.isFullscreen(document_1[fsApi.fullscreenElement]);
23615
23616          // If cancelling fullscreen, remove event listener.
23617          if (this.isFullscreen() === false) {
23618            off(document_1, fsApi.fullscreenchange, documentFullscreenChange);
23619          }
23620          /**
23621           * @event Player#fullscreenchange
23622           * @type {EventTarget~Event}
23623           */
23624          this.trigger('fullscreenchange');
23625        }));
23626
23627        this.el_[fsApi.requestFullscreen]();
23628      } else if (this.tech_.supportsFullScreen()) {
23629        // we can't take the video.js controls fullscreen but we can go fullscreen
23630        // with native controls
23631        this.techCall_('enterFullScreen');
23632      } else {
23633        // fullscreen isn't supported so we'll just stretch the video element to
23634        // fill the viewport
23635        this.enterFullWindow();
23636        /**
23637         * @event Player#fullscreenchange
23638         * @type {EventTarget~Event}
23639         */
23640        this.trigger('fullscreenchange');
23641      }
23642    };
23643
23644    /**
23645     * Return the video to its normal size after having been in full screen mode
23646     *
23647     * @fires Player#fullscreenchange
23648     */
23649
23650
23651    Player.prototype.exitFullscreen = function exitFullscreen() {
23652      var fsApi = FullscreenApi;
23653
23654      this.isFullscreen(false);
23655
23656      // Check for browser element fullscreen support
23657      if (fsApi.requestFullscreen) {
23658        document_1[fsApi.exitFullscreen]();
23659      } else if (this.tech_.supportsFullScreen()) {
23660        this.techCall_('exitFullScreen');
23661      } else {
23662        this.exitFullWindow();
23663        /**
23664         * @event Player#fullscreenchange
23665         * @type {EventTarget~Event}
23666         */
23667        this.trigger('fullscreenchange');
23668      }
23669    };
23670
23671    /**
23672     * When fullscreen isn't supported we can stretch the
23673     * video container to as wide as the browser will let us.
23674     *
23675     * @fires Player#enterFullWindow
23676     */
23677
23678
23679    Player.prototype.enterFullWindow = function enterFullWindow() {
23680      this.isFullWindow = true;
23681
23682      // Storing original doc overflow value to return to when fullscreen is off
23683      this.docOrigOverflow = document_1.documentElement.style.overflow;
23684
23685      // Add listener for esc key to exit fullscreen
23686      on(document_1, 'keydown', bind(this, this.fullWindowOnEscKey));
23687
23688      // Hide any scroll bars
23689      document_1.documentElement.style.overflow = 'hidden';
23690
23691      // Apply fullscreen styles
23692      addClass(document_1.body, 'vjs-full-window');
23693
23694      /**
23695       * @event Player#enterFullWindow
23696       * @type {EventTarget~Event}
23697       */
23698      this.trigger('enterFullWindow');
23699    };
23700
23701    /**
23702     * Check for call to either exit full window or
23703     * full screen on ESC key
23704     *
23705     * @param {string} event
23706     *        Event to check for key press
23707     */
23708
23709
23710    Player.prototype.fullWindowOnEscKey = function fullWindowOnEscKey(event) {
23711      if (event.keyCode === 27) {
23712        if (this.isFullscreen() === true) {
23713          this.exitFullscreen();
23714        } else {
23715          this.exitFullWindow();
23716        }
23717      }
23718    };
23719
23720    /**
23721     * Exit full window
23722     *
23723     * @fires Player#exitFullWindow
23724     */
23725
23726
23727    Player.prototype.exitFullWindow = function exitFullWindow() {
23728      this.isFullWindow = false;
vendor: 73,136 bytes, lines 23729-26243
23729      off(document_1, 'keydown', this.fullWindowOnEscKey);
23730
23731      // Unhide scroll bars.
23732      document_1.documentElement.style.overflow = this.docOrigOverflow;
23733
23734      // Remove fullscreen styles
23735      removeClass(document_1.body, 'vjs-full-window');
23736
23737      // Resize the box, controller, and poster to original sizes
23738      // this.positionAll();
23739      /**
23740       * @event Player#exitFullWindow
23741       * @type {EventTarget~Event}
23742       */
23743      this.trigger('exitFullWindow');
23744    };
23745
23746    /**
23747     * Check whether the player can play a given mimetype
23748     *
23749     * @see https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype
23750     *
23751     * @param {string} type
23752     *        The mimetype to check
23753     *
23754     * @return {string}
23755     *         'probably', 'maybe', or '' (empty string)
23756     */
23757
23758
23759    Player.prototype.canPlayType = function canPlayType(type) {
23760      var can = void 0;
23761
23762      // Loop through each playback technology in the options order
23763      for (var i = 0, j = this.options_.techOrder; i < j.length; i++) {
23764        var techName = j[i];
23765        var tech = Tech.getTech(techName);
23766
23767        // Support old behavior of techs being registered as components.
23768        // Remove once that deprecated behavior is removed.
23769        if (!tech) {
23770          tech = Component.getComponent(techName);
23771        }
23772
23773        // Check if the current tech is defined before continuing
23774        if (!tech) {
23775          log$1.error('The "' + techName + '" tech is undefined. Skipped browser support check for that tech.');
23776          continue;
23777        }
23778
23779        // Check if the browser supports this technology
23780        if (tech.isSupported()) {
23781          can = tech.canPlayType(type);
23782
23783          if (can) {
23784            return can;
23785          }
23786        }
23787      }
23788
23789      return '';
23790    };
23791
23792    /**
23793     * Select source based on tech-order or source-order
23794     * Uses source-order selection if `options.sourceOrder` is truthy. Otherwise,
23795     * defaults to tech-order selection
23796     *
23797     * @param {Array} sources
23798     *        The sources for a media asset
23799     *
23800     * @return {Object|boolean}
23801     *         Object of source and tech order or false
23802     */
23803
23804
23805    Player.prototype.selectSource = function selectSource(sources) {
23806      var _this7 = this;
23807
23808      // Get only the techs specified in `techOrder` that exist and are supported by the
23809      // current platform
23810      var techs = this.options_.techOrder.map(function (techName) {
23811        return [techName, Tech.getTech(techName)];
23812      }).filter(function (_ref) {
23813        var techName = _ref[0],
23814            tech = _ref[1];
23815
23816        // Check if the current tech is defined before continuing
23817        if (tech) {
23818          // Check if the browser supports this technology
23819          return tech.isSupported();
23820        }
23821
23822        log$1.error('The "' + techName + '" tech is undefined. Skipped browser support check for that tech.');
23823        return false;
23824      });
23825
23826      // Iterate over each `innerArray` element once per `outerArray` element and execute
23827      // `tester` with both. If `tester` returns a non-falsy value, exit early and return
23828      // that value.
23829      var findFirstPassingTechSourcePair = function findFirstPassingTechSourcePair(outerArray, innerArray, tester) {
23830        var found = void 0;
23831
23832        outerArray.some(function (outerChoice) {
23833          return innerArray.some(function (innerChoice) {
23834            found = tester(outerChoice, innerChoice);
23835
23836            if (found) {
23837              return true;
23838            }
23839          });
23840        });
23841
23842        return found;
23843      };
23844
23845      var foundSourceAndTech = void 0;
23846      var flip = function flip(fn) {
23847        return function (a, b) {
23848          return fn(b, a);
23849        };
23850      };
23851      var finder = function finder(_ref2, source) {
23852        var techName = _ref2[0],
23853            tech = _ref2[1];
23854
23855        if (tech.canPlaySource(source, _this7.options_[techName.toLowerCase()])) {
23856          return { source: source, tech: techName };
23857        }
23858      };
23859
23860      // Depending on the truthiness of `options.sourceOrder`, we swap the order of techs and sources
23861      // to select from them based on their priority.
23862      if (this.options_.sourceOrder) {
23863        // Source-first ordering
23864        foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder));
23865      } else {
23866        // Tech-first ordering
23867        foundSourceAndTech = findFirstPassingTechSourcePair(techs, sources, finder);
23868      }
23869
23870      return foundSourceAndTech || false;
23871    };
23872
23873    /**
23874     * Get or set the video source.
23875     *
23876     * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source]
23877     *        A SourceObject, an array of SourceObjects, or a string referencing
23878     *        a URL to a media source. It is _highly recommended_ that an object
23879     *        or array of objects is used here, so that source selection
23880     *        algorithms can take the `type` into account.
23881     *
23882     *        If not provided, this method acts as a getter.
23883     *
23884     * @return {string|undefined}
23885     *         If the `source` argument is missing, returns the current source
23886     *         URL. Otherwise, returns nothing/undefined.
23887     */
23888
23889
23890    Player.prototype.src = function src(source) {
23891      var _this8 = this;
23892
23893      // getter usage
23894      if (typeof source === 'undefined') {
23895        return this.cache_.src || '';
23896      }
23897      // filter out invalid sources and turn our source into
23898      // an array of source objects
23899      var sources = filterSource(source);
23900
23901      // if a source was passed in then it is invalid because
23902      // it was filtered to a zero length Array. So we have to
23903      // show an error
23904      if (!sources.length) {
23905        this.setTimeout(function () {
23906          this.error({ code: 4, message: this.localize(this.options_.notSupportedMessage) });
23907        }, 0);
23908        return;
23909      }
23910
23911      // intial sources
23912      this.changingSrc_ = true;
23913
23914      this.cache_.sources = sources;
23915      this.updateSourceCaches_(sources[0]);
23916
23917      // middlewareSource is the source after it has been changed by middleware
23918      setSource(this, sources[0], function (middlewareSource, mws) {
23919        _this8.middleware_ = mws;
23920
23921        // since sourceSet is async we have to update the cache again after we select a source since
23922        // the source that is selected could be out of order from the cache update above this callback.
23923        _this8.cache_.sources = sources;
23924        _this8.updateSourceCaches_(middlewareSource);
23925
23926        var err = _this8.src_(middlewareSource);
23927
23928        if (err) {
23929          if (sources.length > 1) {
23930            return _this8.src(sources.slice(1));
23931          }
23932
23933          _this8.changingSrc_ = false;
23934
23935          // We need to wrap this in a timeout to give folks a chance to add error event handlers
23936          _this8.setTimeout(function () {
23937            this.error({ code: 4, message: this.localize(this.options_.notSupportedMessage) });
23938          }, 0);
23939
23940          // we could not find an appropriate tech, but let's still notify the delegate that this is it
23941          // this needs a better comment about why this is needed
23942          _this8.triggerReady();
23943
23944          return;
23945        }
23946
23947        setTech(mws, _this8.tech_);
23948      });
23949    };
23950
23951    /**
23952     * Set the source object on the tech, returns a boolean that indicates whether
23953     * there is a tech that can play the source or not
23954     *
23955     * @param {Tech~SourceObject} source
23956     *        The source object to set on the Tech
23957     *
23958     * @return {Boolean}
23959     *         - True if there is no Tech to playback this source
23960     *         - False otherwise
23961     *
23962     * @private
23963     */
23964
23965
23966    Player.prototype.src_ = function src_(source) {
23967      var _this9 = this;
23968
23969      var sourceTech = this.selectSource([source]);
23970
23971      if (!sourceTech) {
23972        return true;
23973      }
23974
23975      if (!titleCaseEquals(sourceTech.tech, this.techName_)) {
23976        this.changingSrc_ = true;
23977        // load this technology with the chosen source
23978        this.loadTech_(sourceTech.tech, sourceTech.source);
23979        this.tech_.ready(function () {
23980          _this9.changingSrc_ = false;
23981        });
23982        return false;
23983      }
23984
23985      // wait until the tech is ready to set the source
23986      // and set it synchronously if possible (#2326)
23987      this.ready(function () {
23988
23989        // The setSource tech method was added with source handlers
23990        // so older techs won't support it
23991        // We need to check the direct prototype for the case where subclasses
23992        // of the tech do not support source handlers
23993        if (this.tech_.constructor.prototype.hasOwnProperty('setSource')) {
23994          this.techCall_('setSource', source);
23995        } else {
23996          this.techCall_('src', source.src);
23997        }
23998
23999        this.changingSrc_ = false;
24000      }, true);
24001
24002      return false;
24003    };
24004
24005    /**
24006     * Begin loading the src data.
24007     */
24008
24009
24010    Player.prototype.load = function load() {
24011      this.techCall_('load');
24012    };
24013
24014    /**
24015     * Reset the player. Loads the first tech in the techOrder,
24016     * and calls `reset` on the tech`.
24017     */
24018
24019
24020    Player.prototype.reset = function reset() {
24021      this.loadTech_(this.options_.techOrder[0], null);
24022      this.techCall_('reset');
24023    };
24024
24025    /**
24026     * Returns all of the current source objects.
24027     *
24028     * @return {Tech~SourceObject[]}
24029     *         The current source objects
24030     */
24031
24032
24033    Player.prototype.currentSources = function currentSources() {
24034      var source = this.currentSource();
24035      var sources = [];
24036
24037      // assume `{}` or `{ src }`
24038      if (Object.keys(source).length !== 0) {
24039        sources.push(source);
24040      }
24041
24042      return this.cache_.sources || sources;
24043    };
24044
24045    /**
24046     * Returns the current source object.
24047     *
24048     * @return {Tech~SourceObject}
24049     *         The current source object
24050     */
24051
24052
24053    Player.prototype.currentSource = function currentSource() {
24054      return this.cache_.source || {};
24055    };
24056
24057    /**
24058     * Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4
24059     * Can be used in conjunction with `currentType` to assist in rebuilding the current source object.
24060     *
24061     * @return {string}
24062     *         The current source
24063     */
24064
24065
24066    Player.prototype.currentSrc = function currentSrc() {
24067      return this.currentSource() && this.currentSource().src || '';
24068    };
24069
24070    /**
24071     * Get the current source type e.g. video/mp4
24072     * This can allow you rebuild the current source object so that you could load the same
24073     * source and tech later
24074     *
24075     * @return {string}
24076     *         The source MIME type
24077     */
24078
24079
24080    Player.prototype.currentType = function currentType() {
24081      return this.currentSource() && this.currentSource().type || '';
24082    };
24083
24084    /**
24085     * Get or set the preload attribute
24086     *
24087     * @param {boolean} [value]
24088     *        - true means that we should preload
24089     *        - false means that we should not preload
24090     *
24091     * @return {string}
24092     *         The preload attribute value when getting
24093     */
24094
24095
24096    Player.prototype.preload = function preload(value) {
24097      if (value !== undefined) {
24098        this.techCall_('setPreload', value);
24099        this.options_.preload = value;
24100        return;
24101      }
24102      return this.techGet_('preload');
24103    };
24104
24105    /**
24106     * Get or set the autoplay attribute.
24107     *
24108     * @param {boolean} [value]
24109     *        - true means that we should autoplay
24110     *        - false means that we should not autoplay
24111     *
24112     * @return {string}
24113     *         The current value of autoplay when getting
24114     */
24115
24116
24117    Player.prototype.autoplay = function autoplay(value) {
24118      if (value !== undefined) {
24119        this.techCall_('setAutoplay', value);
24120        this.options_.autoplay = value;
24121        return;
24122      }
24123      return this.techGet_('autoplay', value);
24124    };
24125
24126    /**
24127     * Set or unset the playsinline attribute.
24128     * Playsinline tells the browser that non-fullscreen playback is preferred.
24129     *
24130     * @param {boolean} [value]
24131     *        - true means that we should try to play inline by default
24132     *        - false means that we should use the browser's default playback mode,
24133     *          which in most cases is inline. iOS Safari is a notable exception
24134     *          and plays fullscreen by default.
24135     *
24136     * @return {string|Player}
24137     *         - the current value of playsinline
24138     *         - the player when setting
24139     *
24140     * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
24141     */
24142
24143
24144    Player.prototype.playsinline = function playsinline(value) {
24145      if (value !== undefined) {
24146        this.techCall_('setPlaysinline', value);
24147        this.options_.playsinline = value;
24148        return this;
24149      }
24150      return this.techGet_('playsinline');
24151    };
24152
24153    /**
24154     * Get or set the loop attribute on the video element.
24155     *
24156     * @param {boolean} [value]
24157     *        - true means that we should loop the video
24158     *        - false means that we should not loop the video
24159     *
24160     * @return {string}
24161     *         The current value of loop when getting
24162     */
24163
24164
24165    Player.prototype.loop = function loop(value) {
24166      if (value !== undefined) {
24167        this.techCall_('setLoop', value);
24168        this.options_.loop = value;
24169        return;
24170      }
24171      return this.techGet_('loop');
24172    };
24173
24174    /**
24175     * Get or set the poster image source url
24176     *
24177     * @fires Player#posterchange
24178     *
24179     * @param {string} [src]
24180     *        Poster image source URL
24181     *
24182     * @return {string}
24183     *         The current value of poster when getting
24184     */
24185
24186
24187    Player.prototype.poster = function poster(src) {
24188      if (src === undefined) {
24189        return this.poster_;
24190      }
24191
24192      // The correct way to remove a poster is to set as an empty string
24193      // other falsey values will throw errors
24194      if (!src) {
24195        src = '';
24196      }
24197
24198      if (src === this.poster_) {
24199        return;
24200      }
24201
24202      // update the internal poster variable
24203      this.poster_ = src;
24204
24205      // update the tech's poster
24206      this.techCall_('setPoster', src);
24207
24208      this.isPosterFromTech_ = false;
24209
24210      // alert components that the poster has been set
24211      /**
24212       * This event fires when the poster image is changed on the player.
24213       *
24214       * @event Player#posterchange
24215       * @type {EventTarget~Event}
24216       */
24217      this.trigger('posterchange');
24218    };
24219
24220    /**
24221     * Some techs (e.g. YouTube) can provide a poster source in an
24222     * asynchronous way. We want the poster component to use this
24223     * poster source so that it covers up the tech's controls.
24224     * (YouTube's play button). However we only want to use this
24225     * source if the player user hasn't set a poster through
24226     * the normal APIs.
24227     *
24228     * @fires Player#posterchange
24229     * @listens Tech#posterchange
24230     * @private
24231     */
24232
24233
24234    Player.prototype.handleTechPosterChange_ = function handleTechPosterChange_() {
24235      if ((!this.poster_ || this.options_.techCanOverridePoster) && this.tech_ && this.tech_.poster) {
24236        var newPoster = this.tech_.poster() || '';
24237
24238        if (newPoster !== this.poster_) {
24239          this.poster_ = newPoster;
24240          this.isPosterFromTech_ = true;
24241
24242          // Let components know the poster has changed
24243          this.trigger('posterchange');
24244        }
24245      }
24246    };
24247
24248    /**
24249     * Get or set whether or not the controls are showing.
24250     *
24251     * @fires Player#controlsenabled
24252     *
24253     * @param {boolean} [bool]
24254     *        - true to turn controls on
24255     *        - false to turn controls off
24256     *
24257     * @return {boolean}
24258     *         The current value of controls when getting
24259     */
24260
24261
24262    Player.prototype.controls = function controls(bool) {
24263      if (bool === undefined) {
24264        return !!this.controls_;
24265      }
24266
24267      bool = !!bool;
24268
24269      // Don't trigger a change event unless it actually changed
24270      if (this.controls_ === bool) {
24271        return;
24272      }
24273
24274      this.controls_ = bool;
24275
24276      if (this.usingNativeControls()) {
24277        this.techCall_('setControls', bool);
24278      }
24279
24280      if (this.controls_) {
24281        this.removeClass('vjs-controls-disabled');
24282        this.addClass('vjs-controls-enabled');
24283        /**
24284         * @event Player#controlsenabled
24285         * @type {EventTarget~Event}
24286         */
24287        this.trigger('controlsenabled');
24288        if (!this.usingNativeControls()) {
24289          this.addTechControlsListeners_();
24290        }
24291      } else {
24292        this.removeClass('vjs-controls-enabled');
24293        this.addClass('vjs-controls-disabled');
24294        /**
24295         * @event Player#controlsdisabled
24296         * @type {EventTarget~Event}
24297         */
24298        this.trigger('controlsdisabled');
24299        if (!this.usingNativeControls()) {
24300          this.removeTechControlsListeners_();
24301        }
24302      }
24303    };
24304
24305    /**
24306     * Toggle native controls on/off. Native controls are the controls built into
24307     * devices (e.g. default iPhone controls), Flash, or other techs
24308     * (e.g. Vimeo Controls)
24309     * **This should only be set by the current tech, because only the tech knows
24310     * if it can support native controls**
24311     *
24312     * @fires Player#usingnativecontrols
24313     * @fires Player#usingcustomcontrols
24314     *
24315     * @param {boolean} [bool]
24316     *        - true to turn native controls on
24317     *        - false to turn native controls off
24318     *
24319     * @return {boolean}
24320     *         The current value of native controls when getting
24321     */
24322
24323
24324    Player.prototype.usingNativeControls = function usingNativeControls(bool) {
24325      if (bool === undefined) {
24326        return !!this.usingNativeControls_;
24327      }
24328
24329      bool = !!bool;
24330
24331      // Don't trigger a change event unless it actually changed
24332      if (this.usingNativeControls_ === bool) {
24333        return;
24334      }
24335
24336      this.usingNativeControls_ = bool;
24337
24338      if (this.usingNativeControls_) {
24339        this.addClass('vjs-using-native-controls');
24340
24341        /**
24342         * player is using the native device controls
24343         *
24344         * @event Player#usingnativecontrols
24345         * @type {EventTarget~Event}
24346         */
24347        this.trigger('usingnativecontrols');
24348      } else {
24349        this.removeClass('vjs-using-native-controls');
24350
24351        /**
24352         * player is using the custom HTML controls
24353         *
24354         * @event Player#usingcustomcontrols
24355         * @type {EventTarget~Event}
24356         */
24357        this.trigger('usingcustomcontrols');
24358      }
24359    };
24360
24361    /**
24362     * Set or get the current MediaError
24363     *
24364     * @fires Player#error
24365     *
24366     * @param  {MediaError|string|number} [err]
24367     *         A MediaError or a string/number to be turned
24368     *         into a MediaError
24369     *
24370     * @return {MediaError|null}
24371     *         The current MediaError when getting (or null)
24372     */
24373
24374
24375    Player.prototype.error = function error(err) {
24376      if (err === undefined) {
24377        return this.error_ || null;
24378      }
24379
24380      // restoring to default
24381      if (err === null) {
24382        this.error_ = err;
24383        this.removeClass('vjs-error');
24384        if (this.errorDisplay) {
24385          this.errorDisplay.close();
24386        }
24387        return;
24388      }
24389
24390      this.error_ = new MediaError(err);
24391
24392      // add the vjs-error classname to the player
24393      this.addClass('vjs-error');
24394
24395      // log the name of the error type and any message
24396      // IE11 logs "[object object]" and required you to expand message to see error object
24397      log$1.error('(CODE:' + this.error_.code + ' ' + MediaError.errorTypes[this.error_.code] + ')', this.error_.message, this.error_);
24398
24399      /**
24400       * @event Player#error
24401       * @type {EventTarget~Event}
24402       */
24403      this.trigger('error');
24404
24405      return;
24406    };
24407
24408    /**
24409     * Report user activity
24410     *
24411     * @param {Object} event
24412     *        Event object
24413     */
24414
24415
24416    Player.prototype.reportUserActivity = function reportUserActivity(event) {
24417      this.userActivity_ = true;
24418    };
24419
24420    /**
24421     * Get/set if user is active
24422     *
24423     * @fires Player#useractive
24424     * @fires Player#userinactive
24425     *
24426     * @param {boolean} [bool]
24427     *        - true if the user is active
24428     *        - false if the user is inactive
24429     *
24430     * @return {boolean}
24431     *         The current value of userActive when getting
24432     */
24433
24434
24435    Player.prototype.userActive = function userActive(bool) {
24436      if (bool === undefined) {
24437        return this.userActive_;
24438      }
24439
24440      bool = !!bool;
24441
24442      if (bool === this.userActive_) {
24443        return;
24444      }
24445
24446      this.userActive_ = bool;
24447
24448      if (this.userActive_) {
24449        this.userActivity_ = true;
24450        this.removeClass('vjs-user-inactive');
24451        this.addClass('vjs-user-active');
24452        /**
24453         * @event Player#useractive
24454         * @type {EventTarget~Event}
24455         */
24456        this.trigger('useractive');
24457        return;
24458      }
24459
24460      // Chrome/Safari/IE have bugs where when you change the cursor it can
24461      // trigger a mousemove event. This causes an issue when you're hiding
24462      // the cursor when the user is inactive, and a mousemove signals user
24463      // activity. Making it impossible to go into inactive mode. Specifically
24464      // this happens in fullscreen when we really need to hide the cursor.
24465      //
24466      // When this gets resolved in ALL browsers it can be removed
24467      // https://code.google.com/p/chromium/issues/detail?id=103041
24468      if (this.tech_) {
24469        this.tech_.one('mousemove', function (e) {
24470          e.stopPropagation();
24471          e.preventDefault();
24472        });
24473      }
24474
24475      this.userActivity_ = false;
24476      this.removeClass('vjs-user-active');
24477      this.addClass('vjs-user-inactive');
24478      /**
24479       * @event Player#userinactive
24480       * @type {EventTarget~Event}
24481       */
24482      this.trigger('userinactive');
24483    };
24484
24485    /**
24486     * Listen for user activity based on timeout value
24487     *
24488     * @private
24489     */
24490
24491
24492    Player.prototype.listenForUserActivity_ = function listenForUserActivity_() {
24493      var mouseInProgress = void 0;
24494      var lastMoveX = void 0;
24495      var lastMoveY = void 0;
24496      var handleActivity = bind(this, this.reportUserActivity);
24497
24498      var handleMouseMove = function handleMouseMove(e) {
24499        // #1068 - Prevent mousemove spamming
24500        // Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970
24501        if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) {
24502          lastMoveX = e.screenX;
24503          lastMoveY = e.screenY;
24504          handleActivity();
24505        }
24506      };
24507
24508      var handleMouseDown = function handleMouseDown() {
24509        handleActivity();
24510        // For as long as the they are touching the device or have their mouse down,
24511        // we consider them active even if they're not moving their finger or mouse.
24512        // So we want to continue to update that they are active
24513        this.clearInterval(mouseInProgress);
24514        // Setting userActivity=true now and setting the interval to the same time
24515        // as the activityCheck interval (250) should ensure we never miss the
24516        // next activityCheck
24517        mouseInProgress = this.setInterval(handleActivity, 250);
24518      };
24519
24520      var handleMouseUp = function handleMouseUp(event) {
24521        handleActivity();
24522        // Stop the interval that maintains activity if the mouse/touch is down
24523        this.clearInterval(mouseInProgress);
24524      };
24525
24526      // Any mouse movement will be considered user activity
24527      this.on('mousedown', handleMouseDown);
24528      this.on('mousemove', handleMouseMove);
24529      this.on('mouseup', handleMouseUp);
24530
24531      // Listen for keyboard navigation
24532      // Shouldn't need to use inProgress interval because of key repeat
24533      this.on('keydown', handleActivity);
24534      this.on('keyup', handleActivity);
24535
24536      // Run an interval every 250 milliseconds instead of stuffing everything into
24537      // the mousemove/touchmove function itself, to prevent performance degradation.
24538      // `this.reportUserActivity` simply sets this.userActivity_ to true, which
24539      // then gets picked up by this loop
24540      // http://ejohn.org/blog/learning-from-twitter/
24541      var inactivityTimeout = void 0;
24542
24543      this.setInterval(function () {
24544        // Check to see if mouse/touch activity has happened
24545        if (!this.userActivity_) {
24546          return;
24547        }
24548
24549        // Reset the activity tracker
24550        this.userActivity_ = false;
24551
24552        // If the user state was inactive, set the state to active
24553        this.userActive(true);
24554
24555        // Clear any existing inactivity timeout to start the timer over
24556        this.clearTimeout(inactivityTimeout);
24557
24558        var timeout = this.options_.inactivityTimeout;
24559
24560        if (timeout <= 0) {
24561          return;
24562        }
24563
24564        // In <timeout> milliseconds, if no more activity has occurred the
24565        // user will be considered inactive
24566        inactivityTimeout = this.setTimeout(function () {
24567          // Protect against the case where the inactivityTimeout can trigger just
24568          // before the next user activity is picked up by the activity check loop
24569          // causing a flicker
24570          if (!this.userActivity_) {
24571            this.userActive(false);
24572          }
24573        }, timeout);
24574      }, 250);
24575    };
24576
24577    /**
24578     * Gets or sets the current playback rate. A playback rate of
24579     * 1.0 represents normal speed and 0.5 would indicate half-speed
24580     * playback, for instance.
24581     *
24582     * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate
24583     *
24584     * @param {number} [rate]
24585     *       New playback rate to set.
24586     *
24587     * @return {number}
24588     *         The current playback rate when getting or 1.0
24589     */
24590
24591
24592    Player.prototype.playbackRate = function playbackRate(rate) {
24593      if (rate !== undefined) {
24594        // NOTE: this.cache_.lastPlaybackRate is set from the tech handler
24595        // that is registered above
24596        this.techCall_('setPlaybackRate', rate);
24597        return;
24598      }
24599
24600      if (this.tech_ && this.tech_.featuresPlaybackRate) {
24601        return this.cache_.lastPlaybackRate || this.techGet_('playbackRate');
24602      }
24603      return 1.0;
24604    };
24605
24606    /**
24607     * Gets or sets the current default playback rate. A default playback rate of
24608     * 1.0 represents normal speed and 0.5 would indicate half-speed playback, for instance.
24609     * defaultPlaybackRate will only represent what the initial playbackRate of a video was, not
24610     * not the current playbackRate.
24611     *
24612     * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate
24613     *
24614     * @param {number} [rate]
24615     *       New default playback rate to set.
24616     *
24617     * @return {number|Player}
24618     *         - The default playback rate when getting or 1.0
24619     *         - the player when setting
24620     */
24621
24622
24623    Player.prototype.defaultPlaybackRate = function defaultPlaybackRate(rate) {
24624      if (rate !== undefined) {
24625        return this.techCall_('setDefaultPlaybackRate', rate);
24626      }
24627
24628      if (this.tech_ && this.tech_.featuresPlaybackRate) {
24629        return this.techGet_('defaultPlaybackRate');
24630      }
24631      return 1.0;
24632    };
24633
24634    /**
24635     * Gets or sets the audio flag
24636     *
24637     * @param {boolean} bool
24638     *        - true signals that this is an audio player
24639     *        - false signals that this is not an audio player
24640     *
24641     * @return {boolean}
24642     *         The current value of isAudio when getting
24643     */
24644
24645
24646    Player.prototype.isAudio = function isAudio(bool) {
24647      if (bool !== undefined) {
24648        this.isAudio_ = !!bool;
24649        return;
24650      }
24651
24652      return !!this.isAudio_;
24653    };
24654
24655    /**
24656     * A helper method for adding a {@link TextTrack} to our
24657     * {@link TextTrackList}.
24658     *
24659     * In addition to the W3C settings we allow adding additional info through options.
24660     *
24661     * @see http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
24662     *
24663     * @param {string} [kind]
24664     *        the kind of TextTrack you are adding
24665     *
24666     * @param {string} [label]
24667     *        the label to give the TextTrack label
24668     *
24669     * @param {string} [language]
24670     *        the language to set on the TextTrack
24671     *
24672     * @return {TextTrack|undefined}
24673     *         the TextTrack that was added or undefined
24674     *         if there is no tech
24675     */
24676
24677
24678    Player.prototype.addTextTrack = function addTextTrack(kind, label, language) {
24679      if (this.tech_) {
24680        return this.tech_.addTextTrack(kind, label, language);
24681      }
24682    };
24683
24684    /**
24685     * Create a remote {@link TextTrack} and an {@link HTMLTrackElement}. It will
24686     * automatically removed from the video element whenever the source changes, unless
24687     * manualCleanup is set to false.
24688     *
24689     * @param {Object} options
24690     *        Options to pass to {@link HTMLTrackElement} during creation. See
24691     *        {@link HTMLTrackElement} for object properties that you should use.
24692     *
24693     * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be
24694     *
24695     * @return {HtmlTrackElement}
24696     *         the HTMLTrackElement that was created and added
24697     *         to the HtmlTrackElementList and the remote
24698     *         TextTrackList
24699     *
24700     * @deprecated The default value of the "manualCleanup" parameter will default
24701     *             to "false" in upcoming versions of Video.js
24702     */
24703
24704
24705    Player.prototype.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
24706      if (this.tech_) {
24707        return this.tech_.addRemoteTextTrack(options, manualCleanup);
24708      }
24709    };
24710
24711    /**
24712     * Remove a remote {@link TextTrack} from the respective
24713     * {@link TextTrackList} and {@link HtmlTrackElementList}.
24714     *
24715     * @param {Object} track
24716     *        Remote {@link TextTrack} to remove
24717     *
24718     * @return {undefined}
24719     *         does not return anything
24720     */
24721
24722
24723    Player.prototype.removeRemoteTextTrack = function removeRemoteTextTrack() {
24724      var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
24725          _ref3$track = _ref3.track,
24726          track = _ref3$track === undefined ? arguments[0] : _ref3$track;
24727
24728      // destructure the input into an object with a track argument, defaulting to arguments[0]
24729      // default the whole argument to an empty object if nothing was passed in
24730
24731      if (this.tech_) {
24732        return this.tech_.removeRemoteTextTrack(track);
24733      }
24734    };
24735
24736    /**
24737     * Gets available media playback quality metrics as specified by the W3C's Media
24738     * Playback Quality API.
24739     *
24740     * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
24741     *
24742     * @return {Object|undefined}
24743     *         An object with supported media playback quality metrics or undefined if there
24744     *         is no tech or the tech does not support it.
24745     */
24746
24747
24748    Player.prototype.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
24749      return this.techGet_('getVideoPlaybackQuality');
24750    };
24751
24752    /**
24753     * Get video width
24754     *
24755     * @return {number}
24756     *         current video width
24757     */
24758
24759
24760    Player.prototype.videoWidth = function videoWidth() {
24761      return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0;
24762    };
24763
24764    /**
24765     * Get video height
24766     *
24767     * @return {number}
24768     *         current video height
24769     */
24770
24771
24772    Player.prototype.videoHeight = function videoHeight() {
24773      return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0;
24774    };
24775
24776    /**
24777     * The player's language code
24778     * NOTE: The language should be set in the player options if you want the
24779     * the controls to be built with a specific language. Changing the language
24780     * later will not update controls text.
24781     *
24782     * @param {string} [code]
24783     *        the language code to set the player to
24784     *
24785     * @return {string}
24786     *         The current language code when getting
24787     */
24788
24789
24790    Player.prototype.language = function language(code) {
24791      if (code === undefined) {
24792        return this.language_;
24793      }
24794
24795      this.language_ = String(code).toLowerCase();
24796    };
24797
24798    /**
24799     * Get the player's language dictionary
24800     * Merge every time, because a newly added plugin might call videojs.addLanguage() at any time
24801     * Languages specified directly in the player options have precedence
24802     *
24803     * @return {Array}
24804     *         An array of of supported languages
24805     */
24806
24807
24808    Player.prototype.languages = function languages() {
24809      return mergeOptions(Player.prototype.options_.languages, this.languages_);
24810    };
24811
24812    /**
24813     * returns a JavaScript object reperesenting the current track
24814     * information. **DOES not return it as JSON**
24815     *
24816     * @return {Object}
24817     *         Object representing the current of track info
24818     */
24819
24820
24821    Player.prototype.toJSON = function toJSON() {
24822      var options = mergeOptions(this.options_);
24823      var tracks = options.tracks;
24824
24825      options.tracks = [];
24826
24827      for (var i = 0; i < tracks.length; i++) {
24828        var track = tracks[i];
24829
24830        // deep merge tracks and null out player so no circular references
24831        track = mergeOptions(track);
24832        track.player = undefined;
24833        options.tracks[i] = track;
24834      }
24835
24836      return options;
24837    };
24838
24839    /**
24840     * Creates a simple modal dialog (an instance of the {@link ModalDialog}
24841     * component) that immediately overlays the player with arbitrary
24842     * content and removes itself when closed.
24843     *
24844     * @param {string|Function|Element|Array|null} content
24845     *        Same as {@link ModalDialog#content}'s param of the same name.
24846     *        The most straight-forward usage is to provide a string or DOM
24847     *        element.
24848     *
24849     * @param {Object} [options]
24850     *        Extra options which will be passed on to the {@link ModalDialog}.
24851     *
24852     * @return {ModalDialog}
24853     *         the {@link ModalDialog} that was created
24854     */
24855
24856
24857    Player.prototype.createModal = function createModal(content, options) {
24858      var _this10 = this;
24859
24860      options = options || {};
24861      options.content = content || '';
24862
24863      var modal = new ModalDialog(this, options);
24864
24865      this.addChild(modal);
24866      modal.on('dispose', function () {
24867        _this10.removeChild(modal);
24868      });
24869
24870      modal.open();
24871      return modal;
24872    };
24873
24874    /**
24875     * Gets tag settings
24876     *
24877     * @param {Element} tag
24878     *        The player tag
24879     *
24880     * @return {Object}
24881     *         An object containing all of the settings
24882     *         for a player tag
24883     */
24884
24885
24886    Player.getTagSettings = function getTagSettings(tag) {
24887      var baseOptions = {
24888        sources: [],
24889        tracks: []
24890      };
24891
24892      var tagOptions = getAttributes(tag);
24893      var dataSetup = tagOptions['data-setup'];
24894
24895      if (hasClass(tag, 'vjs-fluid')) {
24896        tagOptions.fluid = true;
24897      }
24898
24899      // Check if data-setup attr exists.
24900      if (dataSetup !== null) {
24901        // Parse options JSON
24902        // If empty string, make it a parsable json object.
24903        var _safeParseTuple = tuple(dataSetup || '{}'),
24904            err = _safeParseTuple[0],
24905            data = _safeParseTuple[1];
24906
24907        if (err) {
24908          log$1.error(err);
24909        }
24910        assign(tagOptions, data);
24911      }
24912
24913      assign(baseOptions, tagOptions);
24914
24915      // Get tag children settings
24916      if (tag.hasChildNodes()) {
24917        var children = tag.childNodes;
24918
24919        for (var i = 0, j = children.length; i < j; i++) {
24920          var child = children[i];
24921          // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
24922          var childName = child.nodeName.toLowerCase();
24923
24924          if (childName === 'source') {
24925            baseOptions.sources.push(getAttributes(child));
24926          } else if (childName === 'track') {
24927            baseOptions.tracks.push(getAttributes(child));
24928          }
24929        }
24930      }
24931
24932      return baseOptions;
24933    };
24934
24935    /**
24936     * Determine whether or not flexbox is supported
24937     *
24938     * @return {boolean}
24939     *         - true if flexbox is supported
24940     *         - false if flexbox is not supported
24941     */
24942
24943
24944    Player.prototype.flexNotSupported_ = function flexNotSupported_() {
24945      var elem = document_1.createElement('i');
24946
24947      // Note: We don't actually use flexBasis (or flexOrder), but it's one of the more
24948      // common flex features that we can rely on when checking for flex support.
24949      return !('flexBasis' in elem.style || 'webkitFlexBasis' in elem.style || 'mozFlexBasis' in elem.style || 'msFlexBasis' in elem.style ||
24950      // IE10-specific (2012 flex spec), available for completeness
24951      'msFlexOrder' in elem.style);
24952    };
24953
24954    return Player;
24955  }(Component);
24956
24957  /**
24958   * Get the {@link VideoTrackList}
24959   * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist
24960   *
24961   * @return {VideoTrackList}
24962   *         the current video track list
24963   *
24964   * @method Player.prototype.videoTracks
24965   */
24966
24967  /**
24968   * Get the {@link AudioTrackList}
24969   * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist
24970   *
24971   * @return {AudioTrackList}
24972   *         the current audio track list
24973   *
24974   * @method Player.prototype.audioTracks
24975   */
24976
24977  /**
24978   * Get the {@link TextTrackList}
24979   *
24980   * @link http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
24981   *
24982   * @return {TextTrackList}
24983   *         the current text track list
24984   *
24985   * @method Player.prototype.textTracks
24986   */
24987
24988  /**
24989   * Get the remote {@link TextTrackList}
24990   *
24991   * @return {TextTrackList}
24992   *         The current remote text track list
24993   *
24994   * @method Player.prototype.remoteTextTracks
24995   */
24996
24997  /**
24998   * Get the remote {@link HtmlTrackElementList} tracks.
24999   *
25000   * @return {HtmlTrackElementList}
25001   *         The current remote text track element list
25002   *
25003   * @method Player.prototype.remoteTextTrackEls
25004   */
25005
25006  ALL.names.forEach(function (name$$1) {
25007    var props = ALL[name$$1];
25008
25009    Player.prototype[props.getterName] = function () {
25010      if (this.tech_) {
25011        return this.tech_[props.getterName]();
25012      }
25013
25014      // if we have not yet loadTech_, we create {video,audio,text}Tracks_
25015      // these will be passed to the tech during loading
25016      this[props.privateName] = this[props.privateName] || new props.ListClass();
25017      return this[props.privateName];
25018    };
25019  });
25020
25021  /**
25022   * Global player list
25023   *
25024   * @type {Object}
25025   */
25026  Player.players = {};
25027
25028  var navigator = window_1.navigator;
25029
25030  /*
25031   * Player instance options, surfaced using options
25032   * options = Player.prototype.options_
25033   * Make changes in options, not here.
25034   *
25035   * @type {Object}
25036   * @private
25037   */
25038  Player.prototype.options_ = {
25039    // Default order of fallback technology
25040    techOrder: Tech.defaultTechOrder_,
25041
25042    html5: {},
25043    flash: {},
25044
25045    // default inactivity timeout
25046    inactivityTimeout: 2000,
25047
25048    // default playback rates
25049    playbackRates: [],
25050    // Add playback rate selection by adding rates
25051    // 'playbackRates': [0.5, 1, 1.5, 2],
25052
25053    // Included control sets
25054    children: ['mediaLoader', 'posterImage', 'textTrackDisplay', 'loadingSpinner', 'bigPlayButton', 'controlBar', 'errorDisplay', 'textTrackSettings', 'resizeManager'],
25055
25056    language: navigator && (navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language) || 'en',
25057
25058    // locales and their language translations
25059    languages: {},
25060
25061    // Default message to show when a video cannot be played.
25062    notSupportedMessage: 'No compatible source was found for this media.'
25063  };
25064
25065  [
25066  /**
25067   * Returns whether or not the player is in the "ended" state.
25068   *
25069   * @return {Boolean} True if the player is in the ended state, false if not.
25070   * @method Player#ended
25071   */
25072  'ended',
25073  /**
25074   * Returns whether or not the player is in the "seeking" state.
25075   *
25076   * @return {Boolean} True if the player is in the seeking state, false if not.
25077   * @method Player#seeking
25078   */
25079  'seeking',
25080  /**
25081   * Returns the TimeRanges of the media that are currently available
25082   * for seeking to.
25083   *
25084   * @return {TimeRanges} the seekable intervals of the media timeline
25085   * @method Player#seekable
25086   */
25087  'seekable',
25088  /**
25089   * Returns the current state of network activity for the element, from
25090   * the codes in the list below.
25091   * - NETWORK_EMPTY (numeric value 0)
25092   *   The element has not yet been initialised. All attributes are in
25093   *   their initial states.
25094   * - NETWORK_IDLE (numeric value 1)
25095   *   The element's resource selection algorithm is active and has
25096   *   selected a resource, but it is not actually using the network at
25097   *   this time.
25098   * - NETWORK_LOADING (numeric value 2)
25099   *   The user agent is actively trying to download data.
25100   * - NETWORK_NO_SOURCE (numeric value 3)
25101   *   The element's resource selection algorithm is active, but it has
25102   *   not yet found a resource to use.
25103   *
25104   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states
25105   * @return {number} the current network activity state
25106   * @method Player#networkState
25107   */
25108  'networkState',
25109  /**
25110   * Returns a value that expresses the current state of the element
25111   * with respect to rendering the current playback position, from the
25112   * codes in the list below.
25113   * - HAVE_NOTHING (numeric value 0)
25114   *   No information regarding the media resource is available.
25115   * - HAVE_METADATA (numeric value 1)
25116   *   Enough of the resource has been obtained that the duration of the
25117   *   resource is available.
25118   * - HAVE_CURRENT_DATA (numeric value 2)
25119   *   Data for the immediate current playback position is available.
25120   * - HAVE_FUTURE_DATA (numeric value 3)
25121   *   Data for the immediate current playback position is available, as
25122   *   well as enough data for the user agent to advance the current
25123   *   playback position in the direction of playback.
25124   * - HAVE_ENOUGH_DATA (numeric value 4)
25125   *   The user agent estimates that enough data is available for
25126   *   playback to proceed uninterrupted.
25127   *
25128   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate
25129   * @return {number} the current playback rendering state
25130   * @method Player#readyState
25131   */
25132  'readyState'].forEach(function (fn) {
25133    Player.prototype[fn] = function () {
25134      return this.techGet_(fn);
25135    };
25136  });
25137
25138  TECH_EVENTS_RETRIGGER.forEach(function (event) {
25139    Player.prototype['handleTech' + toTitleCase(event) + '_'] = function () {
25140      return this.trigger(event);
25141    };
25142  });
25143
25144  /**
25145   * Fired when the player has initial duration and dimension information
25146   *
25147   * @event Player#loadedmetadata
25148   * @type {EventTarget~Event}
25149   */
25150
25151  /**
25152   * Fired when the player has downloaded data at the current playback position
25153   *
25154   * @event Player#loadeddata
25155   * @type {EventTarget~Event}
25156   */
25157
25158  /**
25159   * Fired when the current playback position has changed *
25160   * During playback this is fired every 15-250 milliseconds, depending on the
25161   * playback technology in use.
25162   *
25163   * @event Player#timeupdate
25164   * @type {EventTarget~Event}
25165   */
25166
25167  /**
25168   * Fired when the volume changes
25169   *
25170   * @event Player#volumechange
25171   * @type {EventTarget~Event}
25172   */
25173
25174  /**
25175   * Reports whether or not a player has a plugin available.
25176   *
25177   * This does not report whether or not the plugin has ever been initialized
25178   * on this player. For that, [usingPlugin]{@link Player#usingPlugin}.
25179   *
25180   * @method Player#hasPlugin
25181   * @param  {string}  name
25182   *         The name of a plugin.
25183   *
25184   * @return {boolean}
25185   *         Whether or not this player has the requested plugin available.
25186   */
25187
25188  /**
25189   * Reports whether or not a player is using a plugin by name.
25190   *
25191   * For basic plugins, this only reports whether the plugin has _ever_ been
25192   * initialized on this player.
25193   *
25194   * @method Player#usingPlugin
25195   * @param  {string} name
25196   *         The name of a plugin.
25197   *
25198   * @return {boolean}
25199   *         Whether or not this player is using the requested plugin.
25200   */
25201
25202  Component.registerComponent('Player', Player);
25203
25204  /**
25205   * @file plugin.js
25206   */
25207
25208  /**
25209   * The base plugin name.
25210   *
25211   * @private
25212   * @constant
25213   * @type {string}
25214   */
25215  var BASE_PLUGIN_NAME = 'plugin';
25216
25217  /**
25218   * The key on which a player's active plugins cache is stored.
25219   *
25220   * @private
25221   * @constant
25222   * @type     {string}
25223   */
25224  var PLUGIN_CACHE_KEY = 'activePlugins_';
25225
25226  /**
25227   * Stores registered plugins in a private space.
25228   *
25229   * @private
25230   * @type    {Object}
25231   */
25232  var pluginStorage = {};
25233
25234  /**
25235   * Reports whether or not a plugin has been registered.
25236   *
25237   * @private
25238   * @param   {string} name
25239   *          The name of a plugin.
25240   *
25241   * @returns {boolean}
25242   *          Whether or not the plugin has been registered.
25243   */
25244  var pluginExists = function pluginExists(name) {
25245    return pluginStorage.hasOwnProperty(name);
25246  };
25247
25248  /**
25249   * Get a single registered plugin by name.
25250   *
25251   * @private
25252   * @param   {string} name
25253   *          The name of a plugin.
25254   *
25255   * @returns {Function|undefined}
25256   *          The plugin (or undefined).
25257   */
25258  var getPlugin = function getPlugin(name) {
25259    return pluginExists(name) ? pluginStorage[name] : undefined;
25260  };
25261
25262  /**
25263   * Marks a plugin as "active" on a player.
25264   *
25265   * Also, ensures that the player has an object for tracking active plugins.
25266   *
25267   * @private
25268   * @param   {Player} player
25269   *          A Video.js player instance.
25270   *
25271   * @param   {string} name
25272   *          The name of a plugin.
25273   */
25274  var markPluginAsActive = function markPluginAsActive(player, name) {
25275    player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {};
25276    player[PLUGIN_CACHE_KEY][name] = true;
25277  };
25278
25279  /**
25280   * Triggers a pair of plugin setup events.
25281   *
25282   * @private
25283   * @param  {Player} player
25284   *         A Video.js player instance.
25285   *
25286   * @param  {Plugin~PluginEventHash} hash
25287   *         A plugin event hash.
25288   *
25289   * @param  {Boolean} [before]
25290   *         If true, prefixes the event name with "before". In other words,
25291   *         use this to trigger "beforepluginsetup" instead of "pluginsetup".
25292   */
25293  var triggerSetupEvent = function triggerSetupEvent(player, hash, before) {
25294    var eventName = (before ? 'before' : '') + 'pluginsetup';
25295
25296    player.trigger(eventName, hash);
25297    player.trigger(eventName + ':' + hash.name, hash);
25298  };
25299
25300  /**
25301   * Takes a basic plugin function and returns a wrapper function which marks
25302   * on the player that the plugin has been activated.
25303   *
25304   * @private
25305   * @param   {string} name
25306   *          The name of the plugin.
25307   *
25308   * @param   {Function} plugin
25309   *          The basic plugin.
25310   *
25311   * @returns {Function}
25312   *          A wrapper function for the given plugin.
25313   */
25314  var createBasicPlugin = function createBasicPlugin(name, plugin) {
25315    var basicPluginWrapper = function basicPluginWrapper() {
25316
25317      // We trigger the "beforepluginsetup" and "pluginsetup" events on the player
25318      // regardless, but we want the hash to be consistent with the hash provided
25319      // for advanced plugins.
25320      //
25321      // The only potentially counter-intuitive thing here is the `instance` in
25322      // the "pluginsetup" event is the value returned by the `plugin` function.
25323      triggerSetupEvent(this, { name: name, plugin: plugin, instance: null }, true);
25324
25325      var instance = plugin.apply(this, arguments);
25326
25327      markPluginAsActive(this, name);
25328      triggerSetupEvent(this, { name: name, plugin: plugin, instance: instance });
25329
25330      return instance;
25331    };
25332
25333    Object.keys(plugin).forEach(function (prop) {
25334      basicPluginWrapper[prop] = plugin[prop];
25335    });
25336
25337    return basicPluginWrapper;
25338  };
25339
25340  /**
25341   * Takes a plugin sub-class and returns a factory function for generating
25342   * instances of it.
25343   *
25344   * This factory function will replace itself with an instance of the requested
25345   * sub-class of Plugin.
25346   *
25347   * @private
25348   * @param   {string} name
25349   *          The name of the plugin.
25350   *
25351   * @param   {Plugin} PluginSubClass
25352   *          The advanced plugin.
25353   *
25354   * @returns {Function}
25355   */
25356  var createPluginFactory = function createPluginFactory(name, PluginSubClass) {
25357
25358    // Add a `name` property to the plugin prototype so that each plugin can
25359    // refer to itself by name.
25360    PluginSubClass.prototype.name = name;
25361
25362    return function () {
25363      triggerSetupEvent(this, { name: name, plugin: PluginSubClass, instance: null }, true);
25364
25365      for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
25366        args[_key] = arguments[_key];
25367      }
25368
25369      var instance = new (Function.prototype.bind.apply(PluginSubClass, [null].concat([this].concat(args))))();
25370
25371      // The plugin is replaced by a function that returns the current instance.
25372      this[name] = function () {
25373        return instance;
25374      };
25375
25376      triggerSetupEvent(this, instance.getEventHash());
25377
25378      return instance;
25379    };
25380  };
25381
25382  /**
25383   * Parent class for all advanced plugins.
25384   *
25385   * @mixes   module:evented~EventedMixin
25386   * @mixes   module:stateful~StatefulMixin
25387   * @fires   Player#beforepluginsetup
25388   * @fires   Player#beforepluginsetup:$name
25389   * @fires   Player#pluginsetup
25390   * @fires   Player#pluginsetup:$name
25391   * @listens Player#dispose
25392   * @throws  {Error}
25393   *          If attempting to instantiate the base {@link Plugin} class
25394   *          directly instead of via a sub-class.
25395   */
25396
25397  var Plugin = function () {
25398
25399    /**
25400     * Creates an instance of this class.
25401     *
25402     * Sub-classes should call `super` to ensure plugins are properly initialized.
25403     *
25404     * @param {Player} player
25405     *        A Video.js player instance.
25406     */
25407    function Plugin(player) {
25408      classCallCheck(this, Plugin);
25409
25410      if (this.constructor === Plugin) {
25411        throw new Error('Plugin must be sub-classed; not directly instantiated.');
25412      }
25413
25414      this.player = player;
25415
25416      // Make this object evented, but remove the added `trigger` method so we
25417      // use the prototype version instead.
25418      evented(this);
25419      delete this.trigger;
25420
25421      stateful(this, this.constructor.defaultState);
25422      markPluginAsActive(player, this.name);
25423
25424      // Auto-bind the dispose method so we can use it as a listener and unbind
25425      // it later easily.
25426      this.dispose = bind(this, this.dispose);
25427
25428      // If the player is disposed, dispose the plugin.
25429      player.on('dispose', this.dispose);
25430    }
25431
25432    /**
25433     * Get the version of the plugin that was set on <pluginName>.VERSION
25434     */
25435
25436
25437    Plugin.prototype.version = function version() {
25438      return this.constructor.VERSION;
25439    };
25440
25441    /**
25442     * Each event triggered by plugins includes a hash of additional data with
25443     * conventional properties.
25444     *
25445     * This returns that object or mutates an existing hash.
25446     *
25447     * @param   {Object} [hash={}]
25448     *          An object to be used as event an event hash.
25449     *
25450     * @returns {Plugin~PluginEventHash}
25451     *          An event hash object with provided properties mixed-in.
25452     */
25453
25454
25455    Plugin.prototype.getEventHash = function getEventHash() {
25456      var hash = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
25457
25458      hash.name = this.name;
25459      hash.plugin = this.constructor;
25460      hash.instance = this;
25461      return hash;
25462    };
25463
25464    /**
25465     * Triggers an event on the plugin object and overrides
25466     * {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}.
25467     *
25468     * @param   {string|Object} event
25469     *          An event type or an object with a type property.
25470     *
25471     * @param   {Object} [hash={}]
25472     *          Additional data hash to merge with a
25473     *          {@link Plugin~PluginEventHash|PluginEventHash}.
25474     *
25475     * @returns {boolean}
25476     *          Whether or not default was prevented.
25477     */
25478
25479
25480    Plugin.prototype.trigger = function trigger$$1(event) {
25481      var hash = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
25482
25483      return trigger(this.eventBusEl_, event, this.getEventHash(hash));
25484    };
25485
25486    /**
25487     * Handles "statechanged" events on the plugin. No-op by default, override by
25488     * subclassing.
25489     *
25490     * @abstract
25491     * @param    {Event} e
25492     *           An event object provided by a "statechanged" event.
25493     *
25494     * @param    {Object} e.changes
25495     *           An object describing changes that occurred with the "statechanged"
25496     *           event.
25497     */
25498
25499
25500    Plugin.prototype.handleStateChanged = function handleStateChanged(e) {};
25501
25502    /**
25503     * Disposes a plugin.
25504     *
25505     * Subclasses can override this if they want, but for the sake of safety,
25506     * it's probably best to subscribe the "dispose" event.
25507     *
25508     * @fires Plugin#dispose
25509     */
25510
25511
25512    Plugin.prototype.dispose = function dispose() {
25513      var name = this.name,
25514          player = this.player;
25515
25516      /**
25517       * Signals that a advanced plugin is about to be disposed.
25518       *
25519       * @event Plugin#dispose
25520       * @type  {EventTarget~Event}
25521       */
25522
25523      this.trigger('dispose');
25524      this.off();
25525      player.off('dispose', this.dispose);
25526
25527      // Eliminate any possible sources of leaking memory by clearing up
25528      // references between the player and the plugin instance and nulling out
25529      // the plugin's state and replacing methods with a function that throws.
25530      player[PLUGIN_CACHE_KEY][name] = false;
25531      this.player = this.state = null;
25532
25533      // Finally, replace the plugin name on the player with a new factory
25534      // function, so that the plugin is ready to be set up again.
25535      player[name] = createPluginFactory(name, pluginStorage[name]);
25536    };
25537
25538    /**
25539     * Determines if a plugin is a basic plugin (i.e. not a sub-class of `Plugin`).
25540     *
25541     * @param   {string|Function} plugin
25542     *          If a string, matches the name of a plugin. If a function, will be
25543     *          tested directly.
25544     *
25545     * @returns {boolean}
25546     *          Whether or not a plugin is a basic plugin.
25547     */
25548
25549
25550    Plugin.isBasic = function isBasic(plugin) {
25551      var p = typeof plugin === 'string' ? getPlugin(plugin) : plugin;
25552
25553      return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype);
25554    };
25555
25556    /**
25557     * Register a Video.js plugin.
25558     *
25559     * @param   {string} name
25560     *          The name of the plugin to be registered. Must be a string and
25561     *          must not match an existing plugin or a method on the `Player`
25562     *          prototype.
25563     *
25564     * @param   {Function} plugin
25565     *          A sub-class of `Plugin` or a function for basic plugins.
25566     *
25567     * @returns {Function}
25568     *          For advanced plugins, a factory function for that plugin. For
25569     *          basic plugins, a wrapper function that initializes the plugin.
25570     */
25571
25572
25573    Plugin.registerPlugin = function registerPlugin(name, plugin) {
25574      if (typeof name !== 'string') {
25575        throw new Error('Illegal plugin name, "' + name + '", must be a string, was ' + (typeof name === 'undefined' ? 'undefined' : _typeof(name)) + '.');
25576      }
25577
25578      if (pluginExists(name)) {
25579        log$1.warn('A plugin named "' + name + '" already exists. You may want to avoid re-registering plugins!');
25580      } else if (Player.prototype.hasOwnProperty(name)) {
25581        throw new Error('Illegal plugin name, "' + name + '", cannot share a name with an existing player method!');
25582      }
25583
25584      if (typeof plugin !== 'function') {
25585        throw new Error('Illegal plugin for "' + name + '", must be a function, was ' + (typeof plugin === 'undefined' ? 'undefined' : _typeof(plugin)) + '.');
25586      }
25587
25588      pluginStorage[name] = plugin;
25589
25590      // Add a player prototype method for all sub-classed plugins (but not for
25591      // the base Plugin class).
25592      if (name !== BASE_PLUGIN_NAME) {
25593        if (Plugin.isBasic(plugin)) {
25594          Player.prototype[name] = createBasicPlugin(name, plugin);
25595        } else {
25596          Player.prototype[name] = createPluginFactory(name, plugin);
25597        }
25598      }
25599
25600      return plugin;
25601    };
25602
25603    /**
25604     * De-register a Video.js plugin.
25605     *
25606     * @param {string} name
25607     *        The name of the plugin to be deregistered.
25608     */
25609
25610
25611    Plugin.deregisterPlugin = function deregisterPlugin(name) {
25612      if (name === BASE_PLUGIN_NAME) {
25613        throw new Error('Cannot de-register base plugin.');
25614      }
25615      if (pluginExists(name)) {
25616        delete pluginStorage[name];
25617        delete Player.prototype[name];
25618      }
25619    };
25620
25621    /**
25622     * Gets an object containing multiple Video.js plugins.
25623     *
25624     * @param   {Array} [names]
25625     *          If provided, should be an array of plugin names. Defaults to _all_
25626     *          plugin names.
25627     *
25628     * @returns {Object|undefined}
25629     *          An object containing plugin(s) associated with their name(s) or
25630     *          `undefined` if no matching plugins exist).
25631     */
25632
25633
25634    Plugin.getPlugins = function getPlugins() {
25635      var names = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Object.keys(pluginStorage);
25636
25637      var result = void 0;
25638
25639      names.forEach(function (name) {
25640        var plugin = getPlugin(name);
25641
25642        if (plugin) {
25643          result = result || {};
25644          result[name] = plugin;
25645        }
25646      });
25647
25648      return result;
25649    };
25650
25651    /**
25652     * Gets a plugin's version, if available
25653     *
25654     * @param   {string} name
25655     *          The name of a plugin.
25656     *
25657     * @returns {string}
25658     *          The plugin's version or an empty string.
25659     */
25660
25661
25662    Plugin.getPluginVersion = function getPluginVersion(name) {
25663      var plugin = getPlugin(name);
25664
25665      return plugin && plugin.VERSION || '';
25666    };
25667
25668    return Plugin;
25669  }();
25670
25671  /**
25672   * Gets a plugin by name if it exists.
25673   *
25674   * @static
25675   * @method   getPlugin
25676   * @memberOf Plugin
25677   * @param    {string} name
25678   *           The name of a plugin.
25679   *
25680   * @returns  {Function|undefined}
25681   *           The plugin (or `undefined`).
25682   */
25683
25684
25685  Plugin.getPlugin = getPlugin;
25686
25687  /**
25688   * The name of the base plugin class as it is registered.
25689   *
25690   * @type {string}
25691   */
25692  Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME;
25693
25694  Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin);
25695
25696  /**
25697   * Documented in player.js
25698   *
25699   * @ignore
25700   */
25701  Player.prototype.usingPlugin = function (name) {
25702    return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true;
25703  };
25704
25705  /**
25706   * Documented in player.js
25707   *
25708   * @ignore
25709   */
25710  Player.prototype.hasPlugin = function (name) {
25711    return !!pluginExists(name);
25712  };
25713
25714  /**
25715   * @file extend.js
25716   * @module extend
25717   */
25718
25719  /**
25720   * A combination of node inherits and babel's inherits (after transpile).
25721   * Both work the same but node adds `super_` to the subClass
25722   * and Bable adds the superClass as __proto__. Both seem useful.
25723   *
25724   * @param {Object} subClass
25725   *        The class to inherit to
25726   *
25727   * @param {Object} superClass
25728   *        The class to inherit from
25729   *
25730   * @private
25731   */
25732  var _inherits = function _inherits(subClass, superClass) {
25733    if (typeof superClass !== 'function' && superClass !== null) {
25734      throw new TypeError('Super expression must either be null or a function, not ' + (typeof superClass === 'undefined' ? 'undefined' : _typeof(superClass)));
25735    }
25736
25737    subClass.prototype = Object.create(superClass && superClass.prototype, {
25738      constructor: {
25739        value: subClass,
25740        enumerable: false,
25741        writable: true,
25742        configurable: true
25743      }
25744    });
25745
25746    if (superClass) {
25747      // node
25748      subClass.super_ = superClass;
25749    }
25750  };
25751
25752  /**
25753   * Function for subclassing using the same inheritance that
25754   * videojs uses internally
25755   *
25756   * @static
25757   * @const
25758   *
25759   * @param {Object} superClass
25760   *        The class to inherit from
25761   *
25762   * @param {Object} [subClassMethods={}]
25763   *        The class to inherit to
25764   *
25765   * @return {Object}
25766   *         The new object with subClassMethods that inherited superClass.
25767   */
25768  var extendFn = function extendFn(superClass) {
25769    var subClassMethods = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
25770
25771    var subClass = function subClass() {
25772      superClass.apply(this, arguments);
25773    };
25774
25775    var methods = {};
25776
25777    if ((typeof subClassMethods === 'undefined' ? 'undefined' : _typeof(subClassMethods)) === 'object') {
25778      if (subClassMethods.constructor !== Object.prototype.constructor) {
25779        subClass = subClassMethods.constructor;
25780      }
25781      methods = subClassMethods;
25782    } else if (typeof subClassMethods === 'function') {
25783      subClass = subClassMethods;
25784    }
25785
25786    _inherits(subClass, superClass);
25787
25788    // Extend subObj's prototype with functions and other properties from props
25789    for (var name in methods) {
25790      if (methods.hasOwnProperty(name)) {
25791        subClass.prototype[name] = methods[name];
25792      }
25793    }
25794
25795    return subClass;
25796  };
25797
25798  /**
25799   * @file video.js
25800   * @module videojs
25801   */
25802
25803  /**
25804   * Normalize an `id` value by trimming off a leading `#`
25805   *
25806   * @param   {string} id
25807   *          A string, maybe with a leading `#`.
25808   *
25809   * @returns {string}
25810   *          The string, without any leading `#`.
25811   */
25812  var normalizeId = function normalizeId(id) {
25813    return id.indexOf('#') === 0 ? id.slice(1) : id;
25814  };
25815
25816  /**
25817   * Doubles as the main function for users to create a player instance and also
25818   * the main library object.
25819   * The `videojs` function can be used to initialize or retrieve a player.
25820    *
25821   * @param {string|Element} id
25822   *        Video element or video element ID
25823   *
25824   * @param {Object} [options]
25825   *        Optional options object for config/settings
25826   *
25827   * @param {Component~ReadyCallback} [ready]
25828   *        Optional ready callback
25829   *
25830   * @return {Player}
25831   *         A player instance
25832   */
25833  function videojs$1(id, options, ready) {
25834    var player = videojs$1.getPlayer(id);
25835
25836    if (player) {
25837      if (options) {
25838        log$1.warn('Player "' + id + '" is already initialised. Options will not be applied.');
25839      }
25840      if (ready) {
25841        player.ready(ready);
25842      }
25843      return player;
25844    }
25845
25846    var el = typeof id === 'string' ? $('#' + normalizeId(id)) : id;
25847
25848    if (!isEl(el)) {
25849      throw new TypeError('The element or ID supplied is not valid. (videojs)');
25850    }
25851
25852    if (!document_1.body.contains(el)) {
25853      log$1.warn('The element supplied is not included in the DOM');
25854    }
25855
25856    options = options || {};
25857
25858    videojs$1.hooks('beforesetup').forEach(function (hookFunction) {
25859      var opts = hookFunction(el, mergeOptions(options));
25860
25861      if (!isObject(opts) || Array.isArray(opts)) {
25862        log$1.error('please return an object in beforesetup hooks');
25863        return;
25864      }
25865
25866      options = mergeOptions(options, opts);
25867    });
25868
25869    // We get the current "Player" component here in case an integration has
25870    // replaced it with a custom player.
25871    var PlayerComponent = Component.getComponent('Player');
25872
25873    player = new PlayerComponent(el, options, ready);
25874
25875    videojs$1.hooks('setup').forEach(function (hookFunction) {
25876      return hookFunction(player);
25877    });
25878
25879    return player;
25880  }
25881
25882  /**
25883   * An Object that contains lifecycle hooks as keys which point to an array
25884   * of functions that are run when a lifecycle is triggered
25885   */
25886  videojs$1.hooks_ = {};
25887
25888  /**
25889   * Get a list of hooks for a specific lifecycle
25890   * @function videojs.hooks
25891   *
25892   * @param {string} type
25893   *        the lifecyle to get hooks from
25894   *
25895   * @param {Function|Function[]} [fn]
25896   *        Optionally add a hook (or hooks) to the lifecycle that your are getting.
25897   *
25898   * @return {Array}
25899   *         an array of hooks, or an empty array if there are none.
25900   */
25901  videojs$1.hooks = function (type, fn) {
25902    videojs$1.hooks_[type] = videojs$1.hooks_[type] || [];
25903    if (fn) {
25904      videojs$1.hooks_[type] = videojs$1.hooks_[type].concat(fn);
25905    }
25906    return videojs$1.hooks_[type];
25907  };
25908
25909  /**
25910   * Add a function hook to a specific videojs lifecycle.
25911   *
25912   * @param {string} type
25913   *        the lifecycle to hook the function to.
25914   *
25915   * @param {Function|Function[]}
25916   *        The function or array of functions to attach.
25917   */
25918  videojs$1.hook = function (type, fn) {
25919    videojs$1.hooks(type, fn);
25920  };
25921
25922  /**
25923   * Add a function hook that will only run once to a specific videojs lifecycle.
25924   *
25925   * @param {string} type
25926   *        the lifecycle to hook the function to.
25927   *
25928   * @param {Function|Function[]}
25929   *        The function or array of functions to attach.
25930   */
25931  videojs$1.hookOnce = function (type, fn) {
25932    videojs$1.hooks(type, [].concat(fn).map(function (original) {
25933      var wrapper = function wrapper() {
25934        videojs$1.removeHook(type, wrapper);
25935        return original.apply(undefined, arguments);
25936      };
25937
25938      return wrapper;
25939    }));
25940  };
25941
25942  /**
25943   * Remove a hook from a specific videojs lifecycle.
25944   *
25945   * @param {string} type
25946   *        the lifecycle that the function hooked to
25947   *
25948   * @param {Function} fn
25949   *        The hooked function to remove
25950   *
25951   * @return {boolean}
25952   *         The function that was removed or undef
25953   */
25954  videojs$1.removeHook = function (type, fn) {
25955    var index = videojs$1.hooks(type).indexOf(fn);
25956
25957    if (index <= -1) {
25958      return false;
25959    }
25960
25961    videojs$1.hooks_[type] = videojs$1.hooks_[type].slice();
25962    videojs$1.hooks_[type].splice(index, 1);
25963
25964    return true;
25965  };
25966
25967  // Add default styles
25968  if (window_1.VIDEOJS_NO_DYNAMIC_STYLE !== true && isReal()) {
25969    var style$1 = $('.vjs-styles-defaults');
25970
25971    if (!style$1) {
25972      style$1 = createStyleElement('vjs-styles-defaults');
25973      var head = $('head');
25974
25975      if (head) {
25976        head.insertBefore(style$1, head.firstChild);
25977      }
25978      setTextContent(style$1, '\n      .video-js {\n        width: 300px;\n        height: 150px;\n      }\n\n      .vjs-fluid {\n        padding-top: 56.25%\n      }\n    ');
25979    }
25980  }
25981
25982  // Run Auto-load players
25983  // You have to wait at least once in case this script is loaded after your
25984  // video in the DOM (weird behavior only with minified version)
25985  autoSetupTimeout(1, videojs$1);
25986
25987  /**
25988   * Current software version. Follows semver.
25989   *
25990   * @type {string}
25991   */
25992  videojs$1.VERSION = version;
25993
25994  /**
25995   * The global options object. These are the settings that take effect
25996   * if no overrides are specified when the player is created.
25997   *
25998   * @type {Object}
25999   */
26000  videojs$1.options = Player.prototype.options_;
26001
26002  /**
26003   * Get an object with the currently created players, keyed by player ID
26004   *
26005   * @return {Object}
26006   *         The created players
26007   */
26008  videojs$1.getPlayers = function () {
26009    return Player.players;
26010  };
26011
26012  /**
26013   * Get a single player based on an ID or DOM element.
26014   *
26015   * This is useful if you want to check if an element or ID has an associated
26016   * Video.js player, but not create one if it doesn't.
26017   *
26018   * @param   {string|Element} id
26019   *          An HTML element - `<video>`, `<audio>`, or `<video-js>` -
26020   *          or a string matching the `id` of such an element.
26021   *
26022   * @returns {Player|undefined}
26023   *          A player instance or `undefined` if there is no player instance
26024   *          matching the argument.
26025   */
26026  videojs$1.getPlayer = function (id) {
26027    var players = Player.players;
26028    var tag = void 0;
26029
26030    if (typeof id === 'string') {
26031      var nId = normalizeId(id);
26032      var player = players[nId];
26033
26034      if (player) {
26035        return player;
26036      }
26037
26038      tag = $('#' + nId);
26039    } else {
26040      tag = id;
26041    }
26042
26043    if (isEl(tag)) {
26044      var _tag = tag,
26045          _player = _tag.player,
26046          playerId = _tag.playerId;
26047
26048      // Element may have a `player` property referring to an already created
26049      // player instance. If so, return that.
26050
26051      if (_player || players[playerId]) {
26052        return _player || players[playerId];
26053      }
26054    }
26055  };
26056
26057  /**
26058   * Returns an array of all current players.
26059   *
26060   * @return {Array}
26061   *         An array of all players. The array will be in the order that
26062   *         `Object.keys` provides, which could potentially vary between
26063   *         JavaScript engines.
26064   *
26065   */
26066  videojs$1.getAllPlayers = function () {
26067    return (
26068
26069      // Disposed players leave a key with a `null` value, so we need to make sure
26070      // we filter those out.
26071      Object.keys(Player.players).map(function (k) {
26072        return Player.players[k];
26073      }).filter(Boolean)
26074    );
26075  };
26076
26077  /**
26078   * Expose players object.
26079   *
26080   * @memberOf videojs
26081   * @property {Object} players
26082   */
26083  videojs$1.players = Player.players;
26084
26085  /**
26086   * Get a component class object by name
26087   *
26088   * @borrows Component.getComponent as videojs.getComponent
26089   */
26090  videojs$1.getComponent = Component.getComponent;
26091
26092  /**
26093   * Register a component so it can referred to by name. Used when adding to other
26094   * components, either through addChild `component.addChild('myComponent')` or through
26095   * default children options  `{ children: ['myComponent'] }`.
26096   *
26097   * > NOTE: You could also just initialize the component before adding.
26098   * `component.addChild(new MyComponent());`
26099   *
26100   * @param {string} name
26101   *        The class name of the component
26102   *
26103   * @param {Component} comp
26104   *        The component class
26105   *
26106   * @return {Component}
26107   *         The newly registered component
26108   */
26109  videojs$1.registerComponent = function (name$$1, comp) {
26110    if (Tech.isTech(comp)) {
26111      log$1.warn('The ' + name$$1 + ' tech was registered as a component. It should instead be registered using videojs.registerTech(name, tech)');
26112    }
26113
26114    Component.registerComponent.call(Component, name$$1, comp);
26115  };
26116
26117  /**
26118   * Get a Tech class object by name
26119   *
26120   * @borrows Tech.getTech as videojs.getTech
26121   */
26122  videojs$1.getTech = Tech.getTech;
26123
26124  /**
26125   * Register a Tech so it can referred to by name.
26126   * This is used in the tech order for the player.
26127   *
26128   * @borrows Tech.registerTech as videojs.registerTech
26129   */
26130  videojs$1.registerTech = Tech.registerTech;
26131
26132  /**
26133   * Register a middleware to a source type.
26134   *
26135   * @param {String} type A string representing a MIME type.
26136   * @param {function(player):object} middleware A middleware factory that takes a player.
26137   */
26138  videojs$1.use = use;
26139
26140  /**
26141   * An object that can be returned by a middleware to signify
26142   * that the middleware is being terminated.
26143   *
26144   * @type {object}
26145   * @memberOf {videojs}
26146   * @property {object} middleware.TERMINATOR
26147   */
26148  Object.defineProperty(videojs$1, 'middleware', {
26149    value: {},
26150    writeable: false,
26151    enumerable: true
26152  });
26153
26154  Object.defineProperty(videojs$1.middleware, 'TERMINATOR', {
26155    value: TERMINATOR,
26156    writeable: false,
26157    enumerable: true
26158  });
26159
26160  /**
26161   * A suite of browser and device tests from {@link browser}.
26162   *
26163   * @type {Object}
26164   * @private
26165   */
26166  videojs$1.browser = browser;
26167
26168  /**
26169   * Whether or not the browser supports touch events. Included for backward
26170   * compatibility with 4.x, but deprecated. Use `videojs.browser.TOUCH_ENABLED`
26171   * instead going forward.
26172   *
26173   * @deprecated since version 5.0
26174   * @type {boolean}
26175   */
26176  videojs$1.TOUCH_ENABLED = TOUCH_ENABLED;
26177
26178  /**
26179   * Subclass an existing class
26180   * Mimics ES6 subclassing with the `extend` keyword
26181   *
26182   * @borrows extend:extendFn as videojs.extend
26183   */
26184  videojs$1.extend = extendFn;
26185
26186  /**
26187   * Merge two options objects recursively
26188   * Performs a deep merge like lodash.merge but **only merges plain objects**
26189   * (not arrays, elements, anything else)
26190   * Other values will be copied directly from the second object.
26191   *
26192   * @borrows merge-options:mergeOptions as videojs.mergeOptions
26193   */
26194  videojs$1.mergeOptions = mergeOptions;
26195
26196  /**
26197   * Change the context (this) of a function
26198   *
26199   * > NOTE: as of v5.0 we require an ES5 shim, so you should use the native
26200   * `function() {}.bind(newContext);` instead of this.
26201   *
26202   * @borrows fn:bind as videojs.bind
26203   */
26204  videojs$1.bind = bind;
26205
26206  /**
26207   * Register a Video.js plugin.
26208   *
26209   * @borrows plugin:registerPlugin as videojs.registerPlugin
26210   * @method registerPlugin
26211   *
26212   * @param  {string} name
26213   *         The name of the plugin to be registered. Must be a string and
26214   *         must not match an existing plugin or a method on the `Player`
26215   *         prototype.
26216   *
26217   * @param  {Function} plugin
26218   *         A sub-class of `Plugin` or a function for basic plugins.
26219   *
26220   * @return {Function}
26221   *         For advanced plugins, a factory function for that plugin. For
26222   *         basic plugins, a wrapper function that initializes the plugin.
26223   */
26224  videojs$1.registerPlugin = Plugin.registerPlugin;
26225
26226  /**
26227   * Deprecated method to register a plugin with Video.js
26228   *
26229   * @deprecated
26230   *        videojs.plugin() is deprecated; use videojs.registerPlugin() instead
26231   *
26232   * @param {string} name
26233   *        The plugin name
26234   *
26235   * @param {Plugin|Function} plugin
26236   *         The plugin sub-class or function
26237   */
26238  videojs$1.plugin = function (name$$1, plugin) {
26239    log$1.warn('videojs.plugin() is deprecated; use videojs.registerPlugin() instead');
26240    return Plugin.registerPlugin(name$$1, plugin);
26241  };
26242
26243  /**
26244   * Gets an object containing multiple Video.js plugins.
26245   *
26246   * @param  {Array} [names]
26247   *         If provided, should be an array of plugin names. Defaults to _all_
26248   *         plugin names.
26249   *
26250   * @return {Object|undefined}
26251   *         An object containing plugin(s) associated with their name(s) or
26252   *         `undefined` if no matching plugins exist).
26253   */
26254  videojs$1.getPlugins = Plugin.getPlugins;
26255
26256  /**
26257   * Gets a plugin by name if it exists.
26258   *
26259   * @param  {string} name
26260   *         The name of a plugin.
26261   *
26262   * @return {Function|undefined}
26263   *         The plugin (or `undefined`).
26264   */
26265  videojs$1.getPlugin = Plugin.getPlugin;
26266
26267  /**
26268   * Gets a plugin's version, if available
26269   *
26270   * @param  {string} name
26271   *         The name of a plugin.
26272   *
26273   * @return {string}
26274   *         The plugin's version or an empty string.
26275   */
26276  videojs$1.getPluginVersion = Plugin.getPluginVersion;
26277
26278  /**
26279   * Adding languages so that they're available to all players.
26280   * Example: `videojs.addLanguage('es', { 'Hello': 'Hola' });`
26281   *
26282   * @param {string} code
26283   *        The language code or dictionary property
26284   *
26285   * @param {Object} data
26286   *        The data values to be translated
26287   *
26288   * @return {Object}
26289   *         The resulting language dictionary object
26290   */
26291  videojs$1.addLanguage = function (code, data) {
26292    var _mergeOptions;
26293
26294    code = ('' + code).toLowerCase();
26295
26296    videojs$1.options.languages = mergeOptions(videojs$1.options.languages, (_mergeOptions = {}, _mergeOptions[code] = data, _mergeOptions));
26297
26298    return videojs$1.options.languages[code];
26299  };
26300
26301  /**
26302   * Log messages
26303   *
26304   * @borrows log:log as videojs.log
26305   */
26306  videojs$1.log = log$1;
26307
26308  /**
26309   * Creates an emulated TimeRange object.
26310   *
26311   * @borrows time-ranges:createTimeRanges as videojs.createTimeRange
26312   */
26313  /**
26314   * @borrows time-ranges:createTimeRanges as videojs.createTimeRanges
26315   */
26316  videojs$1.createTimeRange = videojs$1.createTimeRanges = createTimeRanges;
26317
26318  /**
26319   * Format seconds as a time string, H:MM:SS or M:SS
26320   * Supplying a guide (in seconds) will force a number of leading zeros
26321   * to cover the length of the guide
26322   *
26323   * @borrows format-time:formatTime as videojs.formatTime
26324   */
26325  videojs$1.formatTime = formatTime;
26326
26327  /**
26328   * Replaces format-time with a custom implementation, to be used in place of the default.
26329   *
26330   * @borrows format-time:setFormatTime as videojs.setFormatTime
26331   *
26332   * @method setFormatTime
26333   *
26334   * @param {Function} customFn
26335   *        A custom format-time function which will be called with the current time and guide (in seconds) as arguments.
26336   *        Passed fn should return a string.
26337   */
26338  videojs$1.setFormatTime = setFormatTime;
26339
26340  /**
26341   * Resets format-time to the default implementation.
26342   *
26343   * @borrows format-time:resetFormatTime as videojs.resetFormatTime
26344   *
26345   * @method resetFormatTime
26346   */
26347  videojs$1.resetFormatTime = resetFormatTime;
26348
26349  /**
26350   * Resolve and parse the elements of a URL
26351   *
26352   * @borrows url:parseUrl as videojs.parseUrl
26353   *
26354   */
26355  videojs$1.parseUrl = parseUrl;
26356
26357  /**
26358   * Returns whether the url passed is a cross domain request or not.
26359   *
26360   * @borrows url:isCrossOrigin as videojs.isCrossOrigin
26361   */
26362  videojs$1.isCrossOrigin = isCrossOrigin;
26363
26364  /**
26365   * Event target class.
26366   *
26367   * @borrows EventTarget as videojs.EventTarget
26368   */
26369  videojs$1.EventTarget = EventTarget;
26370
26371  /**
26372   * Add an event listener to element
26373   * It stores the handler function in a separate cache object
26374   * and adds a generic handler to the element's event,
26375   * along with a unique id (guid) to the element.
26376   *
26377   * @borrows events:on as videojs.on
26378   */
26379  videojs$1.on = on;
26380
26381  /**
26382   * Trigger a listener only once for an event
26383   *
26384   * @borrows events:one as videojs.one
26385   */
26386  videojs$1.one = one;
26387
26388  /**
26389   * Removes event listeners from an element
26390   *
26391   * @borrows events:off as videojs.off
26392   */
26393  videojs$1.off = off;
26394
26395  /**
26396   * Trigger an event for an element
26397   *
26398   * @borrows events:trigger as videojs.trigger
26399   */
26400  videojs$1.trigger = trigger;
26401
26402  /**
26403   * A cross-browser XMLHttpRequest wrapper. Here's a simple example:
26404   *
26405   * @param {Object} options
26406   *        settings for the request.
26407   *
26408   * @return {XMLHttpRequest|XDomainRequest}
26409   *         The request object.
26410   *
26411   * @see https://github.com/Raynos/xhr
26412   */
26413  videojs$1.xhr = xhr;
26414
26415  /**
26416   * TextTrack class
26417   *
26418   * @borrows TextTrack as videojs.TextTrack
26419   */
26420  videojs$1.TextTrack = TextTrack;
26421
26422  /**
26423   * export the AudioTrack class so that source handlers can create
26424   * AudioTracks and then add them to the players AudioTrackList
26425   *
26426   * @borrows AudioTrack as videojs.AudioTrack
26427   */
26428  videojs$1.AudioTrack = AudioTrack;
26429
26430  /**
26431   * export the VideoTrack class so that source handlers can create
26432   * VideoTracks and then add them to the players VideoTrackList
26433   *
26434   * @borrows VideoTrack as videojs.VideoTrack
26435   */
26436  videojs$1.VideoTrack = VideoTrack;
26437
26438  /**
26439   * Determines, via duck typing, whether or not a value is a DOM element.
26440   *
26441   * @borrows dom:isEl as videojs.isEl
26442   * @deprecated Use videojs.dom.isEl() instead
26443   */
26444
26445  /**
26446   * Determines, via duck typing, whether or not a value is a text node.
26447   *
26448   * @borrows dom:isTextNode as videojs.isTextNode
26449   * @deprecated Use videojs.dom.isTextNode() instead
26450   */
26451
26452  /**
26453   * Creates an element and applies properties.
26454   *
26455   * @borrows dom:createEl as videojs.createEl
26456   * @deprecated Use videojs.dom.createEl() instead
26457   */
26458
26459  /**
26460   * Check if an element has a CSS class
26461   *
26462   * @borrows dom:hasElClass as videojs.hasClass
26463   * @deprecated Use videojs.dom.hasClass() instead
26464   */
26465
26466  /**
26467   * Add a CSS class name to an element
26468   *
26469   * @borrows dom:addElClass as videojs.addClass
26470   * @deprecated Use videojs.dom.addClass() instead
26471   */
26472
26473  /**
26474   * Remove a CSS class name from an element
26475   *
26476   * @borrows dom:removeElClass as videojs.removeClass
26477   * @deprecated Use videojs.dom.removeClass() instead
26478   */
26479
26480  /**
26481   * Adds or removes a CSS class name on an element depending on an optional
26482   * condition or the presence/absence of the class name.
26483   *
26484   * @borrows dom:toggleElClass as videojs.toggleClass
26485   * @deprecated Use videojs.dom.toggleClass() instead
26486   */
26487
26488  /**
26489   * Apply attributes to an HTML element.
26490   *
26491   * @borrows dom:setElAttributes as videojs.setAttribute
26492   * @deprecated Use videojs.dom.setAttributes() instead
26493   */
26494
26495  /**
26496   * Get an element's attribute values, as defined on the HTML tag
26497   * Attributes are not the same as properties. They're defined on the tag
26498   * or with setAttribute (which shouldn't be used with HTML)
26499   * This will return true or false for boolean attributes.
26500   *
26501   * @borrows dom:getElAttributes as videojs.getAttributes
26502   * @deprecated Use videojs.dom.getAttributes() instead
26503   */
26504
26505  /**
26506   * Empties the contents of an element.
26507   *
26508   * @borrows dom:emptyEl as videojs.emptyEl
26509   * @deprecated Use videojs.dom.emptyEl() instead
26510   */
26511
26512  /**
26513   * Normalizes and appends content to an element.
26514   *
26515   * The content for an element can be passed in multiple types and
26516   * combinations, whose behavior is as follows:
26517   *
26518   * - String
26519   *   Normalized into a text node.
26520   *
26521   * - Element, TextNode
26522   *   Passed through.
26523   *
26524   * - Array
26525   *   A one-dimensional array of strings, elements, nodes, or functions (which
26526   *   return single strings, elements, or nodes).
26527   *
26528   * - Function
26529   *   If the sole argument, is expected to produce a string, element,
26530   *   node, or array.
26531   *
26532   * @borrows dom:appendContents as videojs.appendContet
26533   * @deprecated Use videojs.dom.appendContent() instead
26534   */
26535
26536  /**
26537   * Normalizes and inserts content into an element; this is identical to
26538   * `appendContent()`, except it empties the element first.
26539   *
26540   * The content for an element can be passed in multiple types and
26541   * combinations, whose behavior is as follows:
26542   *
26543   * - String
26544   *   Normalized into a text node.
26545   *
26546   * - Element, TextNode
26547   *   Passed through.
26548   *
26549   * - Array
26550   *   A one-dimensional array of strings, elements, nodes, or functions (which
26551   *   return single strings, elements, or nodes).
26552   *
26553   * - Function
26554   *   If the sole argument, is expected to produce a string, element,
26555   *   node, or array.
26556   *
26557   * @borrows dom:insertContent as videojs.insertContent
26558   * @deprecated Use videojs.dom.insertContent() instead
26559   */
26560  ['isEl', 'isTextNode', 'createEl', 'hasClass', 'addClass', 'removeClass', 'toggleClass', 'setAttributes', 'getAttributes', 'emptyEl', 'appendContent', 'insertContent'].forEach(function (k) {
26561    videojs$1[k] = function () {
26562      log$1.warn('videojs.' + k + '() is deprecated; use videojs.dom.' + k + '() instead');
26563      return Dom[k].apply(null, arguments);
26564    };
26565  });
26566
26567  /**
26568   * A safe getComputedStyle.
26569   *
26570   * This is because in Firefox, if the player is loaded in an iframe with `display:none`,
26571   * then `getComputedStyle` returns `null`, so, we do a null-check to make sure
26572   * that the player doesn't break in these cases.
26573   * See https://bugzilla.mozilla.org/show_bug.cgi?id=548397 for more details.
26574   *
26575   * @borrows computed-style:computedStyle as videojs.computedStyle
26576   */
26577  videojs$1.computedStyle = computedStyle;
26578
26579  /**
26580   * Export the Dom utilities for use in external plugins
26581   * and Tech's
26582   */
26583  videojs$1.dom = Dom;
26584
26585  /**
26586   * Export the Url utilities for use in external plugins
26587   * and Tech's
26588   */
26589  videojs$1.url = Url;
26590
26591  var urlToolkit = createCommonjsModule(function (module, exports) {
26592    // see https://tools.ietf.org/html/rfc1808
26593
26594    /* jshint ignore:start */
26595    (function (root) {
26596      /* jshint ignore:end */
26597
26598      var URL_REGEX = /^((?:[a-zA-Z0-9+\-.]+:)?)(\/\/[^\/\;?#]*)?(.*?)??(;.*?)?(\?.*?)?(#.*?)?$/;
26599      var FIRST_SEGMENT_REGEX = /^([^\/;?#]*)(.*)$/;
26600      var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g;
26601      var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/).*?(?=\/)/g;
26602
26603      var URLToolkit = { // jshint ignore:line
26604        // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or //
26605        // E.g
26606        // With opts.alwaysNormalize = false (default, spec compliant)
26607        // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g
26608        // With opts.alwaysNormalize = true (not spec compliant)
26609        // http://a.com/b/cd + /e/f/../g => http://a.com/e/g
26610        buildAbsoluteURL: function buildAbsoluteURL(baseURL, relativeURL, opts) {
26611          opts = opts || {};
26612          // remove any remaining space and CRLF
26613          baseURL = baseURL.trim();
26614          relativeURL = relativeURL.trim();
26615          if (!relativeURL) {
26616            // 2a) If the embedded URL is entirely empty, it inherits the
26617            // entire base URL (i.e., is set equal to the base URL)
26618            // and we are done.
26619            if (!opts.alwaysNormalize) {
26620              return baseURL;
26621            }
26622            var basePartsForNormalise = URLToolkit.parseURL(baseURL);
26623            if (!basePartsForNormalise) {
26624              throw new Error('Error trying to parse base URL.');
26625            }
26626            basePartsForNormalise.path = URLToolkit.normalizePath(basePartsForNormalise.path);
26627            return URLToolkit.buildURLFromParts(basePartsForNormalise);
26628          }
26629          var relativeParts = URLToolkit.parseURL(relativeURL);
26630          if (!relativeParts) {
26631            throw new Error('Error trying to parse relative URL.');
26632          }
26633          if (relativeParts.scheme) {
26634            // 2b) If the embedded URL starts with a scheme name, it is
26635            // interpreted as an absolute URL and we are done.
vendor: 2,564 bytes, lines 26636-26685
26636            if (!opts.alwaysNormalize) {
26637              return relativeURL;
26638            }
26639            relativeParts.path = URLToolkit.normalizePath(relativeParts.path);
26640            return URLToolkit.buildURLFromParts(relativeParts);
26641          }
26642          var baseParts = URLToolkit.parseURL(baseURL);
26643          if (!baseParts) {
26644            throw new Error('Error trying to parse base URL.');
26645          }
26646          if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') {
26647            // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc
26648            // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a'
26649            var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path);
26650            baseParts.netLoc = pathParts[1];
26651            baseParts.path = pathParts[2];
26652          }
26653          if (baseParts.netLoc && !baseParts.path) {
26654            baseParts.path = '/';
26655          }
26656          var builtParts = {
26657            // 2c) Otherwise, the embedded URL inherits the scheme of
26658            // the base URL.
26659            scheme: baseParts.scheme,
26660            netLoc: relativeParts.netLoc,
26661            path: null,
26662            params: relativeParts.params,
26663            query: relativeParts.query,
26664            fragment: relativeParts.fragment
26665          };
26666          if (!relativeParts.netLoc) {
26667            // 3) If the embedded URL's <net_loc> is non-empty, we skip to
26668            // Step 7.  Otherwise, the embedded URL inherits the <net_loc>
26669            // (if any) of the base URL.
26670            builtParts.netLoc = baseParts.netLoc;
26671            // 4) If the embedded URL path is preceded by a slash "/", the
26672            // path is not relative and we skip to Step 7.
26673            if (relativeParts.path[0] !== '/') {
26674              if (!relativeParts.path) {
26675                // 5) If the embedded URL path is empty (and not preceded by a
26676                // slash), then the embedded URL inherits the base URL path
26677                builtParts.path = baseParts.path;
26678                // 5a) if the embedded URL's <params> is non-empty, we skip to
26679                // step 7; otherwise, it inherits the <params> of the base
26680                // URL (if any) and
26681                if (!relativeParts.params) {
26682                  builtParts.params = baseParts.params;
26683                  // 5b) if the embedded URL's <query> is non-empty, we skip to
26684                  // step 7; otherwise, it inherits the <query> of the base
26685                  // URL (if any) and we skip to step 7.
vendor: 63,872 bytes, lines 26686-28606
26686                  if (!relativeParts.query) {
26687                    builtParts.query = baseParts.query;
26688                  }
26689                }
26690              } else {
26691                // 6) The last segment of the base URL's path (anything
26692                // following the rightmost slash "/", or the entire path if no
26693                // slash is present) is removed and the embedded URL's path is
26694                // appended in its place.
26695                var baseURLPath = baseParts.path;
26696                var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path;
26697                builtParts.path = URLToolkit.normalizePath(newPath);
26698              }
26699            }
26700          }
26701          if (builtParts.path === null) {
26702            builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path;
26703          }
26704          return URLToolkit.buildURLFromParts(builtParts);
26705        },
26706        parseURL: function parseURL(url) {
26707          var parts = URL_REGEX.exec(url);
26708          if (!parts) {
26709            return null;
26710          }
26711          return {
26712            scheme: parts[1] || '',
26713            netLoc: parts[2] || '',
26714            path: parts[3] || '',
26715            params: parts[4] || '',
26716            query: parts[5] || '',
26717            fragment: parts[6] || ''
26718          };
26719        },
26720        normalizePath: function normalizePath(path) {
26721          // The following operations are
26722          // then applied, in order, to the new path:
26723          // 6a) All occurrences of "./", where "." is a complete path
26724          // segment, are removed.
26725          // 6b) If the path ends with "." as a complete path segment,
26726          // that "." is removed.
26727          path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, '');
26728          // 6c) All occurrences of "<segment>/../", where <segment> is a
26729          // complete path segment not equal to "..", are removed.
26730          // Removal of these path segments is performed iteratively,
26731          // removing the leftmost matching pattern on each iteration,
26732          // until no matching pattern remains.
26733          // 6d) If the path ends with "<segment>/..", where <segment> is a
26734          // complete path segment not equal to "..", that
26735          // "<segment>/.." is removed.
26736          while (path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length) {} // jshint ignore:line
26737          return path.split('').reverse().join('');
26738        },
26739        buildURLFromParts: function buildURLFromParts(parts) {
26740          return parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment;
26741        }
26742      };
26743
26744      /* jshint ignore:start */
26745      module.exports = URLToolkit;
26746    })(commonjsGlobal);
26747    /* jshint ignore:end */
26748  });
26749
26750  var classCallCheck$1 = function classCallCheck$$1(instance, Constructor) {
26751    if (!(instance instanceof Constructor)) {
26752      throw new TypeError("Cannot call a class as a function");
26753    }
26754  };
26755
26756  var _extends$1 = Object.assign || function (target) {
26757    for (var i = 1; i < arguments.length; i++) {
26758      var source = arguments[i];
26759
26760      for (var key in source) {
26761        if (Object.prototype.hasOwnProperty.call(source, key)) {
26762          target[key] = source[key];
26763        }
26764      }
26765    }
26766
26767    return target;
26768  };
26769
26770  var inherits$1 = function inherits$$1(subClass, superClass) {
26771    if (typeof superClass !== "function" && superClass !== null) {
26772      throw new TypeError("Super expression must either be null or a function, not " + (typeof superClass === "undefined" ? "undefined" : _typeof(superClass)));
26773    }
26774
26775    subClass.prototype = Object.create(superClass && superClass.prototype, {
26776      constructor: {
26777        value: subClass,
26778        enumerable: false,
26779        writable: true,
26780        configurable: true
26781      }
26782    });
26783    if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
26784  };
26785
26786  var possibleConstructorReturn$1 = function possibleConstructorReturn$$1(self, call) {
26787    if (!self) {
26788      throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
26789    }
26790
26791    return call && ((typeof call === "undefined" ? "undefined" : _typeof(call)) === "object" || typeof call === "function") ? call : self;
26792  };
26793
26794  /**
26795   * @file stream.js
26796   */
26797  /**
26798   * A lightweight readable stream implemention that handles event dispatching.
26799   *
26800   * @class Stream
26801   */
26802  var Stream = function () {
26803    function Stream() {
26804      classCallCheck$1(this, Stream);
26805
26806      this.listeners = {};
26807    }
26808
26809    /**
26810     * Add a listener for a specified event type.
26811     *
26812     * @param {String} type the event name
26813     * @param {Function} listener the callback to be invoked when an event of
26814     * the specified type occurs
26815     */
26816
26817    Stream.prototype.on = function on(type, listener) {
26818      if (!this.listeners[type]) {
26819        this.listeners[type] = [];
26820      }
26821      this.listeners[type].push(listener);
26822    };
26823
26824    /**
26825     * Remove a listener for a specified event type.
26826     *
26827     * @param {String} type the event name
26828     * @param {Function} listener  a function previously registered for this
26829     * type of event through `on`
26830     * @return {Boolean} if we could turn it off or not
26831     */
26832
26833    Stream.prototype.off = function off(type, listener) {
26834      if (!this.listeners[type]) {
26835        return false;
26836      }
26837
26838      var index = this.listeners[type].indexOf(listener);
26839
26840      this.listeners[type].splice(index, 1);
26841      return index > -1;
26842    };
26843
26844    /**
26845     * Trigger an event of the specified type on this stream. Any additional
26846     * arguments to this function are passed as parameters to event listeners.
26847     *
26848     * @param {String} type the event name
26849     */
26850
26851    Stream.prototype.trigger = function trigger(type) {
26852      var callbacks = this.listeners[type];
26853      var i = void 0;
26854      var length = void 0;
26855      var args = void 0;
26856
26857      if (!callbacks) {
26858        return;
26859      }
26860      // Slicing the arguments on every invocation of this method
26861      // can add a significant amount of overhead. Avoid the
26862      // intermediate object creation for the common case of a
26863      // single callback argument
26864      if (arguments.length === 2) {
26865        length = callbacks.length;
26866        for (i = 0; i < length; ++i) {
26867          callbacks[i].call(this, arguments[1]);
26868        }
26869      } else {
26870        args = Array.prototype.slice.call(arguments, 1);
26871        length = callbacks.length;
26872        for (i = 0; i < length; ++i) {
26873          callbacks[i].apply(this, args);
26874        }
26875      }
26876    };
26877
26878    /**
26879     * Destroys the stream and cleans up.
26880     */
26881
26882    Stream.prototype.dispose = function dispose() {
26883      this.listeners = {};
26884    };
26885    /**
26886     * Forwards all `data` events on this stream to the destination stream. The
26887     * destination stream should provide a method `push` to receive the data
26888     * events as they arrive.
26889     *
26890     * @param {Stream} destination the stream that will receive all `data` events
26891     * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
26892     */
26893
26894    Stream.prototype.pipe = function pipe(destination) {
26895      this.on('data', function (data) {
26896        destination.push(data);
26897      });
26898    };
26899
26900    return Stream;
26901  }();
26902
26903  /**
26904   * @file m3u8/line-stream.js
26905   */
26906  /**
26907   * A stream that buffers string input and generates a `data` event for each
26908   * line.
26909   *
26910   * @class LineStream
26911   * @extends Stream
26912   */
26913
26914  var LineStream = function (_Stream) {
26915    inherits$1(LineStream, _Stream);
26916
26917    function LineStream() {
26918      classCallCheck$1(this, LineStream);
26919
26920      var _this = possibleConstructorReturn$1(this, _Stream.call(this));
26921
26922      _this.buffer = '';
26923      return _this;
26924    }
26925
26926    /**
26927     * Add new data to be parsed.
26928     *
26929     * @param {String} data the text to process
26930     */
26931
26932    LineStream.prototype.push = function push(data) {
26933      var nextNewline = void 0;
26934
26935      this.buffer += data;
26936      nextNewline = this.buffer.indexOf('\n');
26937
26938      for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) {
26939        this.trigger('data', this.buffer.substring(0, nextNewline));
26940        this.buffer = this.buffer.substring(nextNewline + 1);
26941      }
26942    };
26943
26944    return LineStream;
26945  }(Stream);
26946
26947  /**
26948   * @file m3u8/parse-stream.js
26949   */
26950  /**
26951   * "forgiving" attribute list psuedo-grammar:
26952   * attributes -> keyvalue (',' keyvalue)*
26953   * keyvalue   -> key '=' value
26954   * key        -> [^=]*
26955   * value      -> '"' [^"]* '"' | [^,]*
26956   */
26957  var attributeSeparator = function attributeSeparator() {
26958    var key = '[^=]*';
26959    var value = '"[^"]*"|[^,]*';
26960    var keyvalue = '(?:' + key + ')=(?:' + value + ')';
26961
26962    return new RegExp('(?:^|,)(' + keyvalue + ')');
26963  };
26964
26965  /**
26966   * Parse attributes from a line given the seperator
26967   *
26968   * @param {String} attributes the attibute line to parse
26969   */
26970  var parseAttributes = function parseAttributes(attributes) {
26971    // split the string using attributes as the separator
26972    var attrs = attributes.split(attributeSeparator());
26973    var result = {};
26974    var i = attrs.length;
26975    var attr = void 0;
26976
26977    while (i--) {
26978      // filter out unmatched portions of the string
26979      if (attrs[i] === '') {
26980        continue;
26981      }
26982
26983      // split the key and value
26984      attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1);
26985      // trim whitespace and remove optional quotes around the value
26986      attr[0] = attr[0].replace(/^\s+|\s+$/g, '');
26987      attr[1] = attr[1].replace(/^\s+|\s+$/g, '');
26988      attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1');
26989      result[attr[0]] = attr[1];
26990    }
26991    return result;
26992  };
26993
26994  /**
26995   * A line-level M3U8 parser event stream. It expects to receive input one
26996   * line at a time and performs a context-free parse of its contents. A stream
26997   * interpretation of a manifest can be useful if the manifest is expected to
26998   * be too large to fit comfortably into memory or the entirety of the input
26999   * is not immediately available. Otherwise, it's probably much easier to work
27000   * with a regular `Parser` object.
27001   *
27002   * Produces `data` events with an object that captures the parser's
27003   * interpretation of the input. That object has a property `tag` that is one
27004   * of `uri`, `comment`, or `tag`. URIs only have a single additional
27005   * property, `line`, which captures the entirety of the input without
27006   * interpretation. Comments similarly have a single additional property
27007   * `text` which is the input without the leading `#`.
27008   *
27009   * Tags always have a property `tagType` which is the lower-cased version of
27010   * the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance,
27011   * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized
27012   * tags are given the tag type `unknown` and a single additional property
27013   * `data` with the remainder of the input.
27014   *
27015   * @class ParseStream
27016   * @extends Stream
27017   */
27018
27019  var ParseStream = function (_Stream) {
27020    inherits$1(ParseStream, _Stream);
27021
27022    function ParseStream() {
27023      classCallCheck$1(this, ParseStream);
27024
27025      var _this = possibleConstructorReturn$1(this, _Stream.call(this));
27026
27027      _this.customParsers = [];
27028      return _this;
27029    }
27030
27031    /**
27032     * Parses an additional line of input.
27033     *
27034     * @param {String} line a single line of an M3U8 file to parse
27035     */
27036
27037    ParseStream.prototype.push = function push(line) {
27038      var match = void 0;
27039      var event = void 0;
27040
27041      // strip whitespace
27042      line = line.replace(/^[\u0000\s]+|[\u0000\s]+$/g, '');
27043      if (line.length === 0) {
27044        // ignore empty lines
27045        return;
27046      }
27047
27048      // URIs
27049      if (line[0] !== '#') {
27050        this.trigger('data', {
27051          type: 'uri',
27052          uri: line
27053        });
27054        return;
27055      }
27056
27057      for (var i = 0; i < this.customParsers.length; i++) {
27058        if (this.customParsers[i].call(this, line)) {
27059          return;
27060        }
27061      }
27062
27063      // Comments
27064      if (line.indexOf('#EXT') !== 0) {
27065        this.trigger('data', {
27066          type: 'comment',
27067          text: line.slice(1)
27068        });
27069        return;
27070      }
27071
27072      // strip off any carriage returns here so the regex matching
27073      // doesn't have to account for them.
27074      line = line.replace('\r', '');
27075
27076      // Tags
27077      match = /^#EXTM3U/.exec(line);
27078      if (match) {
27079        this.trigger('data', {
27080          type: 'tag',
27081          tagType: 'm3u'
27082        });
27083        return;
27084      }
27085      match = /^#EXTINF:?([0-9\.]*)?,?(.*)?$/.exec(line);
27086      if (match) {
27087        event = {
27088          type: 'tag',
27089          tagType: 'inf'
27090        };
27091        if (match[1]) {
27092          event.duration = parseFloat(match[1]);
27093        }
27094        if (match[2]) {
27095          event.title = match[2];
27096        }
27097        this.trigger('data', event);
27098        return;
27099      }
27100      match = /^#EXT-X-TARGETDURATION:?([0-9.]*)?/.exec(line);
27101      if (match) {
27102        event = {
27103          type: 'tag',
27104          tagType: 'targetduration'
27105        };
27106        if (match[1]) {
27107          event.duration = parseInt(match[1], 10);
27108        }
27109        this.trigger('data', event);
27110        return;
27111      }
27112      match = /^#ZEN-TOTAL-DURATION:?([0-9.]*)?/.exec(line);
27113      if (match) {
27114        event = {
27115          type: 'tag',
27116          tagType: 'totalduration'
27117        };
27118        if (match[1]) {
27119          event.duration = parseInt(match[1], 10);
27120        }
27121        this.trigger('data', event);
27122        return;
27123      }
27124      match = /^#EXT-X-VERSION:?([0-9.]*)?/.exec(line);
27125      if (match) {
27126        event = {
27127          type: 'tag',
27128          tagType: 'version'
27129        };
27130        if (match[1]) {
27131          event.version = parseInt(match[1], 10);
27132        }
27133        this.trigger('data', event);
27134        return;
27135      }
27136      match = /^#EXT-X-MEDIA-SEQUENCE:?(\-?[0-9.]*)?/.exec(line);
27137      if (match) {
27138        event = {
27139          type: 'tag',
27140          tagType: 'media-sequence'
27141        };
27142        if (match[1]) {
27143          event.number = parseInt(match[1], 10);
27144        }
27145        this.trigger('data', event);
27146        return;
27147      }
27148      match = /^#EXT-X-DISCONTINUITY-SEQUENCE:?(\-?[0-9.]*)?/.exec(line);
27149      if (match) {
27150        event = {
27151          type: 'tag',
27152          tagType: 'discontinuity-sequence'
27153        };
27154        if (match[1]) {
27155          event.number = parseInt(match[1], 10);
27156        }
27157        this.trigger('data', event);
27158        return;
27159      }
27160      match = /^#EXT-X-PLAYLIST-TYPE:?(.*)?$/.exec(line);
27161      if (match) {
27162        event = {
27163          type: 'tag',
27164          tagType: 'playlist-type'
27165        };
27166        if (match[1]) {
27167          event.playlistType = match[1];
27168        }
27169        this.trigger('data', event);
27170        return;
27171      }
27172      match = /^#EXT-X-BYTERANGE:?([0-9.]*)?@?([0-9.]*)?/.exec(line);
27173      if (match) {
27174        event = {
27175          type: 'tag',
27176          tagType: 'byterange'
27177        };
27178        if (match[1]) {
27179          event.length = parseInt(match[1], 10);
27180        }
27181        if (match[2]) {
27182          event.offset = parseInt(match[2], 10);
27183        }
27184        this.trigger('data', event);
27185        return;
27186      }
27187      match = /^#EXT-X-ALLOW-CACHE:?(YES|NO)?/.exec(line);
27188      if (match) {
27189        event = {
27190          type: 'tag',
27191          tagType: 'allow-cache'
27192        };
27193        if (match[1]) {
27194          event.allowed = !/NO/.test(match[1]);
27195        }
27196        this.trigger('data', event);
27197        return;
27198      }
27199      match = /^#EXT-X-MAP:?(.*)$/.exec(line);
27200      if (match) {
27201        event = {
27202          type: 'tag',
27203          tagType: 'map'
27204        };
27205
27206        if (match[1]) {
27207          var attributes = parseAttributes(match[1]);
27208
27209          if (attributes.URI) {
27210            event.uri = attributes.URI;
27211          }
27212          if (attributes.BYTERANGE) {
27213            var _attributes$BYTERANGE = attributes.BYTERANGE.split('@'),
27214                length = _attributes$BYTERANGE[0],
27215                offset = _attributes$BYTERANGE[1];
27216
27217            event.byterange = {};
27218            if (length) {
27219              event.byterange.length = parseInt(length, 10);
27220            }
27221            if (offset) {
27222              event.byterange.offset = parseInt(offset, 10);
27223            }
27224          }
27225        }
27226
27227        this.trigger('data', event);
27228        return;
27229      }
27230      match = /^#EXT-X-STREAM-INF:?(.*)$/.exec(line);
27231      if (match) {
27232        event = {
27233          type: 'tag',
27234          tagType: 'stream-inf'
27235        };
27236        if (match[1]) {
27237          event.attributes = parseAttributes(match[1]);
27238
27239          if (event.attributes.RESOLUTION) {
27240            var split = event.attributes.RESOLUTION.split('x');
27241            var resolution = {};
27242
27243            if (split[0]) {
27244              resolution.width = parseInt(split[0], 10);
27245            }
27246            if (split[1]) {
27247              resolution.height = parseInt(split[1], 10);
27248            }
27249            event.attributes.RESOLUTION = resolution;
27250          }
27251          if (event.attributes.BANDWIDTH) {
27252            event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
27253          }
27254          if (event.attributes['PROGRAM-ID']) {
27255            event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10);
27256          }
27257        }
27258        this.trigger('data', event);
27259        return;
27260      }
27261      match = /^#EXT-X-MEDIA:?(.*)$/.exec(line);
27262      if (match) {
27263        event = {
27264          type: 'tag',
27265          tagType: 'media'
27266        };
27267        if (match[1]) {
27268          event.attributes = parseAttributes(match[1]);
27269        }
27270        this.trigger('data', event);
27271        return;
27272      }
27273      match = /^#EXT-X-ENDLIST/.exec(line);
27274      if (match) {
27275        this.trigger('data', {
27276          type: 'tag',
27277          tagType: 'endlist'
27278        });
27279        return;
27280      }
27281      match = /^#EXT-X-DISCONTINUITY/.exec(line);
27282      if (match) {
27283        this.trigger('data', {
27284          type: 'tag',
27285          tagType: 'discontinuity'
27286        });
27287        return;
27288      }
27289      match = /^#EXT-X-PROGRAM-DATE-TIME:?(.*)$/.exec(line);
27290      if (match) {
27291        event = {
27292          type: 'tag',
27293          tagType: 'program-date-time'
27294        };
27295        if (match[1]) {
27296          event.dateTimeString = match[1];
27297          event.dateTimeObject = new Date(match[1]);
27298        }
27299        this.trigger('data', event);
27300        return;
27301      }
27302      match = /^#EXT-X-KEY:?(.*)$/.exec(line);
27303      if (match) {
27304        event = {
27305          type: 'tag',
27306          tagType: 'key'
27307        };
27308        if (match[1]) {
27309          event.attributes = parseAttributes(match[1]);
27310          // parse the IV string into a Uint32Array
27311          if (event.attributes.IV) {
27312            if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') {
27313              event.attributes.IV = event.attributes.IV.substring(2);
27314            }
27315
27316            event.attributes.IV = event.attributes.IV.match(/.{8}/g);
27317            event.attributes.IV[0] = parseInt(event.attributes.IV[0], 16);
27318            event.attributes.IV[1] = parseInt(event.attributes.IV[1], 16);
27319            event.attributes.IV[2] = parseInt(event.attributes.IV[2], 16);
27320            event.attributes.IV[3] = parseInt(event.attributes.IV[3], 16);
27321            event.attributes.IV = new Uint32Array(event.attributes.IV);
27322          }
27323        }
27324        this.trigger('data', event);
27325        return;
27326      }
27327      match = /^#EXT-X-START:?(.*)$/.exec(line);
27328      if (match) {
27329        event = {
27330          type: 'tag',
27331          tagType: 'start'
27332        };
27333        if (match[1]) {
27334          event.attributes = parseAttributes(match[1]);
27335
27336          event.attributes['TIME-OFFSET'] = parseFloat(event.attributes['TIME-OFFSET']);
27337          event.attributes.PRECISE = /YES/.test(event.attributes.PRECISE);
27338        }
27339        this.trigger('data', event);
27340        return;
27341      }
27342      match = /^#EXT-X-CUE-OUT-CONT:?(.*)?$/.exec(line);
27343      if (match) {
27344        event = {
27345          type: 'tag',
27346          tagType: 'cue-out-cont'
27347        };
27348        if (match[1]) {
27349          event.data = match[1];
27350        } else {
27351          event.data = '';
27352        }
27353        this.trigger('data', event);
27354        return;
27355      }
27356      match = /^#EXT-X-CUE-OUT:?(.*)?$/.exec(line);
27357      if (match) {
27358        event = {
27359          type: 'tag',
27360          tagType: 'cue-out'
27361        };
27362        if (match[1]) {
27363          event.data = match[1];
27364        } else {
27365          event.data = '';
27366        }
27367        this.trigger('data', event);
27368        return;
27369      }
27370      match = /^#EXT-X-CUE-IN:?(.*)?$/.exec(line);
27371      if (match) {
27372        event = {
27373          type: 'tag',
27374          tagType: 'cue-in'
27375        };
27376        if (match[1]) {
27377          event.data = match[1];
27378        } else {
27379          event.data = '';
27380        }
27381        this.trigger('data', event);
27382        return;
27383      }
27384
27385      // unknown tag type
27386      this.trigger('data', {
27387        type: 'tag',
27388        data: line.slice(4)
27389      });
27390    };
27391
27392    /**
27393     * Add a parser for custom headers
27394     *
27395     * @param {Object}   options              a map of options for the added parser
27396     * @param {RegExp}   options.expression   a regular expression to match the custom header
27397     * @param {string}   options.customType   the custom type to register to the output
27398     * @param {Function} [options.dataParser] function to parse the line into an object
27399     * @param {boolean}  [options.segment]    should tag data be attached to the segment object
27400     */
27401
27402    ParseStream.prototype.addParser = function addParser(_ref) {
27403      var _this2 = this;
27404
27405      var expression = _ref.expression,
27406          customType = _ref.customType,
27407          dataParser = _ref.dataParser,
27408          segment = _ref.segment;
27409
27410      if (typeof dataParser !== 'function') {
27411        dataParser = function dataParser(line) {
27412          return line;
27413        };
27414      }
27415      this.customParsers.push(function (line) {
27416        var match = expression.exec(line);
27417
27418        if (match) {
27419          _this2.trigger('data', {
27420            type: 'custom',
27421            data: dataParser(line),
27422            customType: customType,
27423            segment: segment
27424          });
27425          return true;
27426        }
27427      });
27428    };
27429
27430    return ParseStream;
27431  }(Stream);
27432
27433  /**
27434   * @file m3u8/parser.js
27435   */
27436  /**
27437   * A parser for M3U8 files. The current interpretation of the input is
27438   * exposed as a property `manifest` on parser objects. It's just two lines to
27439   * create and parse a manifest once you have the contents available as a string:
27440   *
27441   * ```js
27442   * var parser = new m3u8.Parser();
27443   * parser.push(xhr.responseText);
27444   * ```
27445   *
27446   * New input can later be applied to update the manifest object by calling
27447   * `push` again.
27448   *
27449   * The parser attempts to create a usable manifest object even if the
27450   * underlying input is somewhat nonsensical. It emits `info` and `warning`
27451   * events during the parse if it encounters input that seems invalid or
27452   * requires some property of the manifest object to be defaulted.
27453   *
27454   * @class Parser
27455   * @extends Stream
27456   */
27457
27458  var Parser = function (_Stream) {
27459    inherits$1(Parser, _Stream);
27460
27461    function Parser() {
27462      classCallCheck$1(this, Parser);
27463
27464      var _this = possibleConstructorReturn$1(this, _Stream.call(this));
27465
27466      _this.lineStream = new LineStream();
27467      _this.parseStream = new ParseStream();
27468      _this.lineStream.pipe(_this.parseStream);
27469
27470      /* eslint-disable consistent-this */
27471      var self = _this;
27472      /* eslint-enable consistent-this */
27473      var uris = [];
27474      var currentUri = {};
27475      // if specified, the active EXT-X-MAP definition
27476      var currentMap = void 0;
27477      // if specified, the active decryption key
27478      var _key = void 0;
27479      var noop = function noop() {};
27480      var defaultMediaGroups = {
27481        'AUDIO': {},
27482        'VIDEO': {},
27483        'CLOSED-CAPTIONS': {},
27484        'SUBTITLES': {}
27485      };
27486      // group segments into numbered timelines delineated by discontinuities
27487      var currentTimeline = 0;
27488
27489      // the manifest is empty until the parse stream begins delivering data
27490      _this.manifest = {
27491        allowCache: true,
27492        discontinuityStarts: [],
27493        segments: []
27494      };
27495
27496      // update the manifest with the m3u8 entry from the parse stream
27497      _this.parseStream.on('data', function (entry) {
27498        var mediaGroup = void 0;
27499        var rendition = void 0;
27500
27501        ({
27502          tag: function tag() {
27503            // switch based on the tag type
27504            (({
27505              'allow-cache': function allowCache() {
27506                this.manifest.allowCache = entry.allowed;
27507                if (!('allowed' in entry)) {
27508                  this.trigger('info', {
27509                    message: 'defaulting allowCache to YES'
27510                  });
27511                  this.manifest.allowCache = true;
27512                }
27513              },
27514              byterange: function byterange() {
27515                var byterange = {};
27516
27517                if ('length' in entry) {
27518                  currentUri.byterange = byterange;
27519                  byterange.length = entry.length;
27520
27521                  if (!('offset' in entry)) {
27522                    this.trigger('info', {
27523                      message: 'defaulting offset to zero'
27524                    });
27525                    entry.offset = 0;
27526                  }
27527                }
27528                if ('offset' in entry) {
27529                  currentUri.byterange = byterange;
27530                  byterange.offset = entry.offset;
27531                }
27532              },
27533              endlist: function endlist() {
27534                this.manifest.endList = true;
27535              },
27536              inf: function inf() {
27537                if (!('mediaSequence' in this.manifest)) {
27538                  this.manifest.mediaSequence = 0;
27539                  this.trigger('info', {
27540                    message: 'defaulting media sequence to zero'
27541                  });
27542                }
27543                if (!('discontinuitySequence' in this.manifest)) {
27544                  this.manifest.discontinuitySequence = 0;
27545                  this.trigger('info', {
27546                    message: 'defaulting discontinuity sequence to zero'
27547                  });
27548                }
27549                if (entry.duration > 0) {
27550                  currentUri.duration = entry.duration;
27551                }
27552
27553                if (entry.duration === 0) {
27554                  currentUri.duration = 0.01;
27555                  this.trigger('info', {
27556                    message: 'updating zero segment duration to a small value'
27557                  });
27558                }
27559
27560                this.manifest.segments = uris;
27561              },
27562              key: function key() {
27563                if (!entry.attributes) {
27564                  this.trigger('warn', {
27565                    message: 'ignoring key declaration without attribute list'
27566                  });
27567                  return;
27568                }
27569                // clear the active encryption key
27570                if (entry.attributes.METHOD === 'NONE') {
27571                  _key = null;
27572                  return;
27573                }
27574                if (!entry.attributes.URI) {
27575                  this.trigger('warn', {
27576                    message: 'ignoring key declaration without URI'
27577                  });
27578                  return;
27579                }
27580                if (!entry.attributes.METHOD) {
27581                  this.trigger('warn', {
27582                    message: 'defaulting key method to AES-128'
27583                  });
27584                }
27585
27586                // setup an encryption key for upcoming segments
27587                _key = {
27588                  method: entry.attributes.METHOD || 'AES-128',
27589                  uri: entry.attributes.URI
27590                };
27591
27592                if (typeof entry.attributes.IV !== 'undefined') {
27593                  _key.iv = entry.attributes.IV;
27594                }
27595              },
27596              'media-sequence': function mediaSequence() {
27597                if (!isFinite(entry.number)) {
27598                  this.trigger('warn', {
27599                    message: 'ignoring invalid media sequence: ' + entry.number
27600                  });
27601                  return;
27602                }
27603                this.manifest.mediaSequence = entry.number;
27604              },
27605              'discontinuity-sequence': function discontinuitySequence() {
27606                if (!isFinite(entry.number)) {
27607                  this.trigger('warn', {
27608                    message: 'ignoring invalid discontinuity sequence: ' + entry.number
27609                  });
27610                  return;
27611                }
27612                this.manifest.discontinuitySequence = entry.number;
27613                currentTimeline = entry.number;
27614              },
27615              'playlist-type': function playlistType() {
27616                if (!/VOD|EVENT/.test(entry.playlistType)) {
27617                  this.trigger('warn', {
27618                    message: 'ignoring unknown playlist type: ' + entry.playlist
27619                  });
27620                  return;
27621                }
27622                this.manifest.playlistType = entry.playlistType;
27623              },
27624              map: function map() {
27625                currentMap = {};
27626                if (entry.uri) {
27627                  currentMap.uri = entry.uri;
27628                }
27629                if (entry.byterange) {
27630                  currentMap.byterange = entry.byterange;
27631                }
27632              },
27633              'stream-inf': function streamInf() {
27634                this.manifest.playlists = uris;
27635                this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
27636
27637                if (!entry.attributes) {
27638                  this.trigger('warn', {
27639                    message: 'ignoring empty stream-inf attributes'
27640                  });
27641                  return;
27642                }
27643
27644                if (!currentUri.attributes) {
27645                  currentUri.attributes = {};
27646                }
27647                _extends$1(currentUri.attributes, entry.attributes);
27648              },
27649              media: function media() {
27650                this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
27651
27652                if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) {
27653                  this.trigger('warn', {
27654                    message: 'ignoring incomplete or missing media group'
27655                  });
27656                  return;
27657                }
27658
27659                // find the media group, creating defaults as necessary
27660                var mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE];
27661
27662                mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {};
27663                mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']];
27664
27665                // collect the rendition metadata
27666                rendition = {
27667                  'default': /yes/i.test(entry.attributes.DEFAULT)
27668                };
27669                if (rendition['default']) {
27670                  rendition.autoselect = true;
27671                } else {
27672                  rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT);
27673                }
27674                if (entry.attributes.LANGUAGE) {
27675                  rendition.language = entry.attributes.LANGUAGE;
27676                }
27677                if (entry.attributes.URI) {
27678                  rendition.uri = entry.attributes.URI;
27679                }
27680                if (entry.attributes['INSTREAM-ID']) {
27681                  rendition.instreamId = entry.attributes['INSTREAM-ID'];
27682                }
27683                if (entry.attributes.CHARACTERISTICS) {
27684                  rendition.characteristics = entry.attributes.CHARACTERISTICS;
27685                }
27686                if (entry.attributes.FORCED) {
27687                  rendition.forced = /yes/i.test(entry.attributes.FORCED);
27688                }
27689
27690                // insert the new rendition
27691                mediaGroup[entry.attributes.NAME] = rendition;
27692              },
27693              discontinuity: function discontinuity() {
27694                currentTimeline += 1;
27695                currentUri.discontinuity = true;
27696                this.manifest.discontinuityStarts.push(uris.length);
27697              },
27698              'program-date-time': function programDateTime() {
27699                if (typeof this.manifest.dateTimeString === 'undefined') {
27700                  // PROGRAM-DATE-TIME is a media-segment tag, but for backwards
27701                  // compatibility, we add the first occurence of the PROGRAM-DATE-TIME tag
27702                  // to the manifest object
27703                  // TODO: Consider removing this in future major version
27704                  this.manifest.dateTimeString = entry.dateTimeString;
27705                  this.manifest.dateTimeObject = entry.dateTimeObject;
27706                }
27707
27708                currentUri.dateTimeString = entry.dateTimeString;
27709                currentUri.dateTimeObject = entry.dateTimeObject;
27710              },
27711              targetduration: function targetduration() {
27712                if (!isFinite(entry.duration) || entry.duration < 0) {
27713                  this.trigger('warn', {
27714                    message: 'ignoring invalid target duration: ' + entry.duration
27715                  });
27716                  return;
27717                }
27718                this.manifest.targetDuration = entry.duration;
27719              },
27720              totalduration: function totalduration() {
27721                if (!isFinite(entry.duration) || entry.duration < 0) {
27722                  this.trigger('warn', {
27723                    message: 'ignoring invalid total duration: ' + entry.duration
27724                  });
27725                  return;
27726                }
27727                this.manifest.totalDuration = entry.duration;
27728              },
27729              start: function start() {
27730                if (!entry.attributes || isNaN(entry.attributes['TIME-OFFSET'])) {
27731                  this.trigger('warn', {
27732                    message: 'ignoring start declaration without appropriate attribute list'
27733                  });
27734                  return;
27735                }
27736                this.manifest.start = {
27737                  timeOffset: entry.attributes['TIME-OFFSET'],
27738                  precise: entry.attributes.PRECISE
27739                };
27740              },
27741              'cue-out': function cueOut() {
27742                currentUri.cueOut = entry.data;
27743              },
27744              'cue-out-cont': function cueOutCont() {
27745                currentUri.cueOutCont = entry.data;
27746              },
27747              'cue-in': function cueIn() {
27748                currentUri.cueIn = entry.data;
27749              }
27750            })[entry.tagType] || noop).call(self);
27751          },
27752          uri: function uri() {
27753            currentUri.uri = entry.uri;
27754            uris.push(currentUri);
27755
27756            // if no explicit duration was declared, use the target duration
27757            if (this.manifest.targetDuration && !('duration' in currentUri)) {
27758              this.trigger('warn', {
27759                message: 'defaulting segment duration to the target duration'
27760              });
27761              currentUri.duration = this.manifest.targetDuration;
27762            }
27763            // annotate with encryption information, if necessary
27764            if (_key) {
27765              currentUri.key = _key;
27766            }
27767            currentUri.timeline = currentTimeline;
27768            // annotate with initialization segment information, if necessary
27769            if (currentMap) {
27770              currentUri.map = currentMap;
27771            }
27772
27773            // prepare for the next URI
27774            currentUri = {};
27775          },
27776          comment: function comment() {
27777            // comments are not important for playback
27778          },
27779          custom: function custom() {
27780            // if this is segment-level data attach the output to the segment
27781            if (entry.segment) {
27782              currentUri.custom = currentUri.custom || {};
27783              currentUri.custom[entry.customType] = entry.data;
27784              // if this is manifest-level data attach to the top level manifest object
27785            } else {
27786              this.manifest.custom = this.manifest.custom || {};
27787              this.manifest.custom[entry.customType] = entry.data;
27788            }
27789          }
27790        })[entry.type].call(self);
27791      });
27792      return _this;
27793    }
27794
27795    /**
27796     * Parse the input string and update the manifest object.
27797     *
27798     * @param {String} chunk a potentially incomplete portion of the manifest
27799     */
27800
27801    Parser.prototype.push = function push(chunk) {
27802      this.lineStream.push(chunk);
27803    };
27804
27805    /**
27806     * Flush any remaining input. This can be handy if the last line of an M3U8
27807     * manifest did not contain a trailing newline but the file has been
27808     * completely received.
27809     */
27810
27811    Parser.prototype.end = function end() {
27812      // flush any buffered input
27813      this.lineStream.push('\n');
27814    };
27815    /**
27816     * Add an additional parser for non-standard tags
27817     *
27818     * @param {Object}   options              a map of options for the added parser
27819     * @param {RegExp}   options.expression   a regular expression to match the custom header
27820     * @param {string}   options.type         the type to register to the output
27821     * @param {Function} [options.dataParser] function to parse the line into an object
27822     * @param {boolean}  [options.segment]    should tag data be attached to the segment object
27823     */
27824
27825    Parser.prototype.addParser = function addParser(options) {
27826      this.parseStream.addParser(options);
27827    };
27828
27829    return Parser;
27830  }(Stream);
27831
27832  /**
27833   * mpd-parser
27834   * @version 0.6.1
27835   * @copyright 2018 Brightcove, Inc
27836   * @license Apache-2.0
27837   */
27838
27839  var formatAudioPlaylist = function formatAudioPlaylist(_ref) {
27840    var _attributes;
27841
27842    var attributes = _ref.attributes,
27843        segments = _ref.segments;
27844
27845    var playlist = {
27846      attributes: (_attributes = {
27847        NAME: attributes.id,
27848        BANDWIDTH: attributes.bandwidth,
27849        CODECS: attributes.codecs
27850      }, _attributes['PROGRAM-ID'] = 1, _attributes),
27851      uri: '',
27852      endList: (attributes.type || 'static') === 'static',
27853      timeline: attributes.periodIndex,
27854      resolvedUri: '',
27855      targetDuration: attributes.duration,
27856      segments: segments,
27857      mediaSequence: segments.length ? segments[0].number : 1
27858    };
27859
27860    if (attributes.contentProtection) {
27861      playlist.contentProtection = attributes.contentProtection;
27862    }
27863
27864    return playlist;
27865  };
27866
27867  var formatVttPlaylist = function formatVttPlaylist(_ref2) {
27868    var _attributes2;
27869
27870    var attributes = _ref2.attributes,
27871        segments = _ref2.segments;
27872
27873    if (typeof segments === 'undefined') {
27874      // vtt tracks may use single file in BaseURL
27875      segments = [{
27876        uri: attributes.baseUrl,
27877        timeline: attributes.periodIndex,
27878        resolvedUri: attributes.baseUrl || '',
27879        duration: attributes.sourceDuration,
27880        number: 0
27881      }];
27882      // targetDuration should be the same duration as the only segment
27883      attributes.duration = attributes.sourceDuration;
27884    }
27885    return {
27886      attributes: (_attributes2 = {
27887        NAME: attributes.id,
27888        BANDWIDTH: attributes.bandwidth
27889      }, _attributes2['PROGRAM-ID'] = 1, _attributes2),
27890      uri: '',
27891      endList: (attributes.type || 'static') === 'static',
27892      timeline: attributes.periodIndex,
27893      resolvedUri: attributes.baseUrl || '',
27894      targetDuration: attributes.duration,
27895      segments: segments,
27896      mediaSequence: segments.length ? segments[0].number : 1
27897    };
27898  };
27899
27900  var organizeAudioPlaylists = function organizeAudioPlaylists(playlists) {
27901    return playlists.reduce(function (a, playlist) {
27902      var role = playlist.attributes.role && playlist.attributes.role.value || 'main';
27903      var language = playlist.attributes.lang || '';
27904
27905      var label = 'main';
27906
27907      if (language) {
27908        label = playlist.attributes.lang + ' (' + role + ')';
27909      }
27910
27911      // skip if we already have the highest quality audio for a language
27912      if (a[label] && a[label].playlists[0].attributes.BANDWIDTH > playlist.attributes.bandwidth) {
27913        return a;
27914      }
27915
27916      a[label] = {
27917        language: language,
27918        autoselect: true,
27919        'default': role === 'main',
27920        playlists: [formatAudioPlaylist(playlist)],
27921        uri: ''
27922      };
27923
27924      return a;
27925    }, {});
27926  };
27927
27928  var organizeVttPlaylists = function organizeVttPlaylists(playlists) {
27929    return playlists.reduce(function (a, playlist) {
27930      var label = playlist.attributes.lang || 'text';
27931
27932      // skip if we already have subtitles
27933      if (a[label]) {
27934        return a;
27935      }
27936
27937      a[label] = {
27938        language: label,
27939        'default': false,
27940        autoselect: false,
27941        playlists: [formatVttPlaylist(playlist)],
27942        uri: ''
27943      };
27944
27945      return a;
27946    }, {});
27947  };
27948
27949  var formatVideoPlaylist = function formatVideoPlaylist(_ref3) {
27950    var _attributes3;
27951
27952    var attributes = _ref3.attributes,
27953        segments = _ref3.segments;
27954
27955    var playlist = {
27956      attributes: (_attributes3 = {
27957        NAME: attributes.id,
27958        AUDIO: 'audio',
27959        SUBTITLES: 'subs',
27960        RESOLUTION: {
27961          width: attributes.width,
27962          height: attributes.height
27963        },
27964        CODECS: attributes.codecs,
27965        BANDWIDTH: attributes.bandwidth
27966      }, _attributes3['PROGRAM-ID'] = 1, _attributes3),
27967      uri: '',
27968      endList: (attributes.type || 'static') === 'static',
27969      timeline: attributes.periodIndex,
27970      resolvedUri: '',
27971      targetDuration: attributes.duration,
27972      segments: segments,
27973      mediaSequence: segments.length ? segments[0].number : 1
27974    };
27975
27976    if (attributes.contentProtection) {
27977      playlist.contentProtection = attributes.contentProtection;
27978    }
27979
27980    return playlist;
27981  };
27982
27983  var toM3u8 = function toM3u8(dashPlaylists) {
27984    var _mediaGroups;
27985
27986    if (!dashPlaylists.length) {
27987      return {};
27988    }
27989
27990    // grab all master attributes
27991    var _dashPlaylists$0$attr = dashPlaylists[0].attributes,
27992        duration = _dashPlaylists$0$attr.sourceDuration,
27993        _dashPlaylists$0$attr2 = _dashPlaylists$0$attr.minimumUpdatePeriod,
27994        minimumUpdatePeriod = _dashPlaylists$0$attr2 === undefined ? 0 : _dashPlaylists$0$attr2;
27995
27996    var videoOnly = function videoOnly(_ref4) {
27997      var attributes = _ref4.attributes;
27998      return attributes.mimeType === 'video/mp4' || attributes.contentType === 'video';
27999    };
28000    var audioOnly = function audioOnly(_ref5) {
28001      var attributes = _ref5.attributes;
28002      return attributes.mimeType === 'audio/mp4' || attributes.contentType === 'audio';
28003    };
28004    var vttOnly = function vttOnly(_ref6) {
28005      var attributes = _ref6.attributes;
28006      return attributes.mimeType === 'text/vtt' || attributes.contentType === 'text';
28007    };
28008
28009    var videoPlaylists = dashPlaylists.filter(videoOnly).map(formatVideoPlaylist);
28010    var audioPlaylists = dashPlaylists.filter(audioOnly);
28011    var vttPlaylists = dashPlaylists.filter(vttOnly);
28012
28013    var master = {
28014      allowCache: true,
28015      discontinuityStarts: [],
28016      segments: [],
28017      endList: true,
28018      mediaGroups: (_mediaGroups = {
28019        AUDIO: {},
28020        VIDEO: {}
28021      }, _mediaGroups['CLOSED-CAPTIONS'] = {}, _mediaGroups.SUBTITLES = {}, _mediaGroups),
28022      uri: '',
28023      duration: duration,
28024      playlists: videoPlaylists,
28025      minimumUpdatePeriod: minimumUpdatePeriod * 1000
28026    };
28027
28028    if (audioPlaylists.length) {
28029      master.mediaGroups.AUDIO.audio = organizeAudioPlaylists(audioPlaylists);
28030    }
28031
28032    if (vttPlaylists.length) {
28033      master.mediaGroups.SUBTITLES.subs = organizeVttPlaylists(vttPlaylists);
28034    }
28035
28036    return master;
28037  };
28038
28039  var _typeof$1 = typeof Symbol === "function" && _typeof(Symbol.iterator) === "symbol" ? function (obj) {
28040    return typeof obj === 'undefined' ? 'undefined' : _typeof(obj);
28041  } : function (obj) {
28042    return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj === 'undefined' ? 'undefined' : _typeof(obj);
28043  };
28044
28045  var isObject$1 = function isObject(obj) {
28046    return !!obj && (typeof obj === 'undefined' ? 'undefined' : _typeof$1(obj)) === 'object';
28047  };
28048
28049  var merge = function merge() {
28050    for (var _len = arguments.length, objects = Array(_len), _key = 0; _key < _len; _key++) {
28051      objects[_key] = arguments[_key];
28052    }
28053
28054    return objects.reduce(function (result, source) {
28055
28056      Object.keys(source).forEach(function (key) {
28057
28058        if (Array.isArray(result[key]) && Array.isArray(source[key])) {
28059          result[key] = result[key].concat(source[key]);
28060        } else if (isObject$1(result[key]) && isObject$1(source[key])) {
28061          result[key] = merge(result[key], source[key]);
28062        } else {
28063          result[key] = source[key];
28064        }
28065      });
28066      return result;
28067    }, {});
28068  };
28069
28070  var resolveUrl = function resolveUrl(baseUrl, relativeUrl) {
28071    // return early if we don't need to resolve
28072    if (/^[a-z]+:/i.test(relativeUrl)) {
28073      return relativeUrl;
28074    }
28075
28076    // if the base URL is relative then combine with the current location
28077    if (!/\/\//i.test(baseUrl)) {
28078      baseUrl = urlToolkit.buildAbsoluteURL(window_1.location.href, baseUrl);
28079    }
28080
28081    return urlToolkit.buildAbsoluteURL(baseUrl, relativeUrl);
28082  };
28083
28084  /**
28085   * @typedef {Object} SingleUri
28086   * @property {string} uri - relative location of segment
28087   * @property {string} resolvedUri - resolved location of segment
28088   * @property {Object} byterange - Object containing information on how to make byte range
28089   *   requests following byte-range-spec per RFC2616.
28090   * @property {String} byterange.length - length of range request
28091   * @property {String} byterange.offset - byte offset of range request
28092   *
28093   * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1
28094   */
28095
28096  /**
28097   * Converts a URLType node (5.3.9.2.3 Table 13) to a segment object
28098   * that conforms to how m3u8-parser is structured
28099   *
28100   * @see https://github.com/videojs/m3u8-parser
28101   *
28102   * @param {string} baseUrl - baseUrl provided by <BaseUrl> nodes
28103   * @param {string} source - source url for segment
28104   * @param {string} range - optional range used for range calls, follows
28105   * @return {SingleUri} full segment information transformed into a format similar
28106   *   to m3u8-parser
28107   */
28108  var urlTypeToSegment = function urlTypeToSegment(_ref) {
28109    var _ref$baseUrl = _ref.baseUrl,
28110        baseUrl = _ref$baseUrl === undefined ? '' : _ref$baseUrl,
28111        _ref$source = _ref.source,
28112        source = _ref$source === undefined ? '' : _ref$source,
28113        _ref$range = _ref.range,
28114        range = _ref$range === undefined ? '' : _ref$range;
28115
28116    var init = {
28117      uri: source,
28118      resolvedUri: resolveUrl(baseUrl || '', source)
28119    };
28120
28121    if (range) {
28122      var ranges = range.split('-');
28123      var startRange = parseInt(ranges[0], 10);
28124      var endRange = parseInt(ranges[1], 10);
28125
28126      init.byterange = {
28127        length: endRange - startRange,
28128        offset: startRange
28129      };
28130    }
28131
28132    return init;
28133  };
28134
28135  /**
28136   * Calculates the R (repetition) value for a live stream (for the final segment
28137   * in a manifest where the r value is negative 1)
28138   *
28139   * @param {Object} attributes
28140   *        Object containing all inherited attributes from parent elements with attribute
28141   *        names as keys
28142   * @param {number} time
28143   *        current time (typically the total time up until the final segment)
28144   * @param {number} duration
28145   *        duration property for the given <S />
28146   *
28147   * @return {number}
28148   *        R value to reach the end of the given period
28149   */
28150  var getLiveRValue = function getLiveRValue(attributes, time, duration) {
28151    var NOW = attributes.NOW,
28152        clientOffset = attributes.clientOffset,
28153        availabilityStartTime = attributes.availabilityStartTime,
28154        _attributes$timescale = attributes.timescale,
28155        timescale = _attributes$timescale === undefined ? 1 : _attributes$timescale,
28156        _attributes$start = attributes.start,
28157        start = _attributes$start === undefined ? 0 : _attributes$start,
28158        _attributes$minimumUp = attributes.minimumUpdatePeriod,
28159        minimumUpdatePeriod = _attributes$minimumUp === undefined ? 0 : _attributes$minimumUp;
28160
28161    var now = (NOW + clientOffset) / 1000;
28162    var periodStartWC = availabilityStartTime + start;
28163    var periodEndWC = now + minimumUpdatePeriod;
28164    var periodDuration = periodEndWC - periodStartWC;
28165
28166    return Math.ceil((periodDuration * timescale - time) / duration);
28167  };
28168
28169  /**
28170   * Uses information provided by SegmentTemplate.SegmentTimeline to determine segment
28171   * timing and duration
28172   *
28173   * @param {Object} attributes
28174   *        Object containing all inherited attributes from parent elements with attribute
28175   *        names as keys
28176   * @param {Object[]} segmentTimeline
28177   *        List of objects representing the attributes of each S element contained within
28178   *
28179   * @return {{number: number, duration: number, time: number, timeline: number}[]}
28180   *         List of Objects with segment timing and duration info
28181   */
28182  var parseByTimeline = function parseByTimeline(attributes, segmentTimeline) {
28183    var _attributes$type = attributes.type,
28184        type = _attributes$type === undefined ? 'static' : _attributes$type,
28185        _attributes$minimumUp2 = attributes.minimumUpdatePeriod,
28186        minimumUpdatePeriod = _attributes$minimumUp2 === undefined ? 0 : _attributes$minimumUp2,
28187        _attributes$media = attributes.media,
28188        media = _attributes$media === undefined ? '' : _attributes$media,
28189        sourceDuration = attributes.sourceDuration,
28190        _attributes$timescale2 = attributes.timescale,
28191        timescale = _attributes$timescale2 === undefined ? 1 : _attributes$timescale2,
28192        _attributes$startNumb = attributes.startNumber,
28193        startNumber = _attributes$startNumb === undefined ? 1 : _attributes$startNumb,
28194        timeline = attributes.periodIndex;
28195
28196    var segments = [];
28197    var time = -1;
28198
28199    for (var sIndex = 0; sIndex < segmentTimeline.length; sIndex++) {
28200      var S = segmentTimeline[sIndex];
28201      var duration = S.d;
28202      var repeat = S.r || 0;
28203      var segmentTime = S.t || 0;
28204
28205      if (time < 0) {
28206        // first segment
28207        time = segmentTime;
28208      }
28209
28210      if (segmentTime && segmentTime > time) {
28211        // discontinuity
28212
28213        // TODO: How to handle this type of discontinuity
28214        // timeline++ here would treat it like HLS discontuity and content would
28215        // get appended without gap
28216        // E.G.
28217        //  <S t="0" d="1" />
28218        //  <S d="1" />
28219        //  <S d="1" />
28220        //  <S t="5" d="1" />
28221        // would have $Time$ values of [0, 1, 2, 5]
28222        // should this be appened at time positions [0, 1, 2, 3],(#EXT-X-DISCONTINUITY)
28223        // or [0, 1, 2, gap, gap, 5]? (#EXT-X-GAP)
28224        // does the value of sourceDuration consider this when calculating arbitrary
28225        // negative @r repeat value?
28226        // E.G. Same elements as above with this added at the end
28227        //  <S d="1" r="-1" />
28228        //  with a sourceDuration of 10
28229        // Would the 2 gaps be included in the time duration calculations resulting in
28230        // 8 segments with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9] or 10 segments
28231        // with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9, 10, 11] ?
28232
28233        time = segmentTime;
28234      }
28235
28236      var count = void 0;
28237
28238      if (repeat < 0) {
28239        var nextS = sIndex + 1;
28240
28241        if (nextS === segmentTimeline.length) {
28242          // last segment
28243          if (type === 'dynamic' && minimumUpdatePeriod > 0 && media.indexOf('$Number$') > 0) {
28244            count = getLiveRValue(attributes, time, duration);
28245          } else {
28246            // TODO: This may be incorrect depending on conclusion of TODO above
28247            count = (sourceDuration * timescale - time) / duration;
28248          }
28249        } else {
28250          count = (segmentTimeline[nextS].t - time) / duration;
28251        }
28252      } else {
28253        count = repeat + 1;
28254      }
28255
28256      var end = startNumber + segments.length + count;
28257      var number = startNumber + segments.length;
28258
28259      while (number < end) {
28260        segments.push({ number: number, duration: duration / timescale, time: time, timeline: timeline });
28261        time += duration;
28262        number++;
28263      }
28264    }
28265
28266    return segments;
28267  };
28268
28269  var range = function range(start, end) {
28270    var result = [];
28271
28272    for (var i = start; i < end; i++) {
28273      result.push(i);
28274    }
28275
28276    return result;
28277  };
28278
28279  var flatten = function flatten(lists) {
28280    return lists.reduce(function (x, y) {
28281      return x.concat(y);
28282    }, []);
28283  };
28284
28285  var from = function from(list) {
28286    if (!list.length) {
28287      return [];
28288    }
28289
28290    var result = [];
28291
28292    for (var i = 0; i < list.length; i++) {
28293      result.push(list[i]);
28294    }
28295
28296    return result;
28297  };
28298
28299  /**
28300   * Functions for calculating the range of available segments in static and dynamic
28301   * manifests.
28302   */
28303  var segmentRange = {
28304    /**
28305     * Returns the entire range of available segments for a static MPD
28306     *
28307     * @param {Object} attributes
28308     *        Inheritied MPD attributes
28309     * @return {{ start: number, end: number }}
28310     *         The start and end numbers for available segments
28311     */
28312    'static': function _static(attributes) {
28313      var duration = attributes.duration,
28314          _attributes$timescale = attributes.timescale,
28315          timescale = _attributes$timescale === undefined ? 1 : _attributes$timescale,
28316          sourceDuration = attributes.sourceDuration;
28317
28318      return {
28319        start: 0,
28320        end: Math.ceil(sourceDuration / (duration / timescale))
28321      };
28322    },
28323
28324    /**
28325     * Returns the current live window range of available segments for a dynamic MPD
28326     *
28327     * @param {Object} attributes
28328     *        Inheritied MPD attributes
28329     * @return {{ start: number, end: number }}
28330     *         The start and end numbers for available segments
28331     */
28332    dynamic: function dynamic(attributes) {
28333      var NOW = attributes.NOW,
28334          clientOffset = attributes.clientOffset,
28335          availabilityStartTime = attributes.availabilityStartTime,
28336          _attributes$timescale2 = attributes.timescale,
28337          timescale = _attributes$timescale2 === undefined ? 1 : _attributes$timescale2,
28338          duration = attributes.duration,
28339          _attributes$start = attributes.start,
28340          start = _attributes$start === undefined ? 0 : _attributes$start,
28341          _attributes$minimumUp = attributes.minimumUpdatePeriod,
28342          minimumUpdatePeriod = _attributes$minimumUp === undefined ? 0 : _attributes$minimumUp,
28343          _attributes$timeShift = attributes.timeShiftBufferDepth,
28344          timeShiftBufferDepth = _attributes$timeShift === undefined ? Infinity : _attributes$timeShift;
28345
28346      var now = (NOW + clientOffset) / 1000;
28347      var periodStartWC = availabilityStartTime + start;
28348      var periodEndWC = now + minimumUpdatePeriod;
28349      var periodDuration = periodEndWC - periodStartWC;
28350      var segmentCount = Math.ceil(periodDuration * timescale / duration);
28351      var availableStart = Math.floor((now - periodStartWC - timeShiftBufferDepth) * timescale / duration);
28352      var availableEnd = Math.floor((now - periodStartWC) * timescale / duration);
28353
28354      return {
28355        start: Math.max(0, availableStart),
28356        end: Math.min(segmentCount, availableEnd)
28357      };
28358    }
28359  };
28360
28361  /**
28362   * Maps a range of numbers to objects with information needed to build the corresponding
28363   * segment list
28364   *
28365   * @name toSegmentsCallback
28366   * @function
28367   * @param {number} number
28368   *        Number of the segment
28369   * @param {number} index
28370   *        Index of the number in the range list
28371   * @return {{ number: Number, duration: Number, timeline: Number, time: Number }}
28372   *         Object with segment timing and duration info
28373   */
28374
28375  /**
28376   * Returns a callback for Array.prototype.map for mapping a range of numbers to
28377   * information needed to build the segment list.
28378   *
28379   * @param {Object} attributes
28380   *        Inherited MPD attributes
28381   * @return {toSegmentsCallback}
28382   *         Callback map function
28383   */
28384  var toSegments = function toSegments(attributes) {
28385    return function (number, index) {
28386      var duration = attributes.duration,
28387          _attributes$timescale3 = attributes.timescale,
28388          timescale = _attributes$timescale3 === undefined ? 1 : _attributes$timescale3,
28389          periodIndex = attributes.periodIndex,
28390          _attributes$startNumb = attributes.startNumber,
28391          startNumber = _attributes$startNumb === undefined ? 1 : _attributes$startNumb;
28392
28393      return {
28394        number: startNumber + number,
28395        duration: duration / timescale,
28396        timeline: periodIndex,
28397        time: index * duration
28398      };
28399    };
28400  };
28401
28402  /**
28403   * Returns a list of objects containing segment timing and duration info used for
28404   * building the list of segments. This uses the @duration attribute specified
28405   * in the MPD manifest to derive the range of segments.
28406   *
28407   * @param {Object} attributes
28408   *        Inherited MPD attributes
28409   * @return {{number: number, duration: number, time: number, timeline: number}[]}
28410   *         List of Objects with segment timing and duration info
28411   */
28412  var parseByDuration = function parseByDuration(attributes) {
28413    var _attributes$type = attributes.type,
28414        type = _attributes$type === undefined ? 'static' : _attributes$type,
28415        duration = attributes.duration,
28416        _attributes$timescale4 = attributes.timescale,
28417        timescale = _attributes$timescale4 === undefined ? 1 : _attributes$timescale4,
28418        sourceDuration = attributes.sourceDuration;
28419
28420    var _segmentRange$type = segmentRange[type](attributes),
28421        start = _segmentRange$type.start,
28422        end = _segmentRange$type.end;
28423
28424    var segments = range(start, end).map(toSegments(attributes));
28425
28426    if (type === 'static') {
28427      var index = segments.length - 1;
28428
28429      // final segment may be less than full segment duration
28430      segments[index].duration = sourceDuration - duration / timescale * index;
28431    }
28432
28433    return segments;
28434  };
28435
28436  var identifierPattern = /\$([A-z]*)(?:(%0)([0-9]+)d)?\$/g;
28437
28438  /**
28439   * Replaces template identifiers with corresponding values. To be used as the callback
28440   * for String.prototype.replace
28441   *
28442   * @name replaceCallback
28443   * @function
28444   * @param {string} match
28445   *        Entire match of identifier
28446   * @param {string} identifier
28447   *        Name of matched identifier
28448   * @param {string} format
28449   *        Format tag string. Its presence indicates that padding is expected
28450   * @param {string} width
28451   *        Desired length of the replaced value. Values less than this width shall be left
28452   *        zero padded
28453   * @return {string}
28454   *         Replacement for the matched identifier
28455   */
28456
28457  /**
28458   * Returns a function to be used as a callback for String.prototype.replace to replace
28459   * template identifiers
28460   *
28461   * @param {Obect} values
28462   *        Object containing values that shall be used to replace known identifiers
28463   * @param {number} values.RepresentationID
28464   *        Value of the Representation@id attribute
28465   * @param {number} values.Number
28466   *        Number of the corresponding segment
28467   * @param {number} values.Bandwidth
28468   *        Value of the Representation@bandwidth attribute.
28469   * @param {number} values.Time
28470   *        Timestamp value of the corresponding segment
28471   * @return {replaceCallback}
28472   *         Callback to be used with String.prototype.replace to replace identifiers
28473   */
28474  var identifierReplacement = function identifierReplacement(values) {
28475    return function (match, identifier, format, width) {
28476      if (match === '$$') {
28477        // escape sequence
28478        return '$';
28479      }
28480
28481      if (typeof values[identifier] === 'undefined') {
28482        return match;
28483      }
28484
28485      var value = '' + values[identifier];
28486
28487      if (identifier === 'RepresentationID') {
28488        // Format tag shall not be present with RepresentationID
28489        return value;
28490      }
28491
28492      if (!format) {
28493        width = 1;
28494      } else {
28495        width = parseInt(width, 10);
28496      }
28497
28498      if (value.length >= width) {
28499        return value;
28500      }
28501
28502      return '' + new Array(width - value.length + 1).join('0') + value;
28503    };
28504  };
28505
28506  /**
28507   * Constructs a segment url from a template string
28508   *
28509   * @param {string} url
28510   *        Template string to construct url from
28511   * @param {Obect} values
28512   *        Object containing values that shall be used to replace known identifiers
28513   * @param {number} values.RepresentationID
28514   *        Value of the Representation@id attribute
28515   * @param {number} values.Number
28516   *        Number of the corresponding segment
28517   * @param {number} values.Bandwidth
28518   *        Value of the Representation@bandwidth attribute.
28519   * @param {number} values.Time
28520   *        Timestamp value of the corresponding segment
28521   * @return {string}
28522   *         Segment url with identifiers replaced
28523   */
28524  var constructTemplateUrl = function constructTemplateUrl(url, values) {
28525    return url.replace(identifierPattern, identifierReplacement(values));
28526  };
28527
28528  /**
28529   * Generates a list of objects containing timing and duration information about each
28530   * segment needed to generate segment uris and the complete segment object
28531   *
28532   * @param {Object} attributes
28533   *        Object containing all inherited attributes from parent elements with attribute
28534   *        names as keys
28535   * @param {Object[]|undefined} segmentTimeline
28536   *        List of objects representing the attributes of each S element contained within
28537   *        the SegmentTimeline element
28538   * @return {{number: number, duration: number, time: number, timeline: number}[]}
28539   *         List of Objects with segment timing and duration info
28540   */
28541  var parseTemplateInfo = function parseTemplateInfo(attributes, segmentTimeline) {
28542    if (!attributes.duration && !segmentTimeline) {
28543      // if neither @duration or SegmentTimeline are present, then there shall be exactly
28544      // one media segment
28545      return [{
28546        number: attributes.startNumber || 1,
28547        duration: attributes.sourceDuration,
28548        time: 0,
28549        timeline: attributes.periodIndex
28550      }];
28551    }
28552
28553    if (attributes.duration) {
28554      return parseByDuration(attributes);
28555    }
28556
28557    return parseByTimeline(attributes, segmentTimeline);
28558  };
28559
28560  /**
28561   * Generates a list of segments using information provided by the SegmentTemplate element
28562   *
28563   * @param {Object} attributes
28564   *        Object containing all inherited attributes from parent elements with attribute
28565   *        names as keys
28566   * @param {Object[]|undefined} segmentTimeline
28567   *        List of objects representing the attributes of each S element contained within
28568   *        the SegmentTimeline element
28569   * @return {Object[]}
28570   *         List of segment objects
28571   */
28572  var segmentsFromTemplate = function segmentsFromTemplate(attributes, segmentTimeline) {
28573    var templateValues = {
28574      RepresentationID: attributes.id,
28575      Bandwidth: attributes.bandwidth || 0
28576    };
28577
28578    var _attributes$initializ = attributes.initialization,
28579        initialization = _attributes$initializ === undefined ? { sourceURL: '', range: '' } : _attributes$initializ;
28580
28581    var mapSegment = urlTypeToSegment({
28582      baseUrl: attributes.baseUrl,
28583      source: constructTemplateUrl(initialization.sourceURL, templateValues),
28584      range: initialization.range
28585    });
28586
28587    var segments = parseTemplateInfo(attributes, segmentTimeline);
28588
28589    return segments.map(function (segment) {
28590      templateValues.Number = segment.number;
28591      templateValues.Time = segment.time;
28592
28593      var uri = constructTemplateUrl(attributes.media || '', templateValues);
28594
28595      return {
28596        uri: uri,
28597        timeline: segment.timeline,
28598        duration: segment.duration,
28599        resolvedUri: resolveUrl(attributes.baseUrl || '', uri),
28600        map: mapSegment,
28601        number: segment.number
28602      };
28603    });
28604  };
28605
28606  var errors = {
28607    INVALID_NUMBER_OF_PERIOD: 'INVALID_NUMBER_OF_PERIOD',
28608    DASH_EMPTY_MANIFEST: 'DASH_EMPTY_MANIFEST',
28609    DASH_INVALID_XML: 'DASH_INVALID_XML',
28610    NO_BASE_URL: 'NO_BASE_URL',
28611    MISSING_SEGMENT_INFORMATION: 'MISSING_SEGMENT_INFORMATION',
28612    SEGMENT_TIME_UNSPECIFIED: 'SEGMENT_TIME_UNSPECIFIED',
28613    UNSUPPORTED_UTC_TIMING_SCHEME: 'UNSUPPORTED_UTC_TIMING_SCHEME'
28614  };
28615
28616  /**
28617   * Converts a <SegmentUrl> (of type URLType from the DASH spec 5.3.9.2 Table 14)
28618   * to an object that matches the output of a segment in videojs/mpd-parser
28619   *
28620   * @param {Object} attributes
28621   *   Object containing all inherited attributes from parent elements with attribute
28622   *   names as keys
28623   * @param {Object} segmentUrl
28624   *   <SegmentURL> node to translate into a segment object
28625   * @return {Object} translated segment object
28626   */
28627  var SegmentURLToSegmentObject = function SegmentURLToSegmentObject(attributes, segmentUrl) {
28628    var baseUrl = attributes.baseUrl,
28629        _attributes$initializ = attributes.initialization,
28630        initialization = _attributes$initializ === undefined ? {} : _attributes$initializ;
28631
28632    var initSegment = urlTypeToSegment({
28633      baseUrl: baseUrl,
28634      source: initialization.sourceURL,
28635      range: initialization.range
28636    });
28637
28638    var segment = urlTypeToSegment({
28639      baseUrl: baseUrl,
28640      source: segmentUrl.media,
28641      range: segmentUrl.mediaRange
28642    });
28643
28644    segment.map = initSegment;
28645
28646    return segment;
28647  };
28648
28649  /**
28650   * Generates a list of segments using information provided by the SegmentList element
28651   * SegmentList (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes.  Each
28652   * node should be translated into segment.
28653   *
28654   * @param {Object} attributes
28655   *   Object containing all inherited attributes from parent elements with attribute
28656   *   names as keys
28657   * @param {Object[]|undefined} segmentTimeline
28658   *        List of objects representing the attributes of each S element contained within
28659   *        the SegmentTimeline element
28660   * @return {Object.<Array>} list of segments
28661   */
vendor: 18,658 bytes, lines 28662-29215
28662  var segmentsFromList = function segmentsFromList(attributes, segmentTimeline) {
28663    var duration = attributes.duration,
28664        _attributes$segmentUr = attributes.segmentUrls,
28665        segmentUrls = _attributes$segmentUr === undefined ? [] : _attributes$segmentUr;
28666
28667    // Per spec (5.3.9.2.1) no way to determine segment duration OR
28668    // if both SegmentTimeline and @duration are defined, it is outside of spec.
28669
28670    if (!duration && !segmentTimeline || duration && segmentTimeline) {
28671      throw new Error(errors.SEGMENT_TIME_UNSPECIFIED);
28672    }
28673
28674    var segmentUrlMap = segmentUrls.map(function (segmentUrlObject) {
28675      return SegmentURLToSegmentObject(attributes, segmentUrlObject);
28676    });
28677    var segmentTimeInfo = void 0;
28678
28679    if (duration) {
28680      segmentTimeInfo = parseByDuration(attributes);
28681    }
28682
28683    if (segmentTimeline) {
28684      segmentTimeInfo = parseByTimeline(attributes, segmentTimeline);
28685    }
28686
28687    var segments = segmentTimeInfo.map(function (segmentTime, index) {
28688      if (segmentUrlMap[index]) {
28689        var segment = segmentUrlMap[index];
28690
28691        segment.timeline = segmentTime.timeline;
28692        segment.duration = segmentTime.duration;
28693        segment.number = segmentTime.number;
28694        return segment;
28695      }
28696      // Since we're mapping we should get rid of any blank segments (in case
28697      // the given SegmentTimeline is handling for more elements than we have
28698      // SegmentURLs for).
28699    }).filter(function (segment) {
28700      return segment;
28701    });
28702
28703    return segments;
28704  };
28705
28706  /**
28707   * Translates SegmentBase into a set of segments.
28708   * (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes.  Each
28709   * node should be translated into segment.
28710   *
28711   * @param {Object} attributes
28712   *   Object containing all inherited attributes from parent elements with attribute
28713   *   names as keys
28714   * @return {Object.<Array>} list of segments
28715   */
28716  var segmentsFromBase = function segmentsFromBase(attributes) {
28717    var baseUrl = attributes.baseUrl,
28718        _attributes$initializ = attributes.initialization,
28719        initialization = _attributes$initializ === undefined ? {} : _attributes$initializ,
28720        sourceDuration = attributes.sourceDuration,
28721        _attributes$timescale = attributes.timescale,
28722        timescale = _attributes$timescale === undefined ? 1 : _attributes$timescale,
28723        _attributes$indexRang = attributes.indexRange,
28724        indexRange = _attributes$indexRang === undefined ? '' : _attributes$indexRang,
28725        duration = attributes.duration;
28726
28727    // base url is required for SegmentBase to work, per spec (Section 5.3.9.2.1)
28728
28729    if (!baseUrl) {
28730      throw new Error(errors.NO_BASE_URL);
28731    }
28732
28733    var initSegment = urlTypeToSegment({
28734      baseUrl: baseUrl,
28735      source: initialization.sourceURL,
28736      range: initialization.range
28737    });
28738    var segment = urlTypeToSegment({ baseUrl: baseUrl, source: baseUrl, range: indexRange });
28739
28740    segment.map = initSegment;
28741
28742    // If there is a duration, use it, otherwise use the given duration of the source
28743    // (since SegmentBase is only for one total segment)
28744    if (duration) {
28745      var segmentTimeInfo = parseByDuration(attributes);
28746
28747      if (segmentTimeInfo.length) {
28748        segment.duration = segmentTimeInfo[0].duration;
28749        segment.timeline = segmentTimeInfo[0].timeline;
28750      }
28751    } else if (sourceDuration) {
28752      segment.duration = sourceDuration / timescale;
28753      segment.timeline = 0;
28754    }
28755
28756    // This is used for mediaSequence
28757    segment.number = 0;
28758
28759    return [segment];
28760  };
28761
28762  var generateSegments = function generateSegments(_ref) {
28763    var attributes = _ref.attributes,
28764        segmentInfo = _ref.segmentInfo;
28765
28766    var segmentAttributes = void 0;
28767    var segmentsFn = void 0;
28768
28769    if (segmentInfo.template) {
28770      segmentsFn = segmentsFromTemplate;
28771      segmentAttributes = merge(attributes, segmentInfo.template);
28772    } else if (segmentInfo.base) {
28773      segmentsFn = segmentsFromBase;
28774      segmentAttributes = merge(attributes, segmentInfo.base);
28775    } else if (segmentInfo.list) {
28776      segmentsFn = segmentsFromList;
28777      segmentAttributes = merge(attributes, segmentInfo.list);
28778    }
28779
28780    if (!segmentsFn) {
28781      return { attributes: attributes };
28782    }
28783
28784    var segments = segmentsFn(segmentAttributes, segmentInfo.timeline);
28785
28786    // The @duration attribute will be used to determin the playlist's targetDuration which
28787    // must be in seconds. Since we've generated the segment list, we no longer need
28788    // @duration to be in @timescale units, so we can convert it here.
28789    if (segmentAttributes.duration) {
28790      var _segmentAttributes = segmentAttributes,
28791          duration = _segmentAttributes.duration,
28792          _segmentAttributes$ti = _segmentAttributes.timescale,
28793          timescale = _segmentAttributes$ti === undefined ? 1 : _segmentAttributes$ti;
28794
28795      segmentAttributes.duration = duration / timescale;
28796    } else if (segments.length) {
28797      // if there is no @duration attribute, use the largest segment duration as
28798      // as target duration
28799      segmentAttributes.duration = segments.reduce(function (max, segment) {
28800        return Math.max(max, Math.ceil(segment.duration));
28801      }, 0);
28802    } else {
28803      segmentAttributes.duration = 0;
28804    }
28805
28806    return {
28807      attributes: segmentAttributes,
28808      segments: segments
28809    };
28810  };
28811
28812  var toPlaylists = function toPlaylists(representations) {
28813    return representations.map(generateSegments);
28814  };
28815
28816  var findChildren = function findChildren(element, name) {
28817    return from(element.childNodes).filter(function (_ref) {
28818      var tagName = _ref.tagName;
28819      return tagName === name;
28820    });
28821  };
28822
28823  var getContent = function getContent(element) {
28824    return element.textContent.trim();
28825  };
28826
28827  var parseDuration = function parseDuration(str) {
28828    var SECONDS_IN_YEAR = 365 * 24 * 60 * 60;
28829    var SECONDS_IN_MONTH = 30 * 24 * 60 * 60;
28830    var SECONDS_IN_DAY = 24 * 60 * 60;
28831    var SECONDS_IN_HOUR = 60 * 60;
28832    var SECONDS_IN_MIN = 60;
28833
28834    // P10Y10M10DT10H10M10.1S
28835    var durationRegex = /P(?:(\d*)Y)?(?:(\d*)M)?(?:(\d*)D)?(?:T(?:(\d*)H)?(?:(\d*)M)?(?:([\d.]*)S)?)?/;
28836    var match = durationRegex.exec(str);
28837
28838    if (!match) {
28839      return 0;
28840    }
28841
28842    var _match$slice = match.slice(1),
28843        year = _match$slice[0],
28844        month = _match$slice[1],
28845        day = _match$slice[2],
28846        hour = _match$slice[3],
28847        minute = _match$slice[4],
28848        second = _match$slice[5];
28849
28850    return parseFloat(year || 0) * SECONDS_IN_YEAR + parseFloat(month || 0) * SECONDS_IN_MONTH + parseFloat(day || 0) * SECONDS_IN_DAY + parseFloat(hour || 0) * SECONDS_IN_HOUR + parseFloat(minute || 0) * SECONDS_IN_MIN + parseFloat(second || 0);
28851  };
28852
28853  var parseDate = function parseDate(str) {
28854    // Date format without timezone according to ISO 8601
28855    // YYY-MM-DDThh:mm:ss.ssssss
28856    var dateRegex = /^\d+-\d+-\d+T\d+:\d+:\d+(\.\d+)?$/;
28857
28858    // If the date string does not specifiy a timezone, we must specifiy UTC. This is
28859    // expressed by ending with 'Z'
28860    if (dateRegex.test(str)) {
28861      str += 'Z';
28862    }
28863
28864    return Date.parse(str);
28865  };
28866
28867  // TODO: maybe order these in some way that makes it easy to find specific attributes
28868  var parsers = {
28869    /**
28870     * Specifies the duration of the entire Media Presentation. Format is a duration string
28871     * as specified in ISO 8601
28872     *
28873     * @param {string} value
28874     *        value of attribute as a string
28875     * @return {number}
28876     *         The duration in seconds
28877     */
28878    mediaPresentationDuration: function mediaPresentationDuration(value) {
28879      return parseDuration(value);
28880    },
28881
28882    /**
28883     * Specifies the Segment availability start time for all Segments referred to in this
28884     * MPD. For a dynamic manifest, it specifies the anchor for the earliest availability
28885     * time. Format is a date string as specified in ISO 8601
28886     *
28887     * @param {string} value
28888     *        value of attribute as a string
28889     * @return {number}
28890     *         The date as seconds from unix epoch
28891     */
28892    availabilityStartTime: function availabilityStartTime(value) {
28893      return parseDate(value) / 1000;
28894    },
28895
28896    /**
28897     * Specifies the smallest period between potential changes to the MPD. Format is a
28898     * duration string as specified in ISO 8601
28899     *
28900     * @param {string} value
28901     *        value of attribute as a string
28902     * @return {number}
28903     *         The duration in seconds
28904     */
28905    minimumUpdatePeriod: function minimumUpdatePeriod(value) {
28906      return parseDuration(value);
28907    },
28908
28909    /**
28910     * Specifies the duration of the smallest time shifting buffer for any Representation
28911     * in the MPD. Format is a duration string as specified in ISO 8601
28912     *
28913     * @param {string} value
28914     *        value of attribute as a string
28915     * @return {number}
28916     *         The duration in seconds
28917     */
28918    timeShiftBufferDepth: function timeShiftBufferDepth(value) {
28919      return parseDuration(value);
28920    },
28921
28922    /**
28923     * Specifies the PeriodStart time of the Period relative to the availabilityStarttime.
28924     * Format is a duration string as specified in ISO 8601
28925     *
28926     * @param {string} value
28927     *        value of attribute as a string
28928     * @return {number}
28929     *         The duration in seconds
28930     */
28931    start: function start(value) {
28932      return parseDuration(value);
28933    },
28934
28935    /**
28936     * Specifies the width of the visual presentation
28937     *
28938     * @param {string} value
28939     *        value of attribute as a string
28940     * @return {number}
28941     *         The parsed width
28942     */
28943    width: function width(value) {
28944      return parseInt(value, 10);
28945    },
28946
28947    /**
28948     * Specifies the height of the visual presentation
28949     *
28950     * @param {string} value
28951     *        value of attribute as a string
28952     * @return {number}
28953     *         The parsed height
28954     */
28955    height: function height(value) {
28956      return parseInt(value, 10);
28957    },
28958
28959    /**
28960     * Specifies the bitrate of the representation
28961     *
28962     * @param {string} value
28963     *        value of attribute as a string
28964     * @return {number}
28965     *         The parsed bandwidth
28966     */
28967    bandwidth: function bandwidth(value) {
28968      return parseInt(value, 10);
28969    },
28970
28971    /**
28972     * Specifies the number of the first Media Segment in this Representation in the Period
28973     *
28974     * @param {string} value
28975     *        value of attribute as a string
28976     * @return {number}
28977     *         The parsed number
28978     */
28979    startNumber: function startNumber(value) {
28980      return parseInt(value, 10);
28981    },
28982
28983    /**
28984     * Specifies the timescale in units per seconds
28985     *
28986     * @param {string} value
28987     *        value of attribute as a string
28988     * @return {number}
28989     *         The aprsed timescale
28990     */
28991    timescale: function timescale(value) {
28992      return parseInt(value, 10);
28993    },
28994
28995    /**
28996     * Specifies the constant approximate Segment duration
28997     * NOTE: The <Period> element also contains an @duration attribute. This duration
28998     *       specifies the duration of the Period. This attribute is currently not
28999     *       supported by the rest of the parser, however we still check for it to prevent
29000     *       errors.
29001     *
29002     * @param {string} value
29003     *        value of attribute as a string
29004     * @return {number}
29005     *         The parsed duration
29006     */
29007    duration: function duration(value) {
29008      var parsedValue = parseInt(value, 10);
29009
29010      if (isNaN(parsedValue)) {
29011        return parseDuration(value);
29012      }
29013
29014      return parsedValue;
29015    },
29016
29017    /**
29018     * Specifies the Segment duration, in units of the value of the @timescale.
29019     *
29020     * @param {string} value
29021     *        value of attribute as a string
29022     * @return {number}
29023     *         The parsed duration
29024     */
29025    d: function d(value) {
29026      return parseInt(value, 10);
29027    },
29028
29029    /**
29030     * Specifies the MPD start time, in @timescale units, the first Segment in the series
29031     * starts relative to the beginning of the Period
29032     *
29033     * @param {string} value
29034     *        value of attribute as a string
29035     * @return {number}
29036     *         The parsed time
29037     */
29038    t: function t(value) {
29039      return parseInt(value, 10);
29040    },
29041
29042    /**
29043     * Specifies the repeat count of the number of following contiguous Segments with the
29044     * same duration expressed by the value of @d
29045     *
29046     * @param {string} value
29047     *        value of attribute as a string
29048     * @return {number}
29049     *         The parsed number
29050     */
29051    r: function r(value) {
29052      return parseInt(value, 10);
29053    },
29054
29055    /**
29056     * Default parser for all other attributes. Acts as a no-op and just returns the value
29057     * as a string
29058     *
29059     * @param {string} value
29060     *        value of attribute as a string
29061     * @return {string}
29062     *         Unparsed value
29063     */
29064    DEFAULT: function DEFAULT(value) {
29065      return value;
29066    }
29067  };
29068
29069  /**
29070   * Gets all the attributes and values of the provided node, parses attributes with known
29071   * types, and returns an object with attribute names mapped to values.
29072   *
29073   * @param {Node} el
29074   *        The node to parse attributes from
29075   * @return {Object}
29076   *         Object with all attributes of el parsed
29077   */
29078  var parseAttributes$1 = function parseAttributes(el) {
29079    if (!(el && el.attributes)) {
29080      return {};
29081    }
29082
29083    return from(el.attributes).reduce(function (a, e) {
29084      var parseFn = parsers[e.name] || parsers.DEFAULT;
29085
29086      a[e.name] = parseFn(e.value);
29087
29088      return a;
29089    }, {});
29090  };
29091
29092  function decodeB64ToUint8Array(b64Text) {
29093    var decodedString = window_1.atob(b64Text);
29094    var array = new Uint8Array(decodedString.length);
29095
29096    for (var i = 0; i < decodedString.length; i++) {
29097      array[i] = decodedString.charCodeAt(i);
29098    }
29099    return array;
29100  }
29101
29102  var keySystemsMap = {
29103    'urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b': 'org.w3.clearkey',
29104    'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed': 'com.widevine.alpha',
29105    'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95': 'com.microsoft.playready',
29106    'urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb': 'com.adobe.primetime'
29107  };
29108
29109  /**
29110   * Builds a list of urls that is the product of the reference urls and BaseURL values
29111   *
29112   * @param {string[]} referenceUrls
29113   *        List of reference urls to resolve to
29114   * @param {Node[]} baseUrlElements
29115   *        List of BaseURL nodes from the mpd
29116   * @return {string[]}
29117   *         List of resolved urls
29118   */
29119  var buildBaseUrls = function buildBaseUrls(referenceUrls, baseUrlElements) {
29120    if (!baseUrlElements.length) {
29121      return referenceUrls;
29122    }
29123
29124    return flatten(referenceUrls.map(function (reference) {
29125      return baseUrlElements.map(function (baseUrlElement) {
29126        return resolveUrl(reference, getContent(baseUrlElement));
29127      });
29128    }));
29129  };
29130
29131  /**
29132   * Contains all Segment information for its containing AdaptationSet
29133   *
29134   * @typedef {Object} SegmentInformation
29135   * @property {Object|undefined} template
29136   *           Contains the attributes for the SegmentTemplate node
29137   * @property {Object[]|undefined} timeline
29138   *           Contains a list of atrributes for each S node within the SegmentTimeline node
29139   * @property {Object|undefined} list
29140   *           Contains the attributes for the SegmentList node
29141   * @property {Object|undefined} base
29142   *           Contains the attributes for the SegmentBase node
29143   */
29144
29145  /**
29146   * Returns all available Segment information contained within the AdaptationSet node
29147   *
29148   * @param {Node} adaptationSet
29149   *        The AdaptationSet node to get Segment information from
29150   * @return {SegmentInformation}
29151   *         The Segment information contained within the provided AdaptationSet
29152   */
29153  var getSegmentInformation = function getSegmentInformation(adaptationSet) {
29154    var segmentTemplate = findChildren(adaptationSet, 'SegmentTemplate')[0];
29155    var segmentList = findChildren(adaptationSet, 'SegmentList')[0];
29156    var segmentUrls = segmentList && findChildren(segmentList, 'SegmentURL').map(function (s) {
29157      return merge({ tag: 'SegmentURL' }, parseAttributes$1(s));
29158    });
29159    var segmentBase = findChildren(adaptationSet, 'SegmentBase')[0];
29160    var segmentTimelineParentNode = segmentList || segmentTemplate;
29161    var segmentTimeline = segmentTimelineParentNode && findChildren(segmentTimelineParentNode, 'SegmentTimeline')[0];
29162    var segmentInitializationParentNode = segmentList || segmentBase || segmentTemplate;
29163    var segmentInitialization = segmentInitializationParentNode && findChildren(segmentInitializationParentNode, 'Initialization')[0];
29164
29165    // SegmentTemplate is handled slightly differently, since it can have both
29166    // @initialization and an <Initialization> node.  @initialization can be templated,
29167    // while the node can have a url and range specified.  If the <SegmentTemplate> has
29168    // both @initialization and an <Initialization> subelement we opt to override with
29169    // the node, as this interaction is not defined in the spec.
29170    var template = segmentTemplate && parseAttributes$1(segmentTemplate);
29171
29172    if (template && segmentInitialization) {
29173      template.initialization = segmentInitialization && parseAttributes$1(segmentInitialization);
29174    } else if (template && template.initialization) {
29175      // If it is @initialization we convert it to an object since this is the format that
29176      // later functions will rely on for the initialization segment.  This is only valid
29177      // for <SegmentTemplate>
29178      template.initialization = { sourceURL: template.initialization };
29179    }
29180
29181    var segmentInfo = {
29182      template: template,
29183      timeline: segmentTimeline && findChildren(segmentTimeline, 'S').map(function (s) {
29184        return parseAttributes$1(s);
29185      }),
29186      list: segmentList && merge(parseAttributes$1(segmentList), {
29187        segmentUrls: segmentUrls,
29188        initialization: parseAttributes$1(segmentInitialization)
29189      }),
29190      base: segmentBase && merge(parseAttributes$1(segmentBase), {
29191        initialization: parseAttributes$1(segmentInitialization)
29192      })
29193    };
29194
29195    Object.keys(segmentInfo).forEach(function (key) {
29196      if (!segmentInfo[key]) {
29197        delete segmentInfo[key];
29198      }
29199    });
29200
29201    return segmentInfo;
29202  };
29203
29204  /**
29205   * Contains Segment information and attributes needed to construct a Playlist object
29206   * from a Representation
29207   *
29208   * @typedef {Object} RepresentationInformation
29209   * @property {SegmentInformation} segmentInfo
29210   *           Segment information for this Representation
29211   * @property {Object} attributes
29212   *           Inherited attributes for this Representation
29213   */
29214
29215  /**
29216   * Maps a Representation node to an object containing Segment information and attributes
29217   *
29218   * @name inheritBaseUrlsCallback
29219   * @function
29220   * @param {Node} representation
29221   *        Representation node from the mpd
29222   * @return {RepresentationInformation}
29223   *         Representation information needed to construct a Playlist object
29224   */
29225
29226  /**
29227   * Returns a callback for Array.prototype.map for mapping Representation nodes to
29228   * Segment information and attributes using inherited BaseURL nodes.
29229   *
29230   * @param {Object} adaptationSetAttributes
29231   *        Contains attributes inherited by the AdaptationSet
29232   * @param {string[]} adaptationSetBaseUrls
29233   *        Contains list of resolved base urls inherited by the AdaptationSet
29234   * @param {SegmentInformation} adaptationSetSegmentInfo
29235   *        Contains Segment information for the AdaptationSet
29236   * @return {inheritBaseUrlsCallback}
29237   *         Callback map function
29238   */
29239  var inheritBaseUrls = function inheritBaseUrls(adaptationSetAttributes, adaptationSetBaseUrls, adaptationSetSegmentInfo) {
29240    return function (representation) {
29241      var repBaseUrlElements = findChildren(representation, 'BaseURL');
29242      var repBaseUrls = buildBaseUrls(adaptationSetBaseUrls, repBaseUrlElements);
29243      var attributes = merge(adaptationSetAttributes, parseAttributes$1(representation));
29244      var representationSegmentInfo = getSegmentInformation(representation);
29245
29246      return repBaseUrls.map(function (baseUrl) {
29247        return {
29248          segmentInfo: merge(adaptationSetSegmentInfo, representationSegmentInfo),
29249          attributes: merge(attributes, { baseUrl: baseUrl })
29250        };
29251      });
29252    };
29253  };
29254
29255  /**
29256   * Tranforms a series of content protection nodes to
vendor: 15,888 bytes, lines 29257-29721
29257   * an object containing pssh data by key system
29258   *
29259   * @param {Node[]} contentProtectionNodes
29260   *        Content protection nodes
29261   * @return {Object}
29262   *        Object containing pssh data by key system
29263   */
29264  var generateKeySystemInformation = function generateKeySystemInformation(contentProtectionNodes) {
29265    return contentProtectionNodes.reduce(function (acc, node) {
29266      var attributes = parseAttributes$1(node);
29267      var keySystem = keySystemsMap[attributes.schemeIdUri];
29268
29269      if (keySystem) {
29270        acc[keySystem] = { attributes: attributes };
29271
29272        var psshNode = findChildren(node, 'cenc:pssh')[0];
29273
29274        if (psshNode) {
29275          var pssh = getContent(psshNode);
29276          var psshBuffer = pssh && decodeB64ToUint8Array(pssh);
29277
29278          acc[keySystem].pssh = psshBuffer;
29279        }
29280      }
29281
29282      return acc;
29283    }, {});
29284  };
29285
29286  /**
29287   * Maps an AdaptationSet node to a list of Representation information objects
29288   *
29289   * @name toRepresentationsCallback
29290   * @function
29291   * @param {Node} adaptationSet
29292   *        AdaptationSet node from the mpd
29293   * @return {RepresentationInformation[]}
29294   *         List of objects containing Representaion information
29295   */
29296
29297  /**
29298   * Returns a callback for Array.prototype.map for mapping AdaptationSet nodes to a list of
29299   * Representation information objects
29300   *
29301   * @param {Object} periodAttributes
29302   *        Contains attributes inherited by the Period
29303   * @param {string[]} periodBaseUrls
29304   *        Contains list of resolved base urls inherited by the Period
29305   * @param {string[]} periodSegmentInfo
29306   *        Contains Segment Information at the period level
29307   * @return {toRepresentationsCallback}
29308   *         Callback map function
29309   */
29310  var toRepresentations = function toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo) {
29311    return function (adaptationSet) {
29312      var adaptationSetAttributes = parseAttributes$1(adaptationSet);
29313      var adaptationSetBaseUrls = buildBaseUrls(periodBaseUrls, findChildren(adaptationSet, 'BaseURL'));
29314      var role = findChildren(adaptationSet, 'Role')[0];
29315      var roleAttributes = { role: parseAttributes$1(role) };
29316
29317      var attrs = merge(periodAttributes, adaptationSetAttributes, roleAttributes);
29318
29319      var contentProtection = generateKeySystemInformation(findChildren(adaptationSet, 'ContentProtection'));
29320
29321      if (Object.keys(contentProtection).length) {
29322        attrs = merge(attrs, { contentProtection: contentProtection });
29323      }
29324
29325      var segmentInfo = getSegmentInformation(adaptationSet);
29326      var representations = findChildren(adaptationSet, 'Representation');
29327      var adaptationSetSegmentInfo = merge(periodSegmentInfo, segmentInfo);
29328
29329      return flatten(representations.map(inheritBaseUrls(attrs, adaptationSetBaseUrls, adaptationSetSegmentInfo)));
29330    };
29331  };
29332
29333  /**
29334   * Maps an Period node to a list of Representation inforamtion objects for all
29335   * AdaptationSet nodes contained within the Period
29336   *
29337   * @name toAdaptationSetsCallback
29338   * @function
29339   * @param {Node} period
29340   *        Period node from the mpd
29341   * @param {number} periodIndex
29342   *        Index of the Period within the mpd
29343   * @return {RepresentationInformation[]}
29344   *         List of objects containing Representaion information
29345   */
29346
29347  /**
29348   * Returns a callback for Array.prototype.map for mapping Period nodes to a list of
29349   * Representation information objects
29350   *
29351   * @param {Object} mpdAttributes
29352   *        Contains attributes inherited by the mpd
29353   * @param {string[]} mpdBaseUrls
29354   *        Contains list of resolved base urls inherited by the mpd
29355   * @return {toAdaptationSetsCallback}
29356   *         Callback map function
29357   */
29358  var toAdaptationSets = function toAdaptationSets(mpdAttributes, mpdBaseUrls) {
29359    return function (period, periodIndex) {
29360      var periodBaseUrls = buildBaseUrls(mpdBaseUrls, findChildren(period, 'BaseURL'));
29361      var periodAtt = parseAttributes$1(period);
29362      var periodAttributes = merge(mpdAttributes, periodAtt, { periodIndex: periodIndex });
29363      var adaptationSets = findChildren(period, 'AdaptationSet');
29364      var periodSegmentInfo = getSegmentInformation(period);
29365
29366      return flatten(adaptationSets.map(toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo)));
29367    };
29368  };
29369
29370  /**
29371   * Traverses the mpd xml tree to generate a list of Representation information objects
29372   * that have inherited attributes from parent nodes
29373   *
29374   * @param {Node} mpd
29375   *        The root node of the mpd
29376   * @param {Object} options
29377   *        Available options for inheritAttributes
29378   * @param {string} options.manifestUri
29379   *        The uri source of the mpd
29380   * @param {number} options.NOW
29381   *        Current time per DASH IOP.  Default is current time in ms since epoch
29382   * @param {number} options.clientOffset
29383   *        Client time difference from NOW (in milliseconds)
29384   * @return {RepresentationInformation[]}
29385   *         List of objects containing Representation information
29386   */
29387  var inheritAttributes = function inheritAttributes(mpd) {
29388    var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
29389    var _options$manifestUri = options.manifestUri,
29390        manifestUri = _options$manifestUri === undefined ? '' : _options$manifestUri,
29391        _options$NOW = options.NOW,
29392        NOW = _options$NOW === undefined ? Date.now() : _options$NOW,
29393        _options$clientOffset = options.clientOffset,
29394        clientOffset = _options$clientOffset === undefined ? 0 : _options$clientOffset;
29395
29396    var periods = findChildren(mpd, 'Period');
29397
29398    if (periods.length !== 1) {
29399      // TODO add support for multiperiod
29400      throw new Error(errors.INVALID_NUMBER_OF_PERIOD);
29401    }
29402
29403    var mpdAttributes = parseAttributes$1(mpd);
29404    var mpdBaseUrls = buildBaseUrls([manifestUri], findChildren(mpd, 'BaseURL'));
29405
29406    mpdAttributes.sourceDuration = mpdAttributes.mediaPresentationDuration || 0;
29407    mpdAttributes.NOW = NOW;
29408    mpdAttributes.clientOffset = clientOffset;
29409
29410    return flatten(periods.map(toAdaptationSets(mpdAttributes, mpdBaseUrls)));
29411  };
29412
29413  var stringToMpdXml = function stringToMpdXml(manifestString) {
29414    if (manifestString === '') {
29415      throw new Error(errors.DASH_EMPTY_MANIFEST);
29416    }
29417
29418    var parser = new window_1.DOMParser();
29419    var xml = parser.parseFromString(manifestString, 'application/xml');
29420    var mpd = xml && xml.documentElement.tagName === 'MPD' ? xml.documentElement : null;
29421
29422    if (!mpd || mpd && mpd.getElementsByTagName('parsererror').length > 0) {
29423      throw new Error(errors.DASH_INVALID_XML);
29424    }
29425
29426    return mpd;
29427  };
29428
29429  /**
29430   * Parses the manifest for a UTCTiming node, returning the nodes attributes if found
29431   *
29432   * @param {string} mpd
29433   *        XML string of the MPD manifest
29434   * @return {Object|null}
29435   *         Attributes of UTCTiming node specified in the manifest. Null if none found
29436   */
29437  var parseUTCTimingScheme = function parseUTCTimingScheme(mpd) {
29438    var UTCTimingNode = findChildren(mpd, 'UTCTiming')[0];
29439
29440    if (!UTCTimingNode) {
29441      return null;
29442    }
29443
29444    var attributes = parseAttributes$1(UTCTimingNode);
29445
29446    switch (attributes.schemeIdUri) {
29447      case 'urn:mpeg:dash:utc:http-head:2014':
29448      case 'urn:mpeg:dash:utc:http-head:2012':
29449        attributes.method = 'HEAD';
29450        break;
29451      case 'urn:mpeg:dash:utc:http-xsdate:2014':
29452      case 'urn:mpeg:dash:utc:http-iso:2014':
29453      case 'urn:mpeg:dash:utc:http-xsdate:2012':
29454      case 'urn:mpeg:dash:utc:http-iso:2012':
29455        attributes.method = 'GET';
29456        break;
29457      case 'urn:mpeg:dash:utc:direct:2014':
29458      case 'urn:mpeg:dash:utc:direct:2012':
29459        attributes.method = 'DIRECT';
29460        attributes.value = Date.parse(attributes.value);
29461        break;
29462      case 'urn:mpeg:dash:utc:http-ntp:2014':
29463      case 'urn:mpeg:dash:utc:ntp:2014':
29464      case 'urn:mpeg:dash:utc:sntp:2014':
29465      default:
29466        throw new Error(errors.UNSUPPORTED_UTC_TIMING_SCHEME);
29467    }
29468
29469    return attributes;
29470  };
29471
29472  var parse = function parse(manifestString, options) {
29473    return toM3u8(toPlaylists(inheritAttributes(stringToMpdXml(manifestString), options)));
29474  };
29475
29476  /**
29477   * Parses the manifest for a UTCTiming node, returning the nodes attributes if found
29478   *
29479   * @param {string} manifestString
29480   *        XML string of the MPD manifest
29481   * @return {Object|null}
29482   *         Attributes of UTCTiming node specified in the manifest. Null if none found
29483   */
29484  var parseUTCTiming = function parseUTCTiming(manifestString) {
29485    return parseUTCTimingScheme(stringToMpdXml(manifestString));
29486  };
29487
29488  var toUnsigned = function toUnsigned(value) {
29489    return value >>> 0;
29490  };
29491
29492  var bin = {
29493    toUnsigned: toUnsigned
29494  };
29495  var bin_1 = bin.toUnsigned;
29496
29497  var bin$1 = /*#__PURE__*/Object.freeze({
29498    default: bin,
29499    __moduleExports: bin,
29500    toUnsigned: bin_1
29501  });
29502
29503  var require$$0$1 = ( bin$1 && bin ) || bin$1;
29504
29505  var toUnsigned$1 = require$$0$1.toUnsigned;
29506  var _findBox, parseType, timescale, startTime;
29507
29508  // Find the data for a box specified by its path
29509  _findBox = function findBox(data, path) {
29510    var results = [],
29511        i,
29512        size,
29513        type,
29514        end,
29515        subresults;
29516
29517    if (!path.length) {
29518      // short-circuit the search for empty paths
29519      return null;
29520    }
29521
29522    for (i = 0; i < data.byteLength;) {
29523      size = toUnsigned$1(data[i] << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]);
29524
29525      type = parseType(data.subarray(i + 4, i + 8));
29526
29527      end = size > 1 ? i + size : data.byteLength;
29528
29529      if (type === path[0]) {
29530        if (path.length === 1) {
29531          // this is the end of the path and we've found the box we were
29532          // looking for
29533          results.push(data.subarray(i + 8, end));
29534        } else {
29535          // recursively search for the next box along the path
29536          subresults = _findBox(data.subarray(i + 8, end), path.slice(1));
29537          if (subresults.length) {
29538            results = results.concat(subresults);
29539          }
29540        }
29541      }
29542      i = end;
29543    }
29544
29545    // we've finished searching all of data
29546    return results;
29547  };
29548
29549  /**
29550   * Returns the string representation of an ASCII encoded four byte buffer.
29551   * @param buffer {Uint8Array} a four-byte buffer to translate
29552   * @return {string} the corresponding string
29553   */
29554  parseType = function parseType(buffer) {
29555    var result = '';
29556    result += String.fromCharCode(buffer[0]);
29557    result += String.fromCharCode(buffer[1]);
29558    result += String.fromCharCode(buffer[2]);
29559    result += String.fromCharCode(buffer[3]);
29560    return result;
29561  };
29562
29563  /**
29564   * Parses an MP4 initialization segment and extracts the timescale
29565   * values for any declared tracks. Timescale values indicate the
29566   * number of clock ticks per second to assume for time-based values
29567   * elsewhere in the MP4.
29568   *
29569   * To determine the start time of an MP4, you need two pieces of
29570   * information: the timescale unit and the earliest base media decode
29571   * time. Multiple timescales can be specified within an MP4 but the
29572   * base media decode time is always expressed in the timescale from
29573   * the media header box for the track:
29574   * ```
29575   * moov > trak > mdia > mdhd.timescale
29576   * ```
29577   * @param init {Uint8Array} the bytes of the init segment
29578   * @return {object} a hash of track ids to timescale values or null if
29579   * the init segment is malformed.
29580   */
29581  timescale = function timescale(init) {
29582    var result = {},
29583        traks = _findBox(init, ['moov', 'trak']);
29584
29585    // mdhd timescale
29586    return traks.reduce(function (result, trak) {
29587      var tkhd, version, index, id, mdhd;
29588
29589      tkhd = _findBox(trak, ['tkhd'])[0];
29590      if (!tkhd) {
29591        return null;
29592      }
29593      version = tkhd[0];
29594      index = version === 0 ? 12 : 20;
29595      id = toUnsigned$1(tkhd[index] << 24 | tkhd[index + 1] << 16 | tkhd[index + 2] << 8 | tkhd[index + 3]);
29596
29597      mdhd = _findBox(trak, ['mdia', 'mdhd'])[0];
29598      if (!mdhd) {
29599        return null;
29600      }
29601      version = mdhd[0];
29602      index = version === 0 ? 12 : 20;
29603      result[id] = toUnsigned$1(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]);
29604      return result;
29605    }, result);
29606  };
29607
29608  /**
29609   * Determine the base media decode start time, in seconds, for an MP4
29610   * fragment. If multiple fragments are specified, the earliest time is
29611   * returned.
29612   *
29613   * The base media decode time can be parsed from track fragment
29614   * metadata:
29615   * ```
29616   * moof > traf > tfdt.baseMediaDecodeTime
29617   * ```
29618   * It requires the timescale value from the mdhd to interpret.
29619   *
29620   * @param timescale {object} a hash of track ids to timescale values.
29621   * @return {number} the earliest base media decode start time for the
29622   * fragment, in seconds
29623   */
29624  startTime = function startTime(timescale, fragment) {
29625    var trafs, baseTimes, result;
29626
29627    // we need info from two childrend of each track fragment box
29628    trafs = _findBox(fragment, ['moof', 'traf']);
29629
29630    // determine the start times for each track
29631    baseTimes = [].concat.apply([], trafs.map(function (traf) {
29632      return _findBox(traf, ['tfhd']).map(function (tfhd) {
29633        var id, scale, baseTime;
29634
29635        // get the track id from the tfhd
29636        id = toUnsigned$1(tfhd[4] << 24 | tfhd[5] << 16 | tfhd[6] << 8 | tfhd[7]);
29637        // assume a 90kHz clock if no timescale was specified
29638        scale = timescale[id] || 90e3;
29639
29640        // get the base media decode time from the tfdt
29641        baseTime = _findBox(traf, ['tfdt']).map(function (tfdt) {
29642          var version, result;
29643
29644          version = tfdt[0];
29645          result = toUnsigned$1(tfdt[4] << 24 | tfdt[5] << 16 | tfdt[6] << 8 | tfdt[7]);
29646          if (version === 1) {
29647            result *= Math.pow(2, 32);
29648            result += toUnsigned$1(tfdt[8] << 24 | tfdt[9] << 16 | tfdt[10] << 8 | tfdt[11]);
29649          }
29650          return result;
29651        })[0];
29652        baseTime = baseTime || Infinity;
29653
29654        // convert base time to seconds
29655        return baseTime / scale;
29656      });
29657    }));
29658
29659    // return the minimum
29660    result = Math.min.apply(null, baseTimes);
29661    return isFinite(result) ? result : 0;
29662  };
29663
29664  var probe = {
29665    parseType: parseType,
29666    timescale: timescale,
29667    startTime: startTime
29668  };
29669
29670  var streamTypes = {
29671    H264_STREAM_TYPE: 0x1B,
29672    ADTS_STREAM_TYPE: 0x0F,
29673    METADATA_STREAM_TYPE: 0x15
29674  };
29675  var streamTypes_1 = streamTypes.H264_STREAM_TYPE;
29676  var streamTypes_2 = streamTypes.ADTS_STREAM_TYPE;
29677  var streamTypes_3 = streamTypes.METADATA_STREAM_TYPE;
29678
29679  var streamTypes$1 = /*#__PURE__*/Object.freeze({
29680    default: streamTypes,
29681    __moduleExports: streamTypes,
29682    H264_STREAM_TYPE: streamTypes_1,
29683    ADTS_STREAM_TYPE: streamTypes_2,
29684    METADATA_STREAM_TYPE: streamTypes_3
29685  });
29686
29687  /**
29688   * mux.js
29689   *
29690   * Copyright (c) 2014 Brightcove
29691   * All rights reserved.
29692   *
29693   * A lightweight readable stream implemention that handles event dispatching.
29694   * Objects that inherit from streams should call init in their constructors.
29695   */
29696
29697  var Stream$1 = function Stream() {
29698    this.init = function () {
29699      var listeners = {};
29700      /**
29701       * Add a listener for a specified event type.
29702       * @param type {string} the event name
29703       * @param listener {function} the callback to be invoked when an event of
29704       * the specified type occurs
29705       */
29706      this.on = function (type, listener) {
29707        if (!listeners[type]) {
29708          listeners[type] = [];
29709        }
29710        listeners[type] = listeners[type].concat(listener);
29711      };
29712      /**
29713       * Remove a listener for a specified event type.
29714       * @param type {string} the event name
29715       * @param listener {function} a function previously registered for this
29716       * type of event through `on`
29717       */
29718      this.off = function (type, listener) {
29719        var index;
29720        if (!listeners[type]) {
29721          return false;
vendor: 20,700 bytes, lines 29722-30341
29722        }
29723        index = listeners[type].indexOf(listener);
29724        listeners[type] = listeners[type].slice();
29725        listeners[type].splice(index, 1);
29726        return index > -1;
29727      };
29728      /**
29729       * Trigger an event of the specified type on this stream. Any additional
29730       * arguments to this function are passed as parameters to event listeners.
29731       * @param type {string} the event name
29732       */
29733      this.trigger = function (type) {
29734        var callbacks, i, length, args;
29735        callbacks = listeners[type];
29736        if (!callbacks) {
29737          return;
29738        }
29739        // Slicing the arguments on every invocation of this method
29740        // can add a significant amount of overhead. Avoid the
29741        // intermediate object creation for the common case of a
29742        // single callback argument
29743        if (arguments.length === 2) {
29744          length = callbacks.length;
29745          for (i = 0; i < length; ++i) {
29746            callbacks[i].call(this, arguments[1]);
29747          }
29748        } else {
29749          args = [];
29750          i = arguments.length;
29751          for (i = 1; i < arguments.length; ++i) {
29752            args.push(arguments[i]);
29753          }
29754          length = callbacks.length;
29755          for (i = 0; i < length; ++i) {
29756            callbacks[i].apply(this, args);
29757          }
29758        }
29759      };
29760      /**
29761       * Destroys the stream and cleans up.
29762       */
29763      this.dispose = function () {
29764        listeners = {};
29765      };
29766    };
29767  };
29768
29769  /**
29770   * Forwards all `data` events on this stream to the destination stream. The
29771   * destination stream should provide a method `push` to receive the data
29772   * events as they arrive.
29773   * @param destination {stream} the stream that will receive all `data` events
29774   * @param autoFlush {boolean} if false, we will not call `flush` on the destination
29775   *                            when the current stream emits a 'done' event
29776   * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
29777   */
29778  Stream$1.prototype.pipe = function (destination) {
29779    this.on('data', function (data) {
29780      destination.push(data);
29781    });
29782
29783    this.on('done', function (flushSource) {
29784      destination.flush(flushSource);
29785    });
29786
29787    return destination;
29788  };
29789
29790  // Default stream functions that are expected to be overridden to perform
29791  // actual work. These are provided by the prototype as a sort of no-op
29792  // implementation so that we don't have to check for their existence in the
29793  // `pipe` function above.
29794  Stream$1.prototype.push = function (data) {
29795    this.trigger('data', data);
29796  };
29797
29798  Stream$1.prototype.flush = function (flushSource) {
29799    this.trigger('done', flushSource);
29800  };
29801
29802  var stream = Stream$1;
29803
29804  var stream$1 = /*#__PURE__*/Object.freeze({
29805    default: stream,
29806    __moduleExports: stream
29807  });
29808
29809  var Stream$2 = ( stream$1 && stream ) || stream$1;
29810
29811  var MAX_TS = 8589934592;
29812
29813  var RO_THRESH = 4294967296;
29814
29815  var handleRollover = function handleRollover(value, reference) {
29816    var direction = 1;
29817
29818    if (value > reference) {
29819      // If the current timestamp value is greater than our reference timestamp and we detect a
29820      // timestamp rollover, this means the roll over is happening in the opposite direction.
29821      // Example scenario: Enter a long stream/video just after a rollover occurred. The reference
29822      // point will be set to a small number, e.g. 1. The user then seeks backwards over the
29823      // rollover point. In loading this segment, the timestamp values will be very large,
29824      // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust
29825      // the time stamp to be `value - 2^33`.
29826      direction = -1;
29827    }
29828
29829    // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will
29830    // cause an incorrect adjustment.
29831    while (Math.abs(reference - value) > RO_THRESH) {
29832      value += direction * MAX_TS;
29833    }
29834
29835    return value;
29836  };
29837
29838  var TimestampRolloverStream = function TimestampRolloverStream(type) {
29839    var lastDTS, referenceDTS;
29840
29841    TimestampRolloverStream.prototype.init.call(this);
29842
29843    this.type_ = type;
29844
29845    this.push = function (data) {
29846      if (data.type !== this.type_) {
29847        return;
29848      }
29849
29850      if (referenceDTS === undefined) {
29851        referenceDTS = data.dts;
29852      }
29853
29854      data.dts = handleRollover(data.dts, referenceDTS);
29855      data.pts = handleRollover(data.pts, referenceDTS);
29856
29857      lastDTS = data.dts;
29858
29859      this.trigger('data', data);
29860    };
29861
29862    this.flush = function () {
29863      referenceDTS = lastDTS;
29864      this.trigger('done');
29865    };
29866
29867    this.discontinuity = function () {
29868      referenceDTS = void 0;
29869      lastDTS = void 0;
29870    };
29871  };
29872
29873  TimestampRolloverStream.prototype = new Stream$2();
29874
29875  var timestampRolloverStream = {
29876    TimestampRolloverStream: TimestampRolloverStream,
29877    handleRollover: handleRollover
29878  };
29879  var timestampRolloverStream_1 = timestampRolloverStream.TimestampRolloverStream;
29880  var timestampRolloverStream_2 = timestampRolloverStream.handleRollover;
29881
29882  var timestampRolloverStream$1 = /*#__PURE__*/Object.freeze({
29883    default: timestampRolloverStream,
29884    __moduleExports: timestampRolloverStream,
29885    TimestampRolloverStream: timestampRolloverStream_1,
29886    handleRollover: timestampRolloverStream_2
29887  });
29888
29889  var StreamTypes = ( streamTypes$1 && streamTypes ) || streamTypes$1;
29890
29891  var parsePid = function parsePid(packet) {
29892    var pid = packet[1] & 0x1f;
29893    pid <<= 8;
29894    pid |= packet[2];
29895    return pid;
29896  };
29897
29898  var parsePayloadUnitStartIndicator = function parsePayloadUnitStartIndicator(packet) {
29899    return !!(packet[1] & 0x40);
29900  };
29901
29902  var parseAdaptionField = function parseAdaptionField(packet) {
29903    var offset = 0;
29904    // if an adaption field is present, its length is specified by the
29905    // fifth byte of the TS packet header. The adaptation field is
29906    // used to add stuffing to PES packets that don't fill a complete
29907    // TS packet, and to specify some forms of timing and control data
29908    // that we do not currently use.
29909    if ((packet[3] & 0x30) >>> 4 > 0x01) {
29910      offset += packet[4] + 1;
29911    }
29912    return offset;
29913  };
29914
29915  var parseType$1 = function parseType(packet, pmtPid) {
29916    var pid = parsePid(packet);
29917    if (pid === 0) {
29918      return 'pat';
29919    } else if (pid === pmtPid) {
29920      return 'pmt';
29921    } else if (pmtPid) {
29922      return 'pes';
29923    }
29924    return null;
29925  };
29926
29927  var parsePat = function parsePat(packet) {
29928    var pusi = parsePayloadUnitStartIndicator(packet);
29929    var offset = 4 + parseAdaptionField(packet);
29930
29931    if (pusi) {
29932      offset += packet[offset] + 1;
29933    }
29934
29935    return (packet[offset + 10] & 0x1f) << 8 | packet[offset + 11];
29936  };
29937
29938  var parsePmt = function parsePmt(packet) {
29939    var programMapTable = {};
29940    var pusi = parsePayloadUnitStartIndicator(packet);
29941    var payloadOffset = 4 + parseAdaptionField(packet);
29942
29943    if (pusi) {
29944      payloadOffset += packet[payloadOffset] + 1;
29945    }
29946
29947    // PMTs can be sent ahead of the time when they should actually
29948    // take effect. We don't believe this should ever be the case
29949    // for HLS but we'll ignore "forward" PMT declarations if we see
29950    // them. Future PMT declarations have the current_next_indicator
29951    // set to zero.
29952    if (!(packet[payloadOffset + 5] & 0x01)) {
29953      return;
29954    }
29955
29956    var sectionLength, tableEnd, programInfoLength;
29957    // the mapping table ends at the end of the current section
29958    sectionLength = (packet[payloadOffset + 1] & 0x0f) << 8 | packet[payloadOffset + 2];
29959    tableEnd = 3 + sectionLength - 4;
29960
29961    // to determine where the table is, we have to figure out how
29962    // long the program info descriptors are
29963    programInfoLength = (packet[payloadOffset + 10] & 0x0f) << 8 | packet[payloadOffset + 11];
29964
29965    // advance the offset to the first entry in the mapping table
29966    var offset = 12 + programInfoLength;
29967    while (offset < tableEnd) {
29968      var i = payloadOffset + offset;
29969      // add an entry that maps the elementary_pid to the stream_type
29970      programMapTable[(packet[i + 1] & 0x1F) << 8 | packet[i + 2]] = packet[i];
29971
29972      // move to the next table entry
29973      // skip past the elementary stream descriptors, if present
29974      offset += ((packet[i + 3] & 0x0F) << 8 | packet[i + 4]) + 5;
29975    }
29976    return programMapTable;
29977  };
29978
29979  var parsePesType = function parsePesType(packet, programMapTable) {
29980    var pid = parsePid(packet);
29981    var type = programMapTable[pid];
29982    switch (type) {
29983      case StreamTypes.H264_STREAM_TYPE:
29984        return 'video';
29985      case StreamTypes.ADTS_STREAM_TYPE:
29986        return 'audio';
29987      case StreamTypes.METADATA_STREAM_TYPE:
29988        return 'timed-metadata';
29989      default:
29990        return null;
29991    }
29992  };
29993
29994  var parsePesTime = function parsePesTime(packet) {
29995    var pusi = parsePayloadUnitStartIndicator(packet);
29996    if (!pusi) {
29997      return null;
29998    }
29999
30000    var offset = 4 + parseAdaptionField(packet);
30001
30002    if (offset >= packet.byteLength) {
30003      // From the H 222.0 MPEG-TS spec
30004      // "For transport stream packets carrying PES packets, stuffing is needed when there
30005      //  is insufficient PES packet data to completely fill the transport stream packet
30006      //  payload bytes. Stuffing is accomplished by defining an adaptation field longer than
30007      //  the sum of the lengths of the data elements in it, so that the payload bytes
30008      //  remaining after the adaptation field exactly accommodates the available PES packet
30009      //  data."
30010      //
30011      // If the offset is >= the length of the packet, then the packet contains no data
30012      // and instead is just adaption field stuffing bytes
30013      return null;
30014    }
30015
30016    var pes = null;
30017    var ptsDtsFlags;
30018
30019    // PES packets may be annotated with a PTS value, or a PTS value
30020    // and a DTS value. Determine what combination of values is
30021    // available to work with.
30022    ptsDtsFlags = packet[offset + 7];
30023
30024    // PTS and DTS are normally stored as a 33-bit number.  Javascript
30025    // performs all bitwise operations on 32-bit integers but javascript
30026    // supports a much greater range (52-bits) of integer using standard
30027    // mathematical operations.
30028    // We construct a 31-bit value using bitwise operators over the 31
30029    // most significant bits and then multiply by 4 (equal to a left-shift
30030    // of 2) before we add the final 2 least significant bits of the
30031    // timestamp (equal to an OR.)
30032    if (ptsDtsFlags & 0xC0) {
30033      pes = {};
30034      // the PTS and DTS are not written out directly. For information
30035      // on how they are encoded, see
30036      // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
30037      pes.pts = (packet[offset + 9] & 0x0E) << 27 | (packet[offset + 10] & 0xFF) << 20 | (packet[offset + 11] & 0xFE) << 12 | (packet[offset + 12] & 0xFF) << 5 | (packet[offset + 13] & 0xFE) >>> 3;
30038      pes.pts *= 4; // Left shift by 2
30039      pes.pts += (packet[offset + 13] & 0x06) >>> 1; // OR by the two LSBs
30040      pes.dts = pes.pts;
30041      if (ptsDtsFlags & 0x40) {
30042        pes.dts = (packet[offset + 14] & 0x0E) << 27 | (packet[offset + 15] & 0xFF) << 20 | (packet[offset + 16] & 0xFE) << 12 | (packet[offset + 17] & 0xFF) << 5 | (packet[offset + 18] & 0xFE) >>> 3;
30043        pes.dts *= 4; // Left shift by 2
30044        pes.dts += (packet[offset + 18] & 0x06) >>> 1; // OR by the two LSBs
30045      }
30046    }
30047    return pes;
30048  };
30049
30050  var parseNalUnitType = function parseNalUnitType(type) {
30051    switch (type) {
30052      case 0x05:
30053        return 'slice_layer_without_partitioning_rbsp_idr';
30054      case 0x06:
30055        return 'sei_rbsp';
30056      case 0x07:
30057        return 'seq_parameter_set_rbsp';
30058      case 0x08:
30059        return 'pic_parameter_set_rbsp';
30060      case 0x09:
30061        return 'access_unit_delimiter_rbsp';
30062      default:
30063        return null;
30064    }
30065  };
30066
30067  var videoPacketContainsKeyFrame = function videoPacketContainsKeyFrame(packet) {
30068    var offset = 4 + parseAdaptionField(packet);
30069    var frameBuffer = packet.subarray(offset);
30070    var frameI = 0;
30071    var frameSyncPoint = 0;
30072    var foundKeyFrame = false;
30073    var nalType;
30074
30075    // advance the sync point to a NAL start, if necessary
30076    for (; frameSyncPoint < frameBuffer.byteLength - 3; frameSyncPoint++) {
30077      if (frameBuffer[frameSyncPoint + 2] === 1) {
30078        // the sync point is properly aligned
30079        frameI = frameSyncPoint + 5;
30080        break;
30081      }
30082    }
30083
30084    while (frameI < frameBuffer.byteLength) {
30085      // look at the current byte to determine if we've hit the end of
30086      // a NAL unit boundary
30087      switch (frameBuffer[frameI]) {
30088        case 0:
30089          // skip past non-sync sequences
30090          if (frameBuffer[frameI - 1] !== 0) {
30091            frameI += 2;
30092            break;
30093          } else if (frameBuffer[frameI - 2] !== 0) {
30094            frameI++;
30095            break;
30096          }
30097
30098          if (frameSyncPoint + 3 !== frameI - 2) {
30099            nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
30100            if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
30101              foundKeyFrame = true;
30102            }
30103          }
30104
30105          // drop trailing zeroes
30106          do {
30107            frameI++;
30108          } while (frameBuffer[frameI] !== 1 && frameI < frameBuffer.length);
30109          frameSyncPoint = frameI - 2;
30110          frameI += 3;
30111          break;
30112        case 1:
30113          // skip past non-sync sequences
30114          if (frameBuffer[frameI - 1] !== 0 || frameBuffer[frameI - 2] !== 0) {
30115            frameI += 3;
30116            break;
30117          }
30118
30119          nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
30120          if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
30121            foundKeyFrame = true;
30122          }
30123          frameSyncPoint = frameI - 2;
30124          frameI += 3;
30125          break;
30126        default:
30127          // the current byte isn't a one or zero, so it cannot be part
30128          // of a sync sequence
30129          frameI += 3;
30130          break;
30131      }
30132    }
30133    frameBuffer = frameBuffer.subarray(frameSyncPoint);
30134    frameI -= frameSyncPoint;
30135    frameSyncPoint = 0;
30136    // parse the final nal
30137    if (frameBuffer && frameBuffer.byteLength > 3) {
30138      nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
30139      if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
30140        foundKeyFrame = true;
30141      }
30142    }
30143
30144    return foundKeyFrame;
30145  };
30146
30147  var probe$1 = {
30148    parseType: parseType$1,
30149    parsePat: parsePat,
30150    parsePmt: parsePmt,
30151    parsePayloadUnitStartIndicator: parsePayloadUnitStartIndicator,
30152    parsePesType: parsePesType,
30153    parsePesTime: parsePesTime,
30154    videoPacketContainsKeyFrame: videoPacketContainsKeyFrame
30155  };
30156  var probe_1$1 = probe$1.parseType;
30157  var probe_2$1 = probe$1.parsePat;
30158  var probe_3$1 = probe$1.parsePmt;
30159  var probe_4 = probe$1.parsePayloadUnitStartIndicator;
30160  var probe_5 = probe$1.parsePesType;
30161  var probe_6 = probe$1.parsePesTime;
30162  var probe_7 = probe$1.videoPacketContainsKeyFrame;
30163
30164  var probe$2 = /*#__PURE__*/Object.freeze({
30165    default: probe$1,
30166    __moduleExports: probe$1,
30167    parseType: probe_1$1,
30168    parsePat: probe_2$1,
30169    parsePmt: probe_3$1,
30170    parsePayloadUnitStartIndicator: probe_4,
30171    parsePesType: probe_5,
30172    parsePesTime: probe_6,
30173    videoPacketContainsKeyFrame: probe_7
30174  });
30175
30176  /**
30177   * mux.js
30178   *
30179   * Copyright (c) 2016 Brightcove
30180   * All rights reserved.
30181   *
30182   * Utilities to detect basic properties and metadata about Aac data.
30183   */
30184
30185  var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
30186
30187  var parseSyncSafeInteger = function parseSyncSafeInteger(data) {
30188    return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
30189  };
30190
30191  // return a percent-encoded representation of the specified byte range
30192  // @see http://en.wikipedia.org/wiki/Percent-encoding
30193  var percentEncode = function percentEncode(bytes, start, end) {
30194    var i,
30195        result = '';
30196    for (i = start; i < end; i++) {
30197      result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
30198    }
30199    return result;
30200  };
30201
30202  // return the string representation of the specified byte range,
30203  // interpreted as ISO-8859-1.
30204  var parseIso88591 = function parseIso88591(bytes, start, end) {
30205    return unescape(percentEncode(bytes, start, end)); // jshint ignore:line
30206  };
30207
30208  var parseId3TagSize = function parseId3TagSize(header, byteIndex) {
30209    var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9],
30210        flags = header[byteIndex + 5],
30211        footerPresent = (flags & 16) >> 4;
30212
30213    if (footerPresent) {
30214      return returnSize + 20;
30215    }
30216    return returnSize + 10;
30217  };
30218
30219  var parseAdtsSize = function parseAdtsSize(header, byteIndex) {
30220    var lowThree = (header[byteIndex + 5] & 0xE0) >> 5,
30221        middle = header[byteIndex + 4] << 3,
30222        highTwo = header[byteIndex + 3] & 0x3 << 11;
30223
30224    return highTwo | middle | lowThree;
30225  };
30226
30227  var parseType$2 = function parseType(header, byteIndex) {
30228    if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) {
30229      return 'timed-metadata';
30230    } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) {
30231      return 'audio';
30232    }
30233    return null;
30234  };
30235
30236  var parseSampleRate = function parseSampleRate(packet) {
30237    var i = 0;
30238
30239    while (i + 5 < packet.length) {
30240      if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) {
30241        // If a valid header was not found,  jump one forward and attempt to
30242        // find a valid ADTS header starting at the next byte
30243        i++;
30244        continue;
30245      }
30246      return ADTS_SAMPLING_FREQUENCIES[(packet[i + 2] & 0x3c) >>> 2];
30247    }
30248
30249    return null;
30250  };
30251
30252  var parseAacTimestamp = function parseAacTimestamp(packet) {
30253    var frameStart, frameSize, frame, frameHeader;
30254
30255    // find the start of the first frame and the end of the tag
30256    frameStart = 10;
30257    if (packet[5] & 0x40) {
30258      // advance the frame start past the extended header
30259      frameStart += 4; // header size field
30260      frameStart += parseSyncSafeInteger(packet.subarray(10, 14));
30261    }
30262
30263    // parse one or more ID3 frames
30264    // http://id3.org/id3v2.3.0#ID3v2_frame_overview
30265    do {
30266      // determine the number of bytes in this frame
30267      frameSize = parseSyncSafeInteger(packet.subarray(frameStart + 4, frameStart + 8));
30268      if (frameSize < 1) {
30269        return null;
30270      }
30271      frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]);
30272
30273      if (frameHeader === 'PRIV') {
30274        frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10);
30275
30276        for (var i = 0; i < frame.byteLength; i++) {
30277          if (frame[i] === 0) {
30278            var owner = parseIso88591(frame, 0, i);
30279            if (owner === 'com.apple.streaming.transportStreamTimestamp') {
30280              var d = frame.subarray(i + 1);
30281              var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
30282              size *= 4;
30283              size += d[7] & 0x03;
30284
30285              return size;
30286            }
30287            break;
30288          }
30289        }
30290      }
30291
30292      frameStart += 10; // advance past the frame header
30293      frameStart += frameSize; // advance past the frame body
30294    } while (frameStart < packet.byteLength);
30295    return null;
30296  };
30297
30298  var probe$3 = {
30299    parseId3TagSize: parseId3TagSize,
30300    parseAdtsSize: parseAdtsSize,
30301    parseType: parseType$2,
30302    parseSampleRate: parseSampleRate,
30303    parseAacTimestamp: parseAacTimestamp
30304  };
30305  var probe_1$2 = probe$3.parseId3TagSize;
30306  var probe_2$2 = probe$3.parseAdtsSize;
30307  var probe_3$2 = probe$3.parseType;
30308  var probe_4$1 = probe$3.parseSampleRate;
30309  var probe_5$1 = probe$3.parseAacTimestamp;
30310
30311  var probe$4 = /*#__PURE__*/Object.freeze({
30312    default: probe$3,
30313    __moduleExports: probe$3,
30314    parseId3TagSize: probe_1$2,
30315    parseAdtsSize: probe_2$2,
30316    parseType: probe_3$2,
30317    parseSampleRate: probe_4$1,
30318    parseAacTimestamp: probe_5$1
30319  });
30320
30321  var require$$0$2 = ( timestampRolloverStream$1 && timestampRolloverStream ) || timestampRolloverStream$1;
30322
30323  var require$$1$1 = ( probe$2 && probe$1 ) || probe$2;
30324
30325  var require$$2$1 = ( probe$4 && probe$3 ) || probe$4;
30326
30327  var handleRollover$1 = require$$0$2.handleRollover;
30328  var probe$5 = {};
30329  probe$5.ts = require$$1$1;
30330  probe$5.aac = require$$2$1;
30331
30332  var PES_TIMESCALE = 90000,
30333      MP2T_PACKET_LENGTH = 188,
30334      // bytes
30335  SYNC_BYTE = 0x47;
30336
30337  var isLikelyAacData = function isLikelyAacData(data) {
30338    if (data[0] === 'I'.charCodeAt(0) && data[1] === 'D'.charCodeAt(0) && data[2] === '3'.charCodeAt(0)) {
30339      return true;
30340    }
30341    return false;
vendor: 9,174 bytes, lines 30342-30636
30342  };
30343
30344  /**
30345   * walks through segment data looking for pat and pmt packets to parse out
30346   * program map table information
30347   */
30348  var parsePsi_ = function parsePsi_(bytes, pmt) {
30349    var startIndex = 0,
30350        endIndex = MP2T_PACKET_LENGTH,
30351        packet,
30352        type;
30353
30354    while (endIndex < bytes.byteLength) {
30355      // Look for a pair of start and end sync bytes in the data..
30356      if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
30357        // We found a packet
30358        packet = bytes.subarray(startIndex, endIndex);
30359        type = probe$5.ts.parseType(packet, pmt.pid);
30360
30361        switch (type) {
30362          case 'pat':
30363            if (!pmt.pid) {
30364              pmt.pid = probe$5.ts.parsePat(packet);
30365            }
30366            break;
30367          case 'pmt':
30368            if (!pmt.table) {
30369              pmt.table = probe$5.ts.parsePmt(packet);
30370            }
30371            break;
30372          default:
30373            break;
30374        }
30375
30376        // Found the pat and pmt, we can stop walking the segment
30377        if (pmt.pid && pmt.table) {
30378          return;
30379        }
30380
30381        startIndex += MP2T_PACKET_LENGTH;
30382        endIndex += MP2T_PACKET_LENGTH;
30383        continue;
30384      }
30385
30386      // If we get here, we have somehow become de-synchronized and we need to step
30387      // forward one byte at a time until we find a pair of sync bytes that denote
30388      // a packet
30389      startIndex++;
30390      endIndex++;
30391    }
30392  };
30393
30394  /**
30395   * walks through the segment data from the start and end to get timing information
30396   * for the first and last audio pes packets
30397   */
30398  var parseAudioPes_ = function parseAudioPes_(bytes, pmt, result) {
30399    var startIndex = 0,
30400        endIndex = MP2T_PACKET_LENGTH,
30401        packet,
30402        type,
30403        pesType,
30404        pusi,
30405        parsed;
30406
30407    var endLoop = false;
30408
30409    // Start walking from start of segment to get first audio packet
30410    while (endIndex < bytes.byteLength) {
30411      // Look for a pair of start and end sync bytes in the data..
30412      if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
30413        // We found a packet
30414        packet = bytes.subarray(startIndex, endIndex);
30415        type = probe$5.ts.parseType(packet, pmt.pid);
30416
30417        switch (type) {
30418          case 'pes':
30419            pesType = probe$5.ts.parsePesType(packet, pmt.table);
30420            pusi = probe$5.ts.parsePayloadUnitStartIndicator(packet);
30421            if (pesType === 'audio' && pusi) {
30422              parsed = probe$5.ts.parsePesTime(packet);
30423              if (parsed) {
30424                parsed.type = 'audio';
30425                result.audio.push(parsed);
30426                endLoop = true;
30427              }
30428            }
30429            break;
30430          default:
30431            break;
30432        }
30433
30434        if (endLoop) {
30435          break;
30436        }
30437
30438        startIndex += MP2T_PACKET_LENGTH;
30439        endIndex += MP2T_PACKET_LENGTH;
30440        continue;
30441      }
30442
30443      // If we get here, we have somehow become de-synchronized and we need to step
30444      // forward one byte at a time until we find a pair of sync bytes that denote
30445      // a packet
30446      startIndex++;
30447      endIndex++;
30448    }
30449
30450    // Start walking from end of segment to get last audio packet
30451    endIndex = bytes.byteLength;
30452    startIndex = endIndex - MP2T_PACKET_LENGTH;
30453    endLoop = false;
30454    while (startIndex >= 0) {
30455      // Look for a pair of start and end sync bytes in the data..
30456      if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
30457        // We found a packet
30458        packet = bytes.subarray(startIndex, endIndex);
30459        type = probe$5.ts.parseType(packet, pmt.pid);
30460
30461        switch (type) {
30462          case 'pes':
30463            pesType = probe$5.ts.parsePesType(packet, pmt.table);
30464            pusi = probe$5.ts.parsePayloadUnitStartIndicator(packet);
30465            if (pesType === 'audio' && pusi) {
30466              parsed = probe$5.ts.parsePesTime(packet);
30467              if (parsed) {
30468                parsed.type = 'audio';
30469                result.audio.push(parsed);
30470                endLoop = true;
30471              }
30472            }
30473            break;
30474          default:
30475            break;
30476        }
30477
30478        if (endLoop) {
30479          break;
30480        }
30481
30482        startIndex -= MP2T_PACKET_LENGTH;
30483        endIndex -= MP2T_PACKET_LENGTH;
30484        continue;
30485      }
30486
30487      // If we get here, we have somehow become de-synchronized and we need to step
30488      // forward one byte at a time until we find a pair of sync bytes that denote
30489      // a packet
30490      startIndex--;
30491      endIndex--;
30492    }
30493  };
30494
30495  /**
30496   * walks through the segment data from the start and end to get timing information
30497   * for the first and last video pes packets as well as timing information for the first
30498   * key frame.
30499   */
30500  var parseVideoPes_ = function parseVideoPes_(bytes, pmt, result) {
30501    var startIndex = 0,
30502        endIndex = MP2T_PACKET_LENGTH,
30503        packet,
30504        type,
30505        pesType,
30506        pusi,
30507        parsed,
30508        frame,
30509        i,
30510        pes;
30511
30512    var endLoop = false;
30513
30514    var currentFrame = {
30515      data: [],
30516      size: 0
30517    };
30518
30519    // Start walking from start of segment to get first video packet
30520    while (endIndex < bytes.byteLength) {
30521      // Look for a pair of start and end sync bytes in the data..
30522      if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
30523        // We found a packet
30524        packet = bytes.subarray(startIndex, endIndex);
30525        type = probe$5.ts.parseType(packet, pmt.pid);
30526
30527        switch (type) {
30528          case 'pes':
30529            pesType = probe$5.ts.parsePesType(packet, pmt.table);
30530            pusi = probe$5.ts.parsePayloadUnitStartIndicator(packet);
30531            if (pesType === 'video') {
30532              if (pusi && !endLoop) {
30533                parsed = probe$5.ts.parsePesTime(packet);
30534                if (parsed) {
30535                  parsed.type = 'video';
30536                  result.video.push(parsed);
30537                  endLoop = true;
30538                }
30539              }
30540              if (!result.firstKeyFrame) {
30541                if (pusi) {
30542                  if (currentFrame.size !== 0) {
30543                    frame = new Uint8Array(currentFrame.size);
30544                    i = 0;
30545                    while (currentFrame.data.length) {
30546                      pes = currentFrame.data.shift();
30547                      frame.set(pes, i);
30548                      i += pes.byteLength;
30549                    }
30550                    if (probe$5.ts.videoPacketContainsKeyFrame(frame)) {
30551                      result.firstKeyFrame = probe$5.ts.parsePesTime(frame);
30552                      result.firstKeyFrame.type = 'video';
30553                    }
30554                    currentFrame.size = 0;
30555                  }
30556                }
30557                currentFrame.data.push(packet);
30558                currentFrame.size += packet.byteLength;
30559              }
30560            }
30561            break;
30562          default:
30563            break;
30564        }
30565
30566        if (endLoop && result.firstKeyFrame) {
30567          break;
30568        }
30569
30570        startIndex += MP2T_PACKET_LENGTH;
30571        endIndex += MP2T_PACKET_LENGTH;
30572        continue;
30573      }
30574
30575      // If we get here, we have somehow become de-synchronized and we need to step
30576      // forward one byte at a time until we find a pair of sync bytes that denote
30577      // a packet
30578      startIndex++;
30579      endIndex++;
30580    }
30581
30582    // Start walking from end of segment to get last video packet
30583    endIndex = bytes.byteLength;
30584    startIndex = endIndex - MP2T_PACKET_LENGTH;
30585    endLoop = false;
30586    while (startIndex >= 0) {
30587      // Look for a pair of start and end sync bytes in the data..
30588      if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
30589        // We found a packet
30590        packet = bytes.subarray(startIndex, endIndex);
30591        type = probe$5.ts.parseType(packet, pmt.pid);
30592
30593        switch (type) {
30594          case 'pes':
30595            pesType = probe$5.ts.parsePesType(packet, pmt.table);
30596            pusi = probe$5.ts.parsePayloadUnitStartIndicator(packet);
30597            if (pesType === 'video' && pusi) {
30598              parsed = probe$5.ts.parsePesTime(packet);
30599              if (parsed) {
30600                parsed.type = 'video';
30601                result.video.push(parsed);
30602                endLoop = true;
30603              }
30604            }
30605            break;
30606          default:
30607            break;
30608        }
30609
30610        if (endLoop) {
30611          break;
30612        }
30613
30614        startIndex -= MP2T_PACKET_LENGTH;
30615        endIndex -= MP2T_PACKET_LENGTH;
30616        continue;
30617      }
30618
30619      // If we get here, we have somehow become de-synchronized and we need to step
30620      // forward one byte at a time until we find a pair of sync bytes that denote
30621      // a packet
30622      startIndex--;
30623      endIndex--;
30624    }
30625  };
30626
30627  /**
30628   * Adjusts the timestamp information for the segment to account for
30629   * rollover and convert to seconds based on pes packet timescale (90khz clock)
30630   */
30631  var adjustTimestamp_ = function adjustTimestamp_(segmentInfo, baseTimestamp) {
30632    if (segmentInfo.audio && segmentInfo.audio.length) {
30633      var audioBaseTimestamp = baseTimestamp;
30634      if (typeof audioBaseTimestamp === 'undefined') {
30635        audioBaseTimestamp = segmentInfo.audio[0].dts;
30636      }
vendor: 5,675 bytes, lines 30637-30835
30637      segmentInfo.audio.forEach(function (info) {
30638        info.dts = handleRollover$1(info.dts, audioBaseTimestamp);
30639        info.pts = handleRollover$1(info.pts, audioBaseTimestamp);
30640        // time in seconds
30641        info.dtsTime = info.dts / PES_TIMESCALE;
30642        info.ptsTime = info.pts / PES_TIMESCALE;
30643      });
30644    }
30645
30646    if (segmentInfo.video && segmentInfo.video.length) {
30647      var videoBaseTimestamp = baseTimestamp;
30648      if (typeof videoBaseTimestamp === 'undefined') {
30649        videoBaseTimestamp = segmentInfo.video[0].dts;
30650      }
30651      segmentInfo.video.forEach(function (info) {
30652        info.dts = handleRollover$1(info.dts, videoBaseTimestamp);
30653        info.pts = handleRollover$1(info.pts, videoBaseTimestamp);
30654        // time in seconds
30655        info.dtsTime = info.dts / PES_TIMESCALE;
30656        info.ptsTime = info.pts / PES_TIMESCALE;
30657      });
30658      if (segmentInfo.firstKeyFrame) {
30659        var frame = segmentInfo.firstKeyFrame;
30660        frame.dts = handleRollover$1(frame.dts, videoBaseTimestamp);
30661        frame.pts = handleRollover$1(frame.pts, videoBaseTimestamp);
30662        // time in seconds
30663        frame.dtsTime = frame.dts / PES_TIMESCALE;
30664        frame.ptsTime = frame.dts / PES_TIMESCALE;
30665      }
30666    }
30667  };
30668
30669  /**
30670   * inspects the aac data stream for start and end time information
30671   */
30672  var inspectAac_ = function inspectAac_(bytes) {
30673    var endLoop = false,
30674        audioCount = 0,
30675        sampleRate = null,
30676        timestamp = null,
30677        frameSize = 0,
30678        byteIndex = 0,
30679        packet;
30680
30681    while (bytes.length - byteIndex >= 3) {
30682      var type = probe$5.aac.parseType(bytes, byteIndex);
30683      switch (type) {
30684        case 'timed-metadata':
30685          // Exit early because we don't have enough to parse
30686          // the ID3 tag header
30687          if (bytes.length - byteIndex < 10) {
30688            endLoop = true;
30689            break;
30690          }
30691
30692          frameSize = probe$5.aac.parseId3TagSize(bytes, byteIndex);
30693
30694          // Exit early if we don't have enough in the buffer
30695          // to emit a full packet
30696          if (frameSize > bytes.length) {
30697            endLoop = true;
30698            break;
30699          }
30700          if (timestamp === null) {
30701            packet = bytes.subarray(byteIndex, byteIndex + frameSize);
30702            timestamp = probe$5.aac.parseAacTimestamp(packet);
30703          }
30704          byteIndex += frameSize;
30705          break;
30706        case 'audio':
30707          // Exit early because we don't have enough to parse
30708          // the ADTS frame header
30709          if (bytes.length - byteIndex < 7) {
30710            endLoop = true;
30711            break;
30712          }
30713
30714          frameSize = probe$5.aac.parseAdtsSize(bytes, byteIndex);
30715
30716          // Exit early if we don't have enough in the buffer
30717          // to emit a full packet
30718          if (frameSize > bytes.length) {
30719            endLoop = true;
30720            break;
30721          }
30722          if (sampleRate === null) {
30723            packet = bytes.subarray(byteIndex, byteIndex + frameSize);
30724            sampleRate = probe$5.aac.parseSampleRate(packet);
30725          }
30726          audioCount++;
30727          byteIndex += frameSize;
30728          break;
30729        default:
30730          byteIndex++;
30731          break;
30732      }
30733      if (endLoop) {
30734        return null;
30735      }
30736    }
30737    if (sampleRate === null || timestamp === null) {
30738      return null;
30739    }
30740
30741    var audioTimescale = PES_TIMESCALE / sampleRate;
30742
30743    var result = {
30744      audio: [{
30745        type: 'audio',
30746        dts: timestamp,
30747        pts: timestamp
30748      }, {
30749        type: 'audio',
30750        dts: timestamp + audioCount * 1024 * audioTimescale,
30751        pts: timestamp + audioCount * 1024 * audioTimescale
30752      }]
30753    };
30754
30755    return result;
30756  };
30757
30758  /**
30759   * inspects the transport stream segment data for start and end time information
30760   * of the audio and video tracks (when present) as well as the first key frame's
30761   * start time.
30762   */
30763  var inspectTs_ = function inspectTs_(bytes) {
30764    var pmt = {
30765      pid: null,
30766      table: null
30767    };
30768
30769    var result = {};
30770
30771    parsePsi_(bytes, pmt);
30772
30773    for (var pid in pmt.table) {
30774      if (pmt.table.hasOwnProperty(pid)) {
30775        var type = pmt.table[pid];
30776        switch (type) {
30777          case StreamTypes.H264_STREAM_TYPE:
30778            result.video = [];
30779            parseVideoPes_(bytes, pmt, result);
30780            if (result.video.length === 0) {
30781              delete result.video;
30782            }
30783            break;
30784          case StreamTypes.ADTS_STREAM_TYPE:
30785            result.audio = [];
30786            parseAudioPes_(bytes, pmt, result);
30787            if (result.audio.length === 0) {
30788              delete result.audio;
30789            }
30790            break;
30791          default:
30792            break;
30793        }
30794      }
30795    }
30796    return result;
30797  };
30798
30799  /**
30800   * Inspects segment byte data and returns an object with start and end timing information
30801   *
30802   * @param {Uint8Array} bytes The segment byte data
30803   * @param {Number} baseTimestamp Relative reference timestamp used when adjusting frame
30804   *  timestamps for rollover. This value must be in 90khz clock.
30805   * @return {Object} Object containing start and end frame timing info of segment.
30806   */
30807  var inspect = function inspect(bytes, baseTimestamp) {
30808    var isAacData = isLikelyAacData(bytes);
30809
30810    var result;
30811
30812    if (isAacData) {
30813      result = inspectAac_(bytes);
30814    } else {
30815      result = inspectTs_(bytes);
30816    }
30817
30818    if (!result || !result.audio && !result.video) {
30819      return null;
30820    }
30821
30822    adjustTimestamp_(result, baseTimestamp);
30823
30824    return result;
30825  };
30826
30827  var tsInspector = {
30828    inspect: inspect
30829  };
30830
30831  /*
30832   * pkcs7.pad
30833   * https://github.com/brightcove/pkcs7
30834   *
30835   * Copyright (c) 2014 Brightcove
vendor: 121,872 bytes, lines 30836-34502
30836   * Licensed under the apache2 license.
30837   */
30838
30839  /**
30840   * Returns the subarray of a Uint8Array without PKCS#7 padding.
30841   * @param padded {Uint8Array} unencrypted bytes that have been padded
30842   * @return {Uint8Array} the unpadded bytes
30843   * @see http://tools.ietf.org/html/rfc5652
30844   */
30845  function unpad(padded) {
30846    return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]);
30847  }
30848
30849  var classCallCheck$2 = function classCallCheck$$1(instance, Constructor) {
30850    if (!(instance instanceof Constructor)) {
30851      throw new TypeError("Cannot call a class as a function");
30852    }
30853  };
30854
30855  var createClass$1 = function () {
30856    function defineProperties(target, props) {
30857      for (var i = 0; i < props.length; i++) {
30858        var descriptor = props[i];
30859        descriptor.enumerable = descriptor.enumerable || false;
30860        descriptor.configurable = true;
30861        if ("value" in descriptor) descriptor.writable = true;
30862        Object.defineProperty(target, descriptor.key, descriptor);
30863      }
30864    }
30865
30866    return function (Constructor, protoProps, staticProps) {
30867      if (protoProps) defineProperties(Constructor.prototype, protoProps);
30868      if (staticProps) defineProperties(Constructor, staticProps);
30869      return Constructor;
30870    };
30871  }();
30872
30873  var inherits$2 = function inherits$$1(subClass, superClass) {
30874    if (typeof superClass !== "function" && superClass !== null) {
30875      throw new TypeError("Super expression must either be null or a function, not " + (typeof superClass === "undefined" ? "undefined" : _typeof(superClass)));
30876    }
30877
30878    subClass.prototype = Object.create(superClass && superClass.prototype, {
30879      constructor: {
30880        value: subClass,
30881        enumerable: false,
30882        writable: true,
30883        configurable: true
30884      }
30885    });
30886    if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
30887  };
30888
30889  var possibleConstructorReturn$2 = function possibleConstructorReturn$$1(self, call) {
30890    if (!self) {
30891      throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
30892    }
30893
30894    return call && ((typeof call === "undefined" ? "undefined" : _typeof(call)) === "object" || typeof call === "function") ? call : self;
30895  };
30896
30897  /**
30898   * @file aes.js
30899   *
30900   * This file contains an adaptation of the AES decryption algorithm
30901   * from the Standford Javascript Cryptography Library. That work is
30902   * covered by the following copyright and permissions notice:
30903   *
30904   * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
30905   * All rights reserved.
30906   *
30907   * Redistribution and use in source and binary forms, with or without
30908   * modification, are permitted provided that the following conditions are
30909   * met:
30910   *
30911   * 1. Redistributions of source code must retain the above copyright
30912   *    notice, this list of conditions and the following disclaimer.
30913   *
30914   * 2. Redistributions in binary form must reproduce the above
30915   *    copyright notice, this list of conditions and the following
30916   *    disclaimer in the documentation and/or other materials provided
30917   *    with the distribution.
30918   *
30919   * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
30920   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
30921   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
30922   * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
30923   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
30924   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30925   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
30926   * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
30927   * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
30928   * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
30929   * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30930   *
30931   * The views and conclusions contained in the software and documentation
30932   * are those of the authors and should not be interpreted as representing
30933   * official policies, either expressed or implied, of the authors.
30934   */
30935
30936  /**
30937   * Expand the S-box tables.
30938   *
30939   * @private
30940   */
30941  var precompute = function precompute() {
30942    var tables = [[[], [], [], [], []], [[], [], [], [], []]];
30943    var encTable = tables[0];
30944    var decTable = tables[1];
30945    var sbox = encTable[4];
30946    var sboxInv = decTable[4];
30947    var i = void 0;
30948    var x = void 0;
30949    var xInv = void 0;
30950    var d = [];
30951    var th = [];
30952    var x2 = void 0;
30953    var x4 = void 0;
30954    var x8 = void 0;
30955    var s = void 0;
30956    var tEnc = void 0;
30957    var tDec = void 0;
30958
30959    // Compute double and third tables
30960    for (i = 0; i < 256; i++) {
30961      th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i;
30962    }
30963
30964    for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
30965      // Compute sbox
30966      s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4;
30967      s = s >> 8 ^ s & 255 ^ 99;
30968      sbox[x] = s;
30969      sboxInv[s] = x;
30970
30971      // Compute MixColumns
30972      x8 = d[x4 = d[x2 = d[x]]];
30973      tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
30974      tEnc = d[s] * 0x101 ^ s * 0x1010100;
30975
30976      for (i = 0; i < 4; i++) {
30977        encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8;
30978        decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8;
30979      }
30980    }
30981
30982    // Compactify. Considerable speedup on Firefox.
30983    for (i = 0; i < 5; i++) {
30984      encTable[i] = encTable[i].slice(0);
30985      decTable[i] = decTable[i].slice(0);
30986    }
30987    return tables;
30988  };
30989  var aesTables = null;
30990
30991  /**
30992   * Schedule out an AES key for both encryption and decryption. This
30993   * is a low-level class. Use a cipher mode to do bulk encryption.
30994   *
30995   * @class AES
30996   * @param key {Array} The key as an array of 4, 6 or 8 words.
30997   */
30998
30999  var AES = function () {
31000    function AES(key) {
31001      classCallCheck$2(this, AES);
31002
31003      /**
31004       * The expanded S-box and inverse S-box tables. These will be computed
31005       * on the client so that we don't have to send them down the wire.
31006       *
31007       * There are two tables, _tables[0] is for encryption and
31008       * _tables[1] is for decryption.
31009       *
31010       * The first 4 sub-tables are the expanded S-box with MixColumns. The
31011       * last (_tables[01][4]) is the S-box itself.
31012       *
31013       * @private
31014       */
31015      // if we have yet to precompute the S-box tables
31016      // do so now
31017      if (!aesTables) {
31018        aesTables = precompute();
31019      }
31020      // then make a copy of that object for use
31021      this._tables = [[aesTables[0][0].slice(), aesTables[0][1].slice(), aesTables[0][2].slice(), aesTables[0][3].slice(), aesTables[0][4].slice()], [aesTables[1][0].slice(), aesTables[1][1].slice(), aesTables[1][2].slice(), aesTables[1][3].slice(), aesTables[1][4].slice()]];
31022      var i = void 0;
31023      var j = void 0;
31024      var tmp = void 0;
31025      var encKey = void 0;
31026      var decKey = void 0;
31027      var sbox = this._tables[0][4];
31028      var decTable = this._tables[1];
31029      var keyLen = key.length;
31030      var rcon = 1;
31031
31032      if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
31033        throw new Error('Invalid aes key size');
31034      }
31035
31036      encKey = key.slice(0);
31037      decKey = [];
31038      this._key = [encKey, decKey];
31039
31040      // schedule encryption keys
31041      for (i = keyLen; i < 4 * keyLen + 28; i++) {
31042        tmp = encKey[i - 1];
31043
31044        // apply sbox
31045        if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) {
31046          tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255];
31047
31048          // shift rows and add rcon
31049          if (i % keyLen === 0) {
31050            tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24;
31051            rcon = rcon << 1 ^ (rcon >> 7) * 283;
31052          }
31053        }
31054
31055        encKey[i] = encKey[i - keyLen] ^ tmp;
31056      }
31057
31058      // schedule decryption keys
31059      for (j = 0; i; j++, i--) {
31060        tmp = encKey[j & 3 ? i : i - 4];
31061        if (i <= 4 || j < 4) {
31062          decKey[j] = tmp;
31063        } else {
31064          decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]];
31065        }
31066      }
31067    }
31068
31069    /**
31070     * Decrypt 16 bytes, specified as four 32-bit words.
31071     *
31072     * @param {Number} encrypted0 the first word to decrypt
31073     * @param {Number} encrypted1 the second word to decrypt
31074     * @param {Number} encrypted2 the third word to decrypt
31075     * @param {Number} encrypted3 the fourth word to decrypt
31076     * @param {Int32Array} out the array to write the decrypted words
31077     * into
31078     * @param {Number} offset the offset into the output array to start
31079     * writing results
31080     * @return {Array} The plaintext.
31081     */
31082
31083    AES.prototype.decrypt = function decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
31084      var key = this._key[1];
31085      // state variables a,b,c,d are loaded with pre-whitened data
31086      var a = encrypted0 ^ key[0];
31087      var b = encrypted3 ^ key[1];
31088      var c = encrypted2 ^ key[2];
31089      var d = encrypted1 ^ key[3];
31090      var a2 = void 0;
31091      var b2 = void 0;
31092      var c2 = void 0;
31093
31094      // key.length === 2 ?
31095      var nInnerRounds = key.length / 4 - 2;
31096      var i = void 0;
31097      var kIndex = 4;
31098      var table = this._tables[1];
31099
31100      // load up the tables
31101      var table0 = table[0];
31102      var table1 = table[1];
31103      var table2 = table[2];
31104      var table3 = table[3];
31105      var sbox = table[4];
31106
31107      // Inner rounds. Cribbed from OpenSSL.
31108      for (i = 0; i < nInnerRounds; i++) {
31109        a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex];
31110        b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1];
31111        c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2];
31112        d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3];
31113        kIndex += 4;
31114        a = a2;b = b2;c = c2;
31115      }
31116
31117      // Last round.
31118      for (i = 0; i < 4; i++) {
31119        out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++];
31120        a2 = a;a = b;b = c;c = d;d = a2;
31121      }
31122    };
31123
31124    return AES;
31125  }();
31126
31127  /**
31128   * @file stream.js
31129   */
31130  /**
31131   * A lightweight readable stream implemention that handles event dispatching.
31132   *
31133   * @class Stream
31134   */
31135  var Stream$3 = function () {
31136    function Stream() {
31137      classCallCheck$2(this, Stream);
31138
31139      this.listeners = {};
31140    }
31141
31142    /**
31143     * Add a listener for a specified event type.
31144     *
31145     * @param {String} type the event name
31146     * @param {Function} listener the callback to be invoked when an event of
31147     * the specified type occurs
31148     */
31149
31150    Stream.prototype.on = function on(type, listener) {
31151      if (!this.listeners[type]) {
31152        this.listeners[type] = [];
31153      }
31154      this.listeners[type].push(listener);
31155    };
31156
31157    /**
31158     * Remove a listener for a specified event type.
31159     *
31160     * @param {String} type the event name
31161     * @param {Function} listener  a function previously registered for this
31162     * type of event through `on`
31163     * @return {Boolean} if we could turn it off or not
31164     */
31165
31166    Stream.prototype.off = function off(type, listener) {
31167      if (!this.listeners[type]) {
31168        return false;
31169      }
31170
31171      var index = this.listeners[type].indexOf(listener);
31172
31173      this.listeners[type].splice(index, 1);
31174      return index > -1;
31175    };
31176
31177    /**
31178     * Trigger an event of the specified type on this stream. Any additional
31179     * arguments to this function are passed as parameters to event listeners.
31180     *
31181     * @param {String} type the event name
31182     */
31183
31184    Stream.prototype.trigger = function trigger(type) {
31185      var callbacks = this.listeners[type];
31186
31187      if (!callbacks) {
31188        return;
31189      }
31190
31191      // Slicing the arguments on every invocation of this method
31192      // can add a significant amount of overhead. Avoid the
31193      // intermediate object creation for the common case of a
31194      // single callback argument
31195      if (arguments.length === 2) {
31196        var length = callbacks.length;
31197
31198        for (var i = 0; i < length; ++i) {
31199          callbacks[i].call(this, arguments[1]);
31200        }
31201      } else {
31202        var args = Array.prototype.slice.call(arguments, 1);
31203        var _length = callbacks.length;
31204
31205        for (var _i = 0; _i < _length; ++_i) {
31206          callbacks[_i].apply(this, args);
31207        }
31208      }
31209    };
31210
31211    /**
31212     * Destroys the stream and cleans up.
31213     */
31214
31215    Stream.prototype.dispose = function dispose() {
31216      this.listeners = {};
31217    };
31218    /**
31219     * Forwards all `data` events on this stream to the destination stream. The
31220     * destination stream should provide a method `push` to receive the data
31221     * events as they arrive.
31222     *
31223     * @param {Stream} destination the stream that will receive all `data` events
31224     * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
31225     */
31226
31227    Stream.prototype.pipe = function pipe(destination) {
31228      this.on('data', function (data) {
31229        destination.push(data);
31230      });
31231    };
31232
31233    return Stream;
31234  }();
31235
31236  /**
31237   * @file async-stream.js
31238   */
31239  /**
31240   * A wrapper around the Stream class to use setTiemout
31241   * and run stream "jobs" Asynchronously
31242   *
31243   * @class AsyncStream
31244   * @extends Stream
31245   */
31246
31247  var AsyncStream = function (_Stream) {
31248    inherits$2(AsyncStream, _Stream);
31249
31250    function AsyncStream() {
31251      classCallCheck$2(this, AsyncStream);
31252
31253      var _this = possibleConstructorReturn$2(this, _Stream.call(this, Stream$3));
31254
31255      _this.jobs = [];
31256      _this.delay = 1;
31257      _this.timeout_ = null;
31258      return _this;
31259    }
31260
31261    /**
31262     * process an async job
31263     *
31264     * @private
31265     */
31266
31267    AsyncStream.prototype.processJob_ = function processJob_() {
31268      this.jobs.shift()();
31269      if (this.jobs.length) {
31270        this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
31271      } else {
31272        this.timeout_ = null;
31273      }
31274    };
31275
31276    /**
31277     * push a job into the stream
31278     *
31279     * @param {Function} job the job to push into the stream
31280     */
31281
31282    AsyncStream.prototype.push = function push(job) {
31283      this.jobs.push(job);
31284      if (!this.timeout_) {
31285        this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
31286      }
31287    };
31288
31289    return AsyncStream;
31290  }(Stream$3);
31291
31292  /**
31293   * @file decrypter.js
31294   *
31295   * An asynchronous implementation of AES-128 CBC decryption with
31296   * PKCS#7 padding.
31297   */
31298
31299  /**
31300   * Convert network-order (big-endian) bytes into their little-endian
31301   * representation.
31302   */
31303  var ntoh = function ntoh(word) {
31304    return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24;
31305  };
31306
31307  /**
31308   * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
31309   *
31310   * @param {Uint8Array} encrypted the encrypted bytes
31311   * @param {Uint32Array} key the bytes of the decryption key
31312   * @param {Uint32Array} initVector the initialization vector (IV) to
31313   * use for the first round of CBC.
31314   * @return {Uint8Array} the decrypted bytes
31315   *
31316   * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
31317   * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
31318   * @see https://tools.ietf.org/html/rfc2315
31319   */
31320  var decrypt = function decrypt(encrypted, key, initVector) {
31321    // word-level access to the encrypted bytes
31322    var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2);
31323
31324    var decipher = new AES(Array.prototype.slice.call(key));
31325
31326    // byte and word-level access for the decrypted output
31327    var decrypted = new Uint8Array(encrypted.byteLength);
31328    var decrypted32 = new Int32Array(decrypted.buffer);
31329
31330    // temporary variables for working with the IV, encrypted, and
31331    // decrypted data
31332    var init0 = void 0;
31333    var init1 = void 0;
31334    var init2 = void 0;
31335    var init3 = void 0;
31336    var encrypted0 = void 0;
31337    var encrypted1 = void 0;
31338    var encrypted2 = void 0;
31339    var encrypted3 = void 0;
31340
31341    // iteration variable
31342    var wordIx = void 0;
31343
31344    // pull out the words of the IV to ensure we don't modify the
31345    // passed-in reference and easier access
31346    init0 = initVector[0];
31347    init1 = initVector[1];
31348    init2 = initVector[2];
31349    init3 = initVector[3];
31350
31351    // decrypt four word sequences, applying cipher-block chaining (CBC)
31352    // to each decrypted block
31353    for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
31354      // convert big-endian (network order) words into little-endian
31355      // (javascript order)
31356      encrypted0 = ntoh(encrypted32[wordIx]);
31357      encrypted1 = ntoh(encrypted32[wordIx + 1]);
31358      encrypted2 = ntoh(encrypted32[wordIx + 2]);
31359      encrypted3 = ntoh(encrypted32[wordIx + 3]);
31360
31361      // decrypt the block
31362      decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx);
31363
31364      // XOR with the IV, and restore network byte-order to obtain the
31365      // plaintext
31366      decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
31367      decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
31368      decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
31369      decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3);
31370
31371      // setup the IV for the next round
31372      init0 = encrypted0;
31373      init1 = encrypted1;
31374      init2 = encrypted2;
31375      init3 = encrypted3;
31376    }
31377
31378    return decrypted;
31379  };
31380
31381  /**
31382   * The `Decrypter` class that manages decryption of AES
31383   * data through `AsyncStream` objects and the `decrypt`
31384   * function
31385   *
31386   * @param {Uint8Array} encrypted the encrypted bytes
31387   * @param {Uint32Array} key the bytes of the decryption key
31388   * @param {Uint32Array} initVector the initialization vector (IV) to
31389   * @param {Function} done the function to run when done
31390   * @class Decrypter
31391   */
31392
31393  var Decrypter = function () {
31394    function Decrypter(encrypted, key, initVector, done) {
31395      classCallCheck$2(this, Decrypter);
31396
31397      var step = Decrypter.STEP;
31398      var encrypted32 = new Int32Array(encrypted.buffer);
31399      var decrypted = new Uint8Array(encrypted.byteLength);
31400      var i = 0;
31401
31402      this.asyncStream_ = new AsyncStream();
31403
31404      // split up the encryption job and do the individual chunks asynchronously
31405      this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
31406      for (i = step; i < encrypted32.length; i += step) {
31407        initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]);
31408        this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
31409      }
31410      // invoke the done() callback when everything is finished
31411      this.asyncStream_.push(function () {
31412        // remove pkcs#7 padding from the decrypted bytes
31413        done(null, unpad(decrypted));
31414      });
31415    }
31416
31417    /**
31418     * a getter for step the maximum number of bytes to process at one time
31419     *
31420     * @return {Number} the value of step 32000
31421     */
31422
31423    /**
31424     * @private
31425     */
31426    Decrypter.prototype.decryptChunk_ = function decryptChunk_(encrypted, key, initVector, decrypted) {
31427      return function () {
31428        var bytes = decrypt(encrypted, key, initVector);
31429
31430        decrypted.set(bytes, encrypted.byteOffset);
31431      };
31432    };
31433
31434    createClass$1(Decrypter, null, [{
31435      key: 'STEP',
31436      get: function get$$1() {
31437        // 4 * 8000;
31438        return 32000;
31439      }
31440    }]);
31441    return Decrypter;
31442  }();
31443
31444  /**
31445   * @videojs/http-streaming
31446   * @version 1.0.2
31447   * @copyright 2018 Brightcove, Inc
31448   * @license Apache-2.0
31449   */
31450
31451  /**
31452   * @file resolve-url.js
31453   */
31454
31455  var resolveUrl$1 = function resolveUrl(baseURL, relativeURL) {
31456    // return early if we don't need to resolve
31457    if (/^[a-z]+:/i.test(relativeURL)) {
31458      return relativeURL;
31459    }
31460
31461    // if the base URL is relative then combine with the current location
31462    if (!/\/\//i.test(baseURL)) {
31463      baseURL = urlToolkit.buildAbsoluteURL(window_1.location.href, baseURL);
31464    }
31465
31466    return urlToolkit.buildAbsoluteURL(baseURL, relativeURL);
31467  };
31468
31469  var classCallCheck$3 = function classCallCheck$$1(instance, Constructor) {
31470    if (!(instance instanceof Constructor)) {
31471      throw new TypeError("Cannot call a class as a function");
31472    }
31473  };
31474
31475  var createClass$2 = function () {
31476    function defineProperties(target, props) {
31477      for (var i = 0; i < props.length; i++) {
31478        var descriptor = props[i];
31479        descriptor.enumerable = descriptor.enumerable || false;
31480        descriptor.configurable = true;
31481        if ("value" in descriptor) descriptor.writable = true;
31482        Object.defineProperty(target, descriptor.key, descriptor);
31483      }
31484    }
31485
31486    return function (Constructor, protoProps, staticProps) {
31487      if (protoProps) defineProperties(Constructor.prototype, protoProps);
31488      if (staticProps) defineProperties(Constructor, staticProps);
31489      return Constructor;
31490    };
31491  }();
31492
31493  var get$2 = function get$$1(object, property, receiver) {
31494    if (object === null) object = Function.prototype;
31495    var desc = Object.getOwnPropertyDescriptor(object, property);
31496
31497    if (desc === undefined) {
31498      var parent = Object.getPrototypeOf(object);
31499
31500      if (parent === null) {
31501        return undefined;
31502      } else {
31503        return get$$1(parent, property, receiver);
31504      }
31505    } else if ("value" in desc) {
31506      return desc.value;
31507    } else {
31508      var getter = desc.get;
31509
31510      if (getter === undefined) {
31511        return undefined;
31512      }
31513
31514      return getter.call(receiver);
31515    }
31516  };
31517
31518  var inherits$3 = function inherits$$1(subClass, superClass) {
31519    if (typeof superClass !== "function" && superClass !== null) {
31520      throw new TypeError("Super expression must either be null or a function, not " + (typeof superClass === 'undefined' ? 'undefined' : _typeof(superClass)));
31521    }
31522
31523    subClass.prototype = Object.create(superClass && superClass.prototype, {
31524      constructor: {
31525        value: subClass,
31526        enumerable: false,
31527        writable: true,
31528        configurable: true
31529      }
31530    });
31531    if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
31532  };
31533
31534  var possibleConstructorReturn$3 = function possibleConstructorReturn$$1(self, call) {
31535    if (!self) {
31536      throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
31537    }
31538
31539    return call && ((typeof call === 'undefined' ? 'undefined' : _typeof(call)) === "object" || typeof call === "function") ? call : self;
31540  };
31541
31542  var slicedToArray$1 = function () {
31543    function sliceIterator(arr, i) {
31544      var _arr = [];
31545      var _n = true;
31546      var _d = false;
31547      var _e = undefined;
31548
31549      try {
31550        for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
31551          _arr.push(_s.value);
31552
31553          if (i && _arr.length === i) break;
31554        }
31555      } catch (err) {
31556        _d = true;
31557        _e = err;
31558      } finally {
31559        try {
31560          if (!_n && _i["return"]) _i["return"]();
31561        } finally {
31562          if (_d) throw _e;
31563        }
31564      }
31565
31566      return _arr;
31567    }
31568
31569    return function (arr, i) {
31570      if (Array.isArray(arr)) {
31571        return arr;
31572      } else if (Symbol.iterator in Object(arr)) {
31573        return sliceIterator(arr, i);
31574      } else {
31575        throw new TypeError("Invalid attempt to destructure non-iterable instance");
31576      }
31577    };
31578  }();
31579
31580  /**
31581   * @file playlist-loader.js
31582   *
31583   * A state machine that manages the loading, caching, and updating of
31584   * M3U8 playlists.
31585   *
31586   */
31587
31588  var mergeOptions$1 = videojs$1.mergeOptions,
31589      EventTarget$1 = videojs$1.EventTarget,
31590      log$2 = videojs$1.log;
31591
31592  /**
31593   * Loops through all supported media groups in master and calls the provided
31594   * callback for each group
31595   *
31596   * @param {Object} master
31597   *        The parsed master manifest object
31598   * @param {Function} callback
31599   *        Callback to call for each media group
31600   */
31601
31602  var forEachMediaGroup = function forEachMediaGroup(master, callback) {
31603    ['AUDIO', 'SUBTITLES'].forEach(function (mediaType) {
31604      for (var groupKey in master.mediaGroups[mediaType]) {
31605        for (var labelKey in master.mediaGroups[mediaType][groupKey]) {
31606          var mediaProperties = master.mediaGroups[mediaType][groupKey][labelKey];
31607
31608          callback(mediaProperties, mediaType, groupKey, labelKey);
31609        }
31610      }
31611    });
31612  };
31613
31614  /**
31615    * Returns a new array of segments that is the result of merging
31616    * properties from an older list of segments onto an updated
31617    * list. No properties on the updated playlist will be overridden.
31618    *
31619    * @param {Array} original the outdated list of segments
31620    * @param {Array} update the updated list of segments
31621    * @param {Number=} offset the index of the first update
31622    * segment in the original segment list. For non-live playlists,
31623    * this should always be zero and does not need to be
31624    * specified. For live playlists, it should be the difference
31625    * between the media sequence numbers in the original and updated
31626    * playlists.
31627    * @return a list of merged segment objects
31628    */
31629  var updateSegments = function updateSegments(original, update, offset) {
31630    var result = update.slice();
31631
31632    offset = offset || 0;
31633    var length = Math.min(original.length, update.length + offset);
31634
31635    for (var i = offset; i < length; i++) {
31636      result[i - offset] = mergeOptions$1(original[i], result[i - offset]);
31637    }
31638    return result;
31639  };
31640
31641  var resolveSegmentUris = function resolveSegmentUris(segment, baseUri) {
31642    if (!segment.resolvedUri) {
31643      segment.resolvedUri = resolveUrl$1(baseUri, segment.uri);
31644    }
31645    if (segment.key && !segment.key.resolvedUri) {
31646      segment.key.resolvedUri = resolveUrl$1(baseUri, segment.key.uri);
31647    }
31648    if (segment.map && !segment.map.resolvedUri) {
31649      segment.map.resolvedUri = resolveUrl$1(baseUri, segment.map.uri);
31650    }
31651  };
31652
31653  /**
31654    * Returns a new master playlist that is the result of merging an
31655    * updated media playlist into the original version. If the
31656    * updated media playlist does not match any of the playlist
31657    * entries in the original master playlist, null is returned.
31658    *
31659    * @param {Object} master a parsed master M3U8 object
31660    * @param {Object} media a parsed media M3U8 object
31661    * @return {Object} a new object that represents the original
31662    * master playlist with the updated media playlist merged in, or
31663    * null if the merge produced no change.
31664    */
31665  var updateMaster = function updateMaster(master, media) {
31666    var result = mergeOptions$1(master, {});
31667    var playlist = result.playlists[media.uri];
31668
31669    if (!playlist) {
31670      return null;
31671    }
31672
31673    // consider the playlist unchanged if the number of segments is equal and the media
31674    // sequence number is unchanged
31675    if (playlist.segments && media.segments && playlist.segments.length === media.segments.length && playlist.mediaSequence === media.mediaSequence) {
31676      return null;
31677    }
31678
31679    var mergedPlaylist = mergeOptions$1(playlist, media);
31680
31681    // if the update could overlap existing segment information, merge the two segment lists
31682    if (playlist.segments) {
31683      mergedPlaylist.segments = updateSegments(playlist.segments, media.segments, media.mediaSequence - playlist.mediaSequence);
31684    }
31685
31686    // resolve any segment URIs to prevent us from having to do it later
31687    mergedPlaylist.segments.forEach(function (segment) {
31688      resolveSegmentUris(segment, mergedPlaylist.resolvedUri);
31689    });
31690
31691    // TODO Right now in the playlists array there are two references to each playlist, one
31692    // that is referenced by index, and one by URI. The index reference may no longer be
31693    // necessary.
31694    for (var i = 0; i < result.playlists.length; i++) {
31695      if (result.playlists[i].uri === media.uri) {
31696        result.playlists[i] = mergedPlaylist;
31697      }
31698    }
31699    result.playlists[media.uri] = mergedPlaylist;
31700
31701    return result;
31702  };
31703
31704  var setupMediaPlaylists = function setupMediaPlaylists(master) {
31705    // setup by-URI lookups and resolve media playlist URIs
31706    var i = master.playlists.length;
31707
31708    while (i--) {
31709      var playlist = master.playlists[i];
31710
31711      master.playlists[playlist.uri] = playlist;
31712      playlist.resolvedUri = resolveUrl$1(master.uri, playlist.uri);
31713      playlist.id = i;
31714
31715      if (!playlist.attributes) {
31716        // Although the spec states an #EXT-X-STREAM-INF tag MUST have a
31717        // BANDWIDTH attribute, we can play the stream without it. This means a poorly
31718        // formatted master playlist may not have an attribute list. An attributes
31719        // property is added here to prevent undefined references when we encounter
31720        // this scenario.
31721        playlist.attributes = {};
31722
31723        log$2.warn('Invalid playlist STREAM-INF detected. Missing BANDWIDTH attribute.');
31724      }
31725    }
31726  };
31727
31728  var resolveMediaGroupUris = function resolveMediaGroupUris(master) {
31729    forEachMediaGroup(master, function (properties) {
31730      if (properties.uri) {
31731        properties.resolvedUri = resolveUrl$1(master.uri, properties.uri);
31732      }
31733    });
31734  };
31735
31736  /**
31737   * Calculates the time to wait before refreshing a live playlist
31738   *
31739   * @param {Object} media
31740   *        The current media
31741   * @param {Boolean} update
31742   *        True if there were any updates from the last refresh, false otherwise
31743   * @return {Number}
31744   *         The time in ms to wait before refreshing the live playlist
31745   */
31746  var refreshDelay = function refreshDelay(media, update) {
31747    var lastSegment = media.segments[media.segments.length - 1];
31748    var delay = void 0;
31749
31750    if (update && lastSegment && lastSegment.duration) {
31751      delay = lastSegment.duration * 1000;
31752    } else {
31753      // if the playlist is unchanged since the last reload or last segment duration
31754      // cannot be determined, try again after half the target duration
31755      delay = (media.targetDuration || 10) * 500;
31756    }
31757    return delay;
31758  };
31759
31760  /**
31761   * Load a playlist from a remote location
31762   *
31763   * @class PlaylistLoader
31764   * @extends Stream
31765   * @param {String} srcUrl the url to start with
31766   * @param {Boolean} withCredentials the withCredentials xhr option
31767   * @constructor
31768   */
31769
31770  var PlaylistLoader = function (_EventTarget) {
31771    inherits$3(PlaylistLoader, _EventTarget);
31772
31773    function PlaylistLoader(srcUrl, hls, withCredentials) {
31774      classCallCheck$3(this, PlaylistLoader);
31775
31776      var _this = possibleConstructorReturn$3(this, (PlaylistLoader.__proto__ || Object.getPrototypeOf(PlaylistLoader)).call(this));
31777
31778      _this.srcUrl = srcUrl;
31779      _this.hls_ = hls;
31780      _this.withCredentials = withCredentials;
31781
31782      if (!_this.srcUrl) {
31783        throw new Error('A non-empty playlist URL is required');
31784      }
31785
31786      // initialize the loader state
31787      _this.state = 'HAVE_NOTHING';
31788
31789      // live playlist staleness timeout
31790      _this.on('mediaupdatetimeout', function () {
31791        if (_this.state !== 'HAVE_METADATA') {
31792          // only refresh the media playlist if no other activity is going on
31793          return;
31794        }
31795
31796        _this.state = 'HAVE_CURRENT_METADATA';
31797
31798        _this.request = _this.hls_.xhr({
31799          uri: resolveUrl$1(_this.master.uri, _this.media().uri),
31800          withCredentials: _this.withCredentials
31801        }, function (error, req) {
31802          // disposed
31803          if (!_this.request) {
31804            return;
31805          }
31806
31807          if (error) {
31808            return _this.playlistRequestError(_this.request, _this.media().uri, 'HAVE_METADATA');
31809          }
31810
31811          _this.haveMetadata(_this.request, _this.media().uri);
31812        });
31813      });
31814      return _this;
31815    }
31816
31817    createClass$2(PlaylistLoader, [{
31818      key: 'playlistRequestError',
31819      value: function playlistRequestError(xhr, url, startingState) {
31820        // any in-flight request is now finished
31821        this.request = null;
31822
31823        if (startingState) {
31824          this.state = startingState;
31825        }
31826
31827        this.error = {
31828          playlist: this.master.playlists[url],
31829          status: xhr.status,
31830          message: 'HLS playlist request error at URL: ' + url,
31831          responseText: xhr.responseText,
31832          code: xhr.status >= 500 ? 4 : 2
31833        };
31834
31835        this.trigger('error');
31836      }
31837
31838      // update the playlist loader's state in response to a new or
31839      // updated playlist.
31840
31841    }, {
31842      key: 'haveMetadata',
31843      value: function haveMetadata(xhr, url) {
31844        var _this2 = this;
31845
31846        // any in-flight request is now finished
31847        this.request = null;
31848        this.state = 'HAVE_METADATA';
31849
31850        var parser = new Parser();
31851
31852        parser.push(xhr.responseText);
31853        parser.end();
31854        parser.manifest.uri = url;
31855        // m3u8-parser does not attach an attributes property to media playlists so make
31856        // sure that the property is attached to avoid undefined reference errors
31857        parser.manifest.attributes = parser.manifest.attributes || {};
31858
31859        // merge this playlist into the master
31860        var update = updateMaster(this.master, parser.manifest);
31861
31862        this.targetDuration = parser.manifest.targetDuration;
31863
31864        if (update) {
31865          this.master = update;
31866          this.media_ = this.master.playlists[parser.manifest.uri];
31867        } else {
31868          this.trigger('playlistunchanged');
31869        }
31870
31871        // refresh live playlists after a target duration passes
31872        if (!this.media().endList) {
31873          window_1.clearTimeout(this.mediaUpdateTimeout);
31874          this.mediaUpdateTimeout = window_1.setTimeout(function () {
31875            _this2.trigger('mediaupdatetimeout');
31876          }, refreshDelay(this.media(), !!update));
31877        }
31878
31879        this.trigger('loadedplaylist');
31880      }
31881
31882      /**
31883       * Abort any outstanding work and clean up.
31884       */
31885
31886    }, {
31887      key: 'dispose',
31888      value: function dispose() {
31889        this.stopRequest();
31890        window_1.clearTimeout(this.mediaUpdateTimeout);
31891      }
31892    }, {
31893      key: 'stopRequest',
31894      value: function stopRequest() {
31895        if (this.request) {
31896          var oldRequest = this.request;
31897
31898          this.request = null;
31899          oldRequest.onreadystatechange = null;
31900          oldRequest.abort();
31901        }
31902      }
31903
31904      /**
31905       * When called without any arguments, returns the currently
31906       * active media playlist. When called with a single argument,
31907       * triggers the playlist loader to asynchronously switch to the
31908       * specified media playlist. Calling this method while the
31909       * loader is in the HAVE_NOTHING causes an error to be emitted
31910       * but otherwise has no effect.
31911       *
31912       * @param {Object=} playlist the parsed media playlist
31913       * object to switch to
31914       * @return {Playlist} the current loaded media
31915       */
31916
31917    }, {
31918      key: 'media',
31919      value: function media(playlist) {
31920        var _this3 = this;
31921
31922        // getter
31923        if (!playlist) {
31924          return this.media_;
31925        }
31926
31927        // setter
31928        if (this.state === 'HAVE_NOTHING') {
31929          throw new Error('Cannot switch media playlist from ' + this.state);
31930        }
31931
31932        var startingState = this.state;
31933
31934        // find the playlist object if the target playlist has been
31935        // specified by URI
31936        if (typeof playlist === 'string') {
31937          if (!this.master.playlists[playlist]) {
31938            throw new Error('Unknown playlist URI: ' + playlist);
31939          }
31940          playlist = this.master.playlists[playlist];
31941        }
31942
31943        var mediaChange = !this.media_ || playlist.uri !== this.media_.uri;
31944
31945        // switch to fully loaded playlists immediately
31946        if (this.master.playlists[playlist.uri].endList) {
31947          // abort outstanding playlist requests
31948          if (this.request) {
31949            this.request.onreadystatechange = null;
31950            this.request.abort();
31951            this.request = null;
31952          }
31953          this.state = 'HAVE_METADATA';
31954          this.media_ = playlist;
31955
31956          // trigger media change if the active media has been updated
31957          if (mediaChange) {
31958            this.trigger('mediachanging');
31959            this.trigger('mediachange');
31960          }
31961          return;
31962        }
31963
31964        // switching to the active playlist is a no-op
31965        if (!mediaChange) {
31966          return;
31967        }
31968
31969        this.state = 'SWITCHING_MEDIA';
31970
31971        // there is already an outstanding playlist request
31972        if (this.request) {
31973          if (resolveUrl$1(this.master.uri, playlist.uri) === this.request.url) {
31974            // requesting to switch to the same playlist multiple times
31975            // has no effect after the first
31976            return;
31977          }
31978          this.request.onreadystatechange = null;
31979          this.request.abort();
31980          this.request = null;
31981        }
31982
31983        // request the new playlist
31984        if (this.media_) {
31985          this.trigger('mediachanging');
31986        }
31987
31988        this.request = this.hls_.xhr({
31989          uri: resolveUrl$1(this.master.uri, playlist.uri),
31990          withCredentials: this.withCredentials
31991        }, function (error, req) {
31992          // disposed
31993          if (!_this3.request) {
31994            return;
31995          }
31996
31997          if (error) {
31998            return _this3.playlistRequestError(_this3.request, playlist.uri, startingState);
31999          }
32000
32001          _this3.haveMetadata(req, playlist.uri);
32002
32003          // fire loadedmetadata the first time a media playlist is loaded
32004          if (startingState === 'HAVE_MASTER') {
32005            _this3.trigger('loadedmetadata');
32006          } else {
32007            _this3.trigger('mediachange');
32008          }
32009        });
32010      }
32011
32012      /**
32013       * pause loading of the playlist
32014       */
32015
32016    }, {
32017      key: 'pause',
32018      value: function pause() {
32019        this.stopRequest();
32020        window_1.clearTimeout(this.mediaUpdateTimeout);
32021        if (this.state === 'HAVE_NOTHING') {
32022          // If we pause the loader before any data has been retrieved, its as if we never
32023          // started, so reset to an unstarted state.
32024          this.started = false;
32025        }
32026        // Need to restore state now that no activity is happening
32027        if (this.state === 'SWITCHING_MEDIA') {
32028          // if the loader was in the process of switching media, it should either return to
32029          // HAVE_MASTER or HAVE_METADATA depending on if the loader has loaded a media
32030          // playlist yet. This is determined by the existence of loader.media_
32031          if (this.media_) {
32032            this.state = 'HAVE_METADATA';
32033          } else {
32034            this.state = 'HAVE_MASTER';
32035          }
32036        } else if (this.state === 'HAVE_CURRENT_METADATA') {
32037          this.state = 'HAVE_METADATA';
32038        }
32039      }
32040
32041      /**
32042       * start loading of the playlist
32043       */
32044
32045    }, {
32046      key: 'load',
32047      value: function load(isFinalRendition) {
32048        var _this4 = this;
32049
32050        window_1.clearTimeout(this.mediaUpdateTimeout);
32051
32052        var media = this.media();
32053
32054        if (isFinalRendition) {
32055          var delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000;
32056
32057          this.mediaUpdateTimeout = window_1.setTimeout(function () {
32058            return _this4.load();
32059          }, delay);
32060          return;
32061        }
32062
32063        if (!this.started) {
32064          this.start();
32065          return;
32066        }
32067
32068        if (media && !media.endList) {
32069          this.trigger('mediaupdatetimeout');
32070        } else {
32071          this.trigger('loadedplaylist');
32072        }
32073      }
32074
32075      /**
32076       * start loading of the playlist
32077       */
32078
32079    }, {
32080      key: 'start',
32081      value: function start() {
32082        var _this5 = this;
32083
32084        this.started = true;
32085
32086        // request the specified URL
32087        this.request = this.hls_.xhr({
32088          uri: this.srcUrl,
32089          withCredentials: this.withCredentials
32090        }, function (error, req) {
32091          // disposed
32092          if (!_this5.request) {
32093            return;
32094          }
32095
32096          // clear the loader's request reference
32097          _this5.request = null;
32098
32099          if (error) {
32100            _this5.error = {
32101              status: req.status,
32102              message: 'HLS playlist request error at URL: ' + _this5.srcUrl,
32103              responseText: req.responseText,
32104              // MEDIA_ERR_NETWORK
32105              code: 2
32106            };
32107            if (_this5.state === 'HAVE_NOTHING') {
32108              _this5.started = false;
32109            }
32110            return _this5.trigger('error');
32111          }
32112
32113          var parser = new Parser();
32114
32115          parser.push(req.responseText);
32116          parser.end();
32117
32118          _this5.state = 'HAVE_MASTER';
32119
32120          parser.manifest.uri = _this5.srcUrl;
32121
32122          // loaded a master playlist
32123          if (parser.manifest.playlists) {
32124            _this5.master = parser.manifest;
32125
32126            setupMediaPlaylists(_this5.master);
32127            resolveMediaGroupUris(_this5.master);
32128
32129            _this5.trigger('loadedplaylist');
32130            if (!_this5.request) {
32131              // no media playlist was specifically selected so start
32132              // from the first listed one
32133              _this5.media(parser.manifest.playlists[0]);
32134            }
32135            return;
32136          }
32137
32138          // loaded a media playlist
32139          // infer a master playlist if none was previously requested
32140          _this5.master = {
32141            mediaGroups: {
32142              'AUDIO': {},
32143              'VIDEO': {},
32144              'CLOSED-CAPTIONS': {},
32145              'SUBTITLES': {}
32146            },
32147            uri: window_1.location.href,
32148            playlists: [{
32149              uri: _this5.srcUrl,
32150              id: 0
32151            }]
32152          };
32153          _this5.master.playlists[_this5.srcUrl] = _this5.master.playlists[0];
32154          _this5.master.playlists[0].resolvedUri = _this5.srcUrl;
32155          // m3u8-parser does not attach an attributes property to media playlists so make
32156          // sure that the property is attached to avoid undefined reference errors
32157          _this5.master.playlists[0].attributes = _this5.master.playlists[0].attributes || {};
32158          _this5.haveMetadata(req, _this5.srcUrl);
32159          return _this5.trigger('loadedmetadata');
32160        });
32161      }
32162    }]);
32163    return PlaylistLoader;
32164  }(EventTarget$1);
32165
32166  /**
32167   * @file playlist.js
32168   *
32169   * Playlist related utilities.
32170   */
32171
32172  var createTimeRange = videojs$1.createTimeRange;
32173
32174  /**
32175   * walk backward until we find a duration we can use
32176   * or return a failure
32177   *
32178   * @param {Playlist} playlist the playlist to walk through
32179   * @param {Number} endSequence the mediaSequence to stop walking on
32180   */
32181
32182  var backwardDuration = function backwardDuration(playlist, endSequence) {
32183    var result = 0;
32184    var i = endSequence - playlist.mediaSequence;
32185    // if a start time is available for segment immediately following
32186    // the interval, use it
32187    var segment = playlist.segments[i];
32188
32189    // Walk backward until we find the latest segment with timeline
32190    // information that is earlier than endSequence
32191    if (segment) {
32192      if (typeof segment.start !== 'undefined') {
32193        return { result: segment.start, precise: true };
32194      }
32195      if (typeof segment.end !== 'undefined') {
32196        return {
32197          result: segment.end - segment.duration,
32198          precise: true
32199        };
32200      }
32201    }
32202    while (i--) {
32203      segment = playlist.segments[i];
32204      if (typeof segment.end !== 'undefined') {
32205        return { result: result + segment.end, precise: true };
32206      }
32207
32208      result += segment.duration;
32209
32210      if (typeof segment.start !== 'undefined') {
32211        return { result: result + segment.start, precise: true };
32212      }
32213    }
32214    return { result: result, precise: false };
32215  };
32216
32217  /**
32218   * walk forward until we find a duration we can use
32219   * or return a failure
32220   *
32221   * @param {Playlist} playlist the playlist to walk through
32222   * @param {Number} endSequence the mediaSequence to stop walking on
32223   */
32224  var forwardDuration = function forwardDuration(playlist, endSequence) {
32225    var result = 0;
32226    var segment = void 0;
32227    var i = endSequence - playlist.mediaSequence;
32228    // Walk forward until we find the earliest segment with timeline
32229    // information
32230
32231    for (; i < playlist.segments.length; i++) {
32232      segment = playlist.segments[i];
32233      if (typeof segment.start !== 'undefined') {
32234        return {
32235          result: segment.start - result,
32236          precise: true
32237        };
32238      }
32239
32240      result += segment.duration;
32241
32242      if (typeof segment.end !== 'undefined') {
32243        return {
32244          result: segment.end - result,
32245          precise: true
32246        };
32247      }
32248    }
32249    // indicate we didn't find a useful duration estimate
32250    return { result: -1, precise: false };
32251  };
32252
32253  /**
32254    * Calculate the media duration from the segments associated with a
32255    * playlist. The duration of a subinterval of the available segments
32256    * may be calculated by specifying an end index.
32257    *
32258    * @param {Object} playlist a media playlist object
32259    * @param {Number=} endSequence an exclusive upper boundary
32260    * for the playlist.  Defaults to playlist length.
32261    * @param {Number} expired the amount of time that has dropped
32262    * off the front of the playlist in a live scenario
32263    * @return {Number} the duration between the first available segment
32264    * and end index.
32265    */
32266  var intervalDuration = function intervalDuration(playlist, endSequence, expired) {
32267    var backward = void 0;
32268    var forward = void 0;
32269
32270    if (typeof endSequence === 'undefined') {
32271      endSequence = playlist.mediaSequence + playlist.segments.length;
32272    }
32273
32274    if (endSequence < playlist.mediaSequence) {
32275      return 0;
32276    }
32277
32278    // do a backward walk to estimate the duration
32279    backward = backwardDuration(playlist, endSequence);
32280    if (backward.precise) {
32281      // if we were able to base our duration estimate on timing
32282      // information provided directly from the Media Source, return
32283      // it
32284      return backward.result;
32285    }
32286
32287    // walk forward to see if a precise duration estimate can be made
32288    // that way
32289    forward = forwardDuration(playlist, endSequence);
32290    if (forward.precise) {
32291      // we found a segment that has been buffered and so it's
32292      // position is known precisely
32293      return forward.result;
32294    }
32295
32296    // return the less-precise, playlist-based duration estimate
32297    return backward.result + expired;
32298  };
32299
32300  /**
32301    * Calculates the duration of a playlist. If a start and end index
32302    * are specified, the duration will be for the subset of the media
32303    * timeline between those two indices. The total duration for live
32304    * playlists is always Infinity.
32305    *
32306    * @param {Object} playlist a media playlist object
32307    * @param {Number=} endSequence an exclusive upper
32308    * boundary for the playlist. Defaults to the playlist media
32309    * sequence number plus its length.
32310    * @param {Number=} expired the amount of time that has
32311    * dropped off the front of the playlist in a live scenario
32312    * @return {Number} the duration between the start index and end
32313    * index.
32314    */
32315  var duration = function duration(playlist, endSequence, expired) {
32316    if (!playlist) {
32317      return 0;
32318    }
32319
32320    if (typeof expired !== 'number') {
32321      expired = 0;
32322    }
32323
32324    // if a slice of the total duration is not requested, use
32325    // playlist-level duration indicators when they're present
32326    if (typeof endSequence === 'undefined') {
32327      // if present, use the duration specified in the playlist
32328      if (playlist.totalDuration) {
32329        return playlist.totalDuration;
32330      }
32331
32332      // duration should be Infinity for live playlists
32333      if (!playlist.endList) {
32334        return window_1.Infinity;
32335      }
32336    }
32337
32338    // calculate the total duration based on the segment durations
32339    return intervalDuration(playlist, endSequence, expired);
32340  };
32341
32342  /**
32343    * Calculate the time between two indexes in the current playlist
32344    * neight the start- nor the end-index need to be within the current
32345    * playlist in which case, the targetDuration of the playlist is used
32346    * to approximate the durations of the segments
32347    *
32348    * @param {Object} playlist a media playlist object
32349    * @param {Number} startIndex
32350    * @param {Number} endIndex
32351    * @return {Number} the number of seconds between startIndex and endIndex
32352    */
32353  var sumDurations = function sumDurations(playlist, startIndex, endIndex) {
32354    var durations = 0;
32355
32356    if (startIndex > endIndex) {
32357      var _ref = [endIndex, startIndex];
32358      startIndex = _ref[0];
32359      endIndex = _ref[1];
32360    }
32361
32362    if (startIndex < 0) {
32363      for (var i = startIndex; i < Math.min(0, endIndex); i++) {
32364        durations += playlist.targetDuration;
32365      }
32366      startIndex = 0;
32367    }
32368
32369    for (var _i = startIndex; _i < endIndex; _i++) {
32370      durations += playlist.segments[_i].duration;
32371    }
32372
32373    return durations;
32374  };
32375
32376  /**
32377   * Determines the media index of the segment corresponding to the safe edge of the live
32378   * window which is the duration of the last segment plus 2 target durations from the end
32379   * of the playlist.
32380   *
32381   * @param {Object} playlist
32382   *        a media playlist object
32383   * @return {Number}
32384   *         The media index of the segment at the safe live point. 0 if there is no "safe"
32385   *         point.
32386   * @function safeLiveIndex
32387   */
32388  var safeLiveIndex = function safeLiveIndex(playlist) {
32389    if (!playlist.segments.length) {
32390      return 0;
32391    }
32392
32393    var i = playlist.segments.length - 1;
32394    var distanceFromEnd = playlist.segments[i].duration || playlist.targetDuration;
32395    var safeDistance = distanceFromEnd + playlist.targetDuration * 2;
32396
32397    while (i--) {
32398      distanceFromEnd += playlist.segments[i].duration;
32399
32400      if (distanceFromEnd >= safeDistance) {
32401        break;
32402      }
32403    }
32404
32405    return Math.max(0, i);
32406  };
32407
32408  /**
32409   * Calculates the playlist end time
32410   *
32411   * @param {Object} playlist a media playlist object
32412   * @param {Number=} expired the amount of time that has
32413   *                  dropped off the front of the playlist in a live scenario
32414   * @param {Boolean|false} useSafeLiveEnd a boolean value indicating whether or not the
32415   *                        playlist end calculation should consider the safe live end
32416   *                        (truncate the playlist end by three segments). This is normally
32417   *                        used for calculating the end of the playlist's seekable range.
32418   * @returns {Number} the end time of playlist
32419   * @function playlistEnd
32420   */
32421  var playlistEnd = function playlistEnd(playlist, expired, useSafeLiveEnd) {
32422    if (!playlist || !playlist.segments) {
32423      return null;
32424    }
32425    if (playlist.endList) {
32426      return duration(playlist);
32427    }
32428
32429    if (expired === null) {
32430      return null;
32431    }
32432
32433    expired = expired || 0;
32434
32435    var endSequence = useSafeLiveEnd ? safeLiveIndex(playlist) : playlist.segments.length;
32436
32437    return intervalDuration(playlist, playlist.mediaSequence + endSequence, expired);
32438  };
32439
32440  /**
32441    * Calculates the interval of time that is currently seekable in a
32442    * playlist. The returned time ranges are relative to the earliest
32443    * moment in the specified playlist that is still available. A full
32444    * seekable implementation for live streams would need to offset
32445    * these values by the duration of content that has expired from the
32446    * stream.
32447    *
32448    * @param {Object} playlist a media playlist object
32449    * dropped off the front of the playlist in a live scenario
32450    * @param {Number=} expired the amount of time that has
32451    * dropped off the front of the playlist in a live scenario
32452    * @return {TimeRanges} the periods of time that are valid targets
32453    * for seeking
32454    */
32455  var seekable = function seekable(playlist, expired) {
32456    var useSafeLiveEnd = true;
32457    var seekableStart = expired || 0;
32458    var seekableEnd = playlistEnd(playlist, expired, useSafeLiveEnd);
32459
32460    if (seekableEnd === null) {
32461      return createTimeRange();
32462    }
32463    return createTimeRange(seekableStart, seekableEnd);
32464  };
32465
32466  var isWholeNumber = function isWholeNumber(num) {
32467    return num - Math.floor(num) === 0;
32468  };
32469
32470  var roundSignificantDigit = function roundSignificantDigit(increment, num) {
32471    // If we have a whole number, just add 1 to it
32472    if (isWholeNumber(num)) {
32473      return num + increment * 0.1;
32474    }
32475
32476    var numDecimalDigits = num.toString().split('.')[1].length;
32477
32478    for (var i = 1; i <= numDecimalDigits; i++) {
32479      var scale = Math.pow(10, i);
32480      var temp = num * scale;
32481
32482      if (isWholeNumber(temp) || i === numDecimalDigits) {
32483        return (temp + increment) / scale;
32484      }
32485    }
32486  };
32487
32488  var ceilLeastSignificantDigit = roundSignificantDigit.bind(null, 1);
32489  var floorLeastSignificantDigit = roundSignificantDigit.bind(null, -1);
32490
32491  /**
32492   * Determine the index and estimated starting time of the segment that
32493   * contains a specified playback position in a media playlist.
32494   *
32495   * @param {Object} playlist the media playlist to query
32496   * @param {Number} currentTime The number of seconds since the earliest
32497   * possible position to determine the containing segment for
32498   * @param {Number} startIndex
32499   * @param {Number} startTime
32500   * @return {Object}
32501   */
32502  var getMediaInfoForTime = function getMediaInfoForTime(playlist, currentTime, startIndex, startTime) {
32503    var i = void 0;
32504    var segment = void 0;
32505    var numSegments = playlist.segments.length;
32506
32507    var time = currentTime - startTime;
32508
32509    if (time < 0) {
32510      // Walk backward from startIndex in the playlist, adding durations
32511      // until we find a segment that contains `time` and return it
32512      if (startIndex > 0) {
32513        for (i = startIndex - 1; i >= 0; i--) {
32514          segment = playlist.segments[i];
32515          time += floorLeastSignificantDigit(segment.duration);
32516          if (time > 0) {
32517            return {
32518              mediaIndex: i,
32519              startTime: startTime - sumDurations(playlist, startIndex, i)
32520            };
32521          }
32522        }
32523      }
32524      // We were unable to find a good segment within the playlist
32525      // so select the first segment
32526      return {
32527        mediaIndex: 0,
32528        startTime: currentTime
32529      };
32530    }
32531
32532    // When startIndex is negative, we first walk forward to first segment
32533    // adding target durations. If we "run out of time" before getting to
32534    // the first segment, return the first segment
32535    if (startIndex < 0) {
32536      for (i = startIndex; i < 0; i++) {
32537        time -= playlist.targetDuration;
32538        if (time < 0) {
32539          return {
32540            mediaIndex: 0,
32541            startTime: currentTime
32542          };
32543        }
32544      }
32545      startIndex = 0;
32546    }
32547
32548    // Walk forward from startIndex in the playlist, subtracting durations
32549    // until we find a segment that contains `time` and return it
32550    for (i = startIndex; i < numSegments; i++) {
32551      segment = playlist.segments[i];
32552      time -= ceilLeastSignificantDigit(segment.duration);
32553      if (time < 0) {
32554        return {
32555          mediaIndex: i,
32556          startTime: startTime + sumDurations(playlist, startIndex, i)
32557        };
32558      }
32559    }
32560
32561    // We are out of possible candidates so load the last one...
32562    return {
32563      mediaIndex: numSegments - 1,
32564      startTime: currentTime
32565    };
32566  };
32567
32568  /**
32569   * Check whether the playlist is blacklisted or not.
32570   *
32571   * @param {Object} playlist the media playlist object
32572   * @return {boolean} whether the playlist is blacklisted or not
32573   * @function isBlacklisted
32574   */
32575  var isBlacklisted = function isBlacklisted(playlist) {
32576    return playlist.excludeUntil && playlist.excludeUntil > Date.now();
32577  };
32578
32579  /**
32580   * Check whether the playlist is compatible with current playback configuration or has
32581   * been blacklisted permanently for being incompatible.
32582   *
32583   * @param {Object} playlist the media playlist object
32584   * @return {boolean} whether the playlist is incompatible or not
32585   * @function isIncompatible
32586   */
32587  var isIncompatible = function isIncompatible(playlist) {
32588    return playlist.excludeUntil && playlist.excludeUntil === Infinity;
32589  };
32590
32591  /**
32592   * Check whether the playlist is enabled or not.
32593   *
32594   * @param {Object} playlist the media playlist object
32595   * @return {boolean} whether the playlist is enabled or not
32596   * @function isEnabled
32597   */
32598  var isEnabled = function isEnabled(playlist) {
32599    var blacklisted = isBlacklisted(playlist);
32600
32601    return !playlist.disabled && !blacklisted;
32602  };
32603
32604  /**
32605   * Check whether the playlist has been manually disabled through the representations api.
32606   *
32607   * @param {Object} playlist the media playlist object
32608   * @return {boolean} whether the playlist is disabled manually or not
32609   * @function isDisabled
32610   */
32611  var isDisabled = function isDisabled(playlist) {
32612    return playlist.disabled;
32613  };
32614
32615  /**
32616   * Returns whether the current playlist is an AES encrypted HLS stream
32617   *
32618   * @return {Boolean} true if it's an AES encrypted HLS stream
32619   */
32620  var isAes = function isAes(media) {
32621    for (var i = 0; i < media.segments.length; i++) {
32622      if (media.segments[i].key) {
32623        return true;
32624      }
32625    }
32626    return false;
32627  };
32628
32629  /**
32630   * Returns whether the current playlist contains fMP4
32631   *
32632   * @return {Boolean} true if the playlist contains fMP4
32633   */
32634  var isFmp4 = function isFmp4(media) {
32635    for (var i = 0; i < media.segments.length; i++) {
32636      if (media.segments[i].map) {
32637        return true;
32638      }
32639    }
32640    return false;
32641  };
32642
32643  /**
32644   * Checks if the playlist has a value for the specified attribute
32645   *
32646   * @param {String} attr
32647   *        Attribute to check for
32648   * @param {Object} playlist
32649   *        The media playlist object
32650   * @return {Boolean}
32651   *         Whether the playlist contains a value for the attribute or not
32652   * @function hasAttribute
32653   */
32654  var hasAttribute = function hasAttribute(attr, playlist) {
32655    return playlist.attributes && playlist.attributes[attr];
32656  };
32657
32658  /**
32659   * Estimates the time required to complete a segment download from the specified playlist
32660   *
32661   * @param {Number} segmentDuration
32662   *        Duration of requested segment
32663   * @param {Number} bandwidth
32664   *        Current measured bandwidth of the player
32665   * @param {Object} playlist
32666   *        The media playlist object
32667   * @param {Number=} bytesReceived
32668   *        Number of bytes already received for the request. Defaults to 0
32669   * @return {Number|NaN}
32670   *         The estimated time to request the segment. NaN if bandwidth information for
32671   *         the given playlist is unavailable
32672   * @function estimateSegmentRequestTime
32673   */
32674  var estimateSegmentRequestTime = function estimateSegmentRequestTime(segmentDuration, bandwidth, playlist) {
32675    var bytesReceived = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
32676
32677    if (!hasAttribute('BANDWIDTH', playlist)) {
32678      return NaN;
32679    }
32680
32681    var size = segmentDuration * playlist.attributes.BANDWIDTH;
32682
32683    return (size - bytesReceived * 8) / bandwidth;
32684  };
32685
32686  /*
32687   * Returns whether the current playlist is the lowest rendition
32688   *
32689   * @return {Boolean} true if on lowest rendition
32690   */
32691  var isLowestEnabledRendition = function isLowestEnabledRendition(master, media) {
32692    if (master.playlists.length === 1) {
32693      return true;
32694    }
32695
32696    var currentBandwidth = media.attributes.BANDWIDTH || Number.MAX_VALUE;
32697
32698    return master.playlists.filter(function (playlist) {
32699      if (!isEnabled(playlist)) {
32700        return false;
32701      }
32702
32703      return (playlist.attributes.BANDWIDTH || 0) < currentBandwidth;
32704    }).length === 0;
32705  };
32706
32707  // exports
32708  var Playlist = {
32709    duration: duration,
32710    seekable: seekable,
32711    safeLiveIndex: safeLiveIndex,
32712    getMediaInfoForTime: getMediaInfoForTime,
32713    isEnabled: isEnabled,
32714    isDisabled: isDisabled,
32715    isBlacklisted: isBlacklisted,
32716    isIncompatible: isIncompatible,
32717    playlistEnd: playlistEnd,
32718    isAes: isAes,
32719    isFmp4: isFmp4,
32720    hasAttribute: hasAttribute,
32721    estimateSegmentRequestTime: estimateSegmentRequestTime,
32722    isLowestEnabledRendition: isLowestEnabledRendition
32723  };
32724
32725  /**
32726   * @file xhr.js
32727   */
32728
32729  var videojsXHR = videojs$1.xhr,
32730      mergeOptions$1$1 = videojs$1.mergeOptions;
32731
32732  var xhrFactory = function xhrFactory() {
32733    var xhr = function XhrFunction(options, callback) {
32734      // Add a default timeout for all hls requests
32735      options = mergeOptions$1$1({
32736        timeout: 45e3
32737      }, options);
32738
32739      // Allow an optional user-specified function to modify the option
32740      // object before we construct the xhr request
32741      var beforeRequest = XhrFunction.beforeRequest || videojs$1.Hls.xhr.beforeRequest;
32742
32743      if (beforeRequest && typeof beforeRequest === 'function') {
32744        var newOptions = beforeRequest(options);
32745
32746        if (newOptions) {
32747          options = newOptions;
32748        }
32749      }
32750
32751      var request = videojsXHR(options, function (error, response) {
32752        var reqResponse = request.response;
32753
32754        if (!error && reqResponse) {
32755          request.responseTime = Date.now();
32756          request.roundTripTime = request.responseTime - request.requestTime;
32757          request.bytesReceived = reqResponse.byteLength || reqResponse.length;
32758          if (!request.bandwidth) {
32759            request.bandwidth = Math.floor(request.bytesReceived / request.roundTripTime * 8 * 1000);
32760          }
32761        }
32762
32763        if (response.headers) {
32764          request.responseHeaders = response.headers;
32765        }
32766
32767        // videojs.xhr now uses a specific code on the error
32768        // object to signal that a request has timed out instead
32769        // of setting a boolean on the request object
32770        if (error && error.code === 'ETIMEDOUT') {
32771          request.timedout = true;
32772        }
32773
32774        // videojs.xhr no longer considers status codes outside of 200 and 0
32775        // (for file uris) to be errors, but the old XHR did, so emulate that
32776        // behavior. Status 206 may be used in response to byterange requests.
32777        if (!error && !request.aborted && response.statusCode !== 200 && response.statusCode !== 206 && response.statusCode !== 0) {
32778          error = new Error('XHR Failed with a response of: ' + (request && (reqResponse || request.responseText)));
32779        }
32780
32781        callback(error, request);
32782      });
32783      var originalAbort = request.abort;
32784
32785      request.abort = function () {
32786        request.aborted = true;
32787        return originalAbort.apply(request, arguments);
32788      };
32789      request.uri = options.uri;
32790      request.requestTime = Date.now();
32791      return request;
32792    };
32793
32794    return xhr;
32795  };
32796
32797  /**
32798   * @file bin-utils.js
32799   */
32800
32801  /**
32802   * convert a TimeRange to text
32803   *
32804   * @param {TimeRange} range the timerange to use for conversion
32805   * @param {Number} i the iterator on the range to convert
32806   */
32807  var textRange = function textRange(range, i) {
32808    return range.start(i) + '-' + range.end(i);
32809  };
32810
32811  /**
32812   * format a number as hex string
32813   *
32814   * @param {Number} e The number
32815   * @param {Number} i the iterator
32816   */
32817  var formatHexString = function formatHexString(e, i) {
32818    var value = e.toString(16);
32819
32820    return '00'.substring(0, 2 - value.length) + value + (i % 2 ? ' ' : '');
32821  };
32822  var formatAsciiString = function formatAsciiString(e) {
32823    if (e >= 0x20 && e < 0x7e) {
32824      return String.fromCharCode(e);
32825    }
32826    return '.';
32827  };
32828
32829  /**
32830   * Creates an object for sending to a web worker modifying properties that are TypedArrays
32831   * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
32832   *
32833   * @param {Object} message
32834   *        Object of properties and values to send to the web worker
32835   * @return {Object}
32836   *         Modified message with TypedArray values expanded
32837   * @function createTransferableMessage
32838   */
32839  var createTransferableMessage = function createTransferableMessage(message) {
32840    var transferable = {};
32841
32842    Object.keys(message).forEach(function (key) {
32843      var value = message[key];
32844
32845      if (ArrayBuffer.isView(value)) {
32846        transferable[key] = {
32847          bytes: value.buffer,
32848          byteOffset: value.byteOffset,
32849          byteLength: value.byteLength
32850        };
32851      } else {
32852        transferable[key] = value;
32853      }
32854    });
32855
32856    return transferable;
32857  };
32858
32859  /**
32860   * Returns a unique string identifier for a media initialization
32861   * segment.
32862   */
32863  var initSegmentId = function initSegmentId(initSegment) {
32864    var byterange = initSegment.byterange || {
32865      length: Infinity,
32866      offset: 0
32867    };
32868
32869    return [byterange.length, byterange.offset, initSegment.resolvedUri].join(',');
32870  };
32871
32872  /**
32873   * utils to help dump binary data to the console
32874   */
32875  var hexDump = function hexDump(data) {
32876    var bytes = Array.prototype.slice.call(data);
32877    var step = 16;
32878    var result = '';
32879    var hex = void 0;
32880    var ascii = void 0;
32881
32882    for (var j = 0; j < bytes.length / step; j++) {
32883      hex = bytes.slice(j * step, j * step + step).map(formatHexString).join('');
32884      ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join('');
32885      result += hex + ' ' + ascii + '\n';
32886    }
32887
32888    return result;
32889  };
32890
32891  var tagDump = function tagDump(_ref) {
32892    var bytes = _ref.bytes;
32893    return hexDump(bytes);
32894  };
32895
32896  var textRanges = function textRanges(ranges) {
32897    var result = '';
32898    var i = void 0;
32899
32900    for (i = 0; i < ranges.length; i++) {
32901      result += textRange(ranges, i) + ' ';
32902    }
32903    return result;
32904  };
32905
32906  var utils = /*#__PURE__*/Object.freeze({
32907    createTransferableMessage: createTransferableMessage,
32908    initSegmentId: initSegmentId,
32909    hexDump: hexDump,
32910    tagDump: tagDump,
32911    textRanges: textRanges
32912  });
32913
32914  /**
32915   * ranges
32916   *
32917   * Utilities for working with TimeRanges.
32918   *
32919   */
32920
32921  // Fudge factor to account for TimeRanges rounding
32922  var TIME_FUDGE_FACTOR = 1 / 30;
32923  // Comparisons between time values such as current time and the end of the buffered range
32924  // can be misleading because of precision differences or when the current media has poorly
32925  // aligned audio and video, which can cause values to be slightly off from what you would
32926  // expect. This value is what we consider to be safe to use in such comparisons to account
32927  // for these scenarios.
32928  var SAFE_TIME_DELTA = TIME_FUDGE_FACTOR * 3;
32929  var filterRanges = function filterRanges(timeRanges, predicate) {
32930    var results = [];
32931    var i = void 0;
32932
32933    if (timeRanges && timeRanges.length) {
32934      // Search for ranges that match the predicate
32935      for (i = 0; i < timeRanges.length; i++) {
32936        if (predicate(timeRanges.start(i), timeRanges.end(i))) {
32937          results.push([timeRanges.start(i), timeRanges.end(i)]);
32938        }
32939      }
32940    }
32941
32942    return videojs$1.createTimeRanges(results);
32943  };
32944
32945  /**
32946   * Attempts to find the buffered TimeRange that contains the specified
32947   * time.
32948   * @param {TimeRanges} buffered - the TimeRanges object to query
32949   * @param {number} time  - the time to filter on.
32950   * @returns {TimeRanges} a new TimeRanges object
32951   */
32952  var findRange = function findRange(buffered, time) {
32953    return filterRanges(buffered, function (start, end) {
32954      return start - TIME_FUDGE_FACTOR <= time && end + TIME_FUDGE_FACTOR >= time;
32955    });
32956  };
32957
32958  /**
32959   * Returns the TimeRanges that begin later than the specified time.
32960   * @param {TimeRanges} timeRanges - the TimeRanges object to query
32961   * @param {number} time - the time to filter on.
32962   * @returns {TimeRanges} a new TimeRanges object.
32963   */
32964  var findNextRange = function findNextRange(timeRanges, time) {
32965    return filterRanges(timeRanges, function (start) {
32966      return start - TIME_FUDGE_FACTOR >= time;
32967    });
32968  };
32969
32970  /**
32971   * Returns gaps within a list of TimeRanges
32972   * @param {TimeRanges} buffered - the TimeRanges object
32973   * @return {TimeRanges} a TimeRanges object of gaps
32974   */
32975  var findGaps = function findGaps(buffered) {
32976    if (buffered.length < 2) {
32977      return videojs$1.createTimeRanges();
32978    }
32979
32980    var ranges = [];
32981
32982    for (var i = 1; i < buffered.length; i++) {
32983      var start = buffered.end(i - 1);
32984      var end = buffered.start(i);
32985
32986      ranges.push([start, end]);
32987    }
32988
32989    return videojs$1.createTimeRanges(ranges);
32990  };
32991
32992  /**
32993   * Gets a human readable string for a TimeRange
32994   *
32995   * @param {TimeRange} range
32996   * @returns {String} a human readable string
32997   */
32998  var printableRange = function printableRange(range) {
32999    var strArr = [];
33000
33001    if (!range || !range.length) {
33002      return '';
33003    }
33004
33005    for (var i = 0; i < range.length; i++) {
33006      strArr.push(range.start(i) + ' => ' + range.end(i));
33007    }
33008
33009    return strArr.join(', ');
33010  };
33011
33012  /**
33013   * Calculates the amount of time left in seconds until the player hits the end of the
33014   * buffer and causes a rebuffer
33015   *
33016   * @param {TimeRange} buffered
33017   *        The state of the buffer
33018   * @param {Numnber} currentTime
33019   *        The current time of the player
33020   * @param {Number} playbackRate
33021   *        The current playback rate of the player. Defaults to 1.
33022   * @return {Number}
33023   *         Time until the player has to start rebuffering in seconds.
33024   * @function timeUntilRebuffer
33025   */
33026  var timeUntilRebuffer = function timeUntilRebuffer(buffered, currentTime) {
33027    var playbackRate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
33028
33029    var bufferedEnd = buffered.length ? buffered.end(buffered.length - 1) : 0;
33030
33031    return (bufferedEnd - currentTime) / playbackRate;
33032  };
33033
33034  /**
33035   * Converts a TimeRanges object into an array representation
33036   * @param {TimeRanges} timeRanges
33037   * @returns {Array}
33038   */
33039  var timeRangesToArray = function timeRangesToArray(timeRanges) {
33040    var timeRangesList = [];
33041
33042    for (var i = 0; i < timeRanges.length; i++) {
33043      timeRangesList.push({
33044        start: timeRanges.start(i),
33045        end: timeRanges.end(i)
33046      });
33047    }
33048
33049    return timeRangesList;
33050  };
33051
33052  /**
33053   * @file create-text-tracks-if-necessary.js
33054   */
33055
33056  /**
33057   * Create text tracks on video.js if they exist on a segment.
33058   *
33059   * @param {Object} sourceBuffer the VSB or FSB
33060   * @param {Object} mediaSource the HTML media source
33061   * @param {Object} segment the segment that may contain the text track
33062   * @private
33063   */
33064  var createTextTracksIfNecessary = function createTextTracksIfNecessary(sourceBuffer, mediaSource, segment) {
33065    var player = mediaSource.player_;
33066
33067    // create an in-band caption track if one is present in the segment
33068    if (segment.captions && segment.captions.length) {
33069      if (!sourceBuffer.inbandTextTracks_) {
33070        sourceBuffer.inbandTextTracks_ = {};
33071      }
33072
33073      for (var trackId in segment.captionStreams) {
33074        if (!sourceBuffer.inbandTextTracks_[trackId]) {
33075          player.tech_.trigger({ type: 'usage', name: 'hls-608' });
33076          var track = player.textTracks().getTrackById(trackId);
33077
33078          if (track) {
33079            // Resuse an existing track with a CC# id because this was
33080            // very likely created by videojs-contrib-hls from information
33081            // in the m3u8 for us to use
33082            sourceBuffer.inbandTextTracks_[trackId] = track;
33083          } else {
33084            // Otherwise, create a track with the default `CC#` label and
33085            // without a language
33086            sourceBuffer.inbandTextTracks_[trackId] = player.addRemoteTextTrack({
33087              kind: 'captions',
33088              id: trackId,
33089              label: trackId
33090            }, false).track;
33091          }
33092        }
33093      }
33094    }
33095
33096    if (segment.metadata && segment.metadata.length && !sourceBuffer.metadataTrack_) {
33097      sourceBuffer.metadataTrack_ = player.addRemoteTextTrack({
33098        kind: 'metadata',
33099        label: 'Timed Metadata'
33100      }, false).track;
33101      sourceBuffer.metadataTrack_.inBandMetadataTrackDispatchType = segment.metadata.dispatchType;
33102    }
33103  };
33104
33105  /**
33106   * @file remove-cues-from-track.js
33107   */
33108
33109  /**
33110   * Remove cues from a track on video.js.
33111   *
33112   * @param {Double} start start of where we should remove the cue
33113   * @param {Double} end end of where the we should remove the cue
33114   * @param {Object} track the text track to remove the cues from
33115   * @private
33116   */
33117  var removeCuesFromTrack = function removeCuesFromTrack(start, end, track) {
33118    var i = void 0;
33119    var cue = void 0;
33120
33121    if (!track) {
33122      return;
33123    }
33124
33125    if (!track.cues) {
33126      return;
33127    }
33128
33129    i = track.cues.length;
33130
33131    while (i--) {
33132      cue = track.cues[i];
33133
33134      // Remove any overlapping cue
33135      if (cue.startTime <= end && cue.endTime >= start) {
33136        track.removeCue(cue);
33137      }
33138    }
33139  };
33140
33141  /**
33142   * @file add-text-track-data.js
33143   */
33144  /**
33145   * Define properties on a cue for backwards compatability,
33146   * but warn the user that the way that they are using it
33147   * is depricated and will be removed at a later date.
33148   *
33149   * @param {Cue} cue the cue to add the properties on
33150   * @private
33151   */
33152  var deprecateOldCue = function deprecateOldCue(cue) {
33153    Object.defineProperties(cue.frame, {
33154      id: {
33155        get: function get$$1() {
33156          videojs$1.log.warn('cue.frame.id is deprecated. Use cue.value.key instead.');
33157          return cue.value.key;
33158        }
33159      },
33160      value: {
33161        get: function get$$1() {
33162          videojs$1.log.warn('cue.frame.value is deprecated. Use cue.value.data instead.');
33163          return cue.value.data;
33164        }
33165      },
33166      privateData: {
33167        get: function get$$1() {
33168          videojs$1.log.warn('cue.frame.privateData is deprecated. Use cue.value.data instead.');
33169          return cue.value.data;
33170        }
33171      }
33172    });
33173  };
33174
33175  var durationOfVideo = function durationOfVideo(duration) {
33176    var dur = void 0;
33177
33178    if (isNaN(duration) || Math.abs(duration) === Infinity) {
33179      dur = Number.MAX_VALUE;
33180    } else {
33181      dur = duration;
33182    }
33183    return dur;
33184  };
33185  /**
33186   * Add text track data to a source handler given the captions and
33187   * metadata from the buffer.
33188   *
33189   * @param {Object} sourceHandler the virtual source buffer
33190   * @param {Array} captionArray an array of caption data
33191   * @param {Array} metadataArray an array of meta data
33192   * @private
33193   */
33194  var addTextTrackData = function addTextTrackData(sourceHandler, captionArray, metadataArray) {
33195    var Cue = window_1.WebKitDataCue || window_1.VTTCue;
33196
33197    if (captionArray) {
33198      captionArray.forEach(function (caption) {
33199        var track = caption.stream;
33200
33201        this.inbandTextTracks_[track].addCue(new Cue(caption.startTime + this.timestampOffset, caption.endTime + this.timestampOffset, caption.text));
33202      }, sourceHandler);
33203    }
33204
33205    if (metadataArray) {
33206      var videoDuration = durationOfVideo(sourceHandler.mediaSource_.duration);
33207
33208      metadataArray.forEach(function (metadata) {
33209        var time = metadata.cueTime + this.timestampOffset;
33210
33211        metadata.frames.forEach(function (frame) {
33212          var cue = new Cue(time, time, frame.value || frame.url || frame.data || '');
33213
33214          cue.frame = frame;
33215          cue.value = frame;
33216          deprecateOldCue(cue);
33217
33218          this.metadataTrack_.addCue(cue);
33219        }, this);
33220      }, sourceHandler);
33221
33222      // Updating the metadeta cues so that
33223      // the endTime of each cue is the startTime of the next cue
33224      // the endTime of last cue is the duration of the video
33225      if (sourceHandler.metadataTrack_ && sourceHandler.metadataTrack_.cues && sourceHandler.metadataTrack_.cues.length) {
33226        var cues = sourceHandler.metadataTrack_.cues;
33227        var cuesArray = [];
33228
33229        // Create a copy of the TextTrackCueList...
33230        // ...disregarding cues with a falsey value
33231        for (var i = 0; i < cues.length; i++) {
33232          if (cues[i]) {
33233            cuesArray.push(cues[i]);
33234          }
33235        }
33236
33237        // Group cues by their startTime value
33238        var cuesGroupedByStartTime = cuesArray.reduce(function (obj, cue) {
33239          var timeSlot = obj[cue.startTime] || [];
33240
33241          timeSlot.push(cue);
33242          obj[cue.startTime] = timeSlot;
33243
33244          return obj;
33245        }, {});
33246
33247        // Sort startTimes by ascending order
33248        var sortedStartTimes = Object.keys(cuesGroupedByStartTime).sort(function (a, b) {
33249          return Number(a) - Number(b);
33250        });
33251
33252        // Map each cue group's endTime to the next group's startTime
33253        sortedStartTimes.forEach(function (startTime, idx) {
33254          var cueGroup = cuesGroupedByStartTime[startTime];
33255          var nextTime = Number(sortedStartTimes[idx + 1]) || videoDuration;
33256
33257          // Map each cue's endTime the next group's startTime
33258          cueGroup.forEach(function (cue) {
33259            cue.endTime = nextTime;
33260          });
33261        });
33262      }
33263    }
33264  };
33265
33266  var win$1 = typeof window !== 'undefined' ? window : {},
33267      TARGET = typeof Symbol === 'undefined' ? '__target' : Symbol(),
33268      SCRIPT_TYPE = 'application/javascript',
33269      BlobBuilder = win$1.BlobBuilder || win$1.WebKitBlobBuilder || win$1.MozBlobBuilder || win$1.MSBlobBuilder,
33270      URL = win$1.URL || win$1.webkitURL || URL && URL.msURL,
33271      Worker = win$1.Worker;
33272
33273  /**
33274   * Returns a wrapper around Web Worker code that is constructible.
33275   *
33276   * @function shimWorker
33277   *
33278   * @param { String }    filename    The name of the file
33279   * @param { Function }  fn          Function wrapping the code of the worker
33280   */
33281  function shimWorker(filename, fn) {
33282    return function ShimWorker(forceFallback) {
33283      var o = this;
33284
33285      if (!fn) {
33286        return new Worker(filename);
33287      } else if (Worker && !forceFallback) {
33288        // Convert the function's inner code to a string to construct the worker
33289        var source = fn.toString().replace(/^function.+?{/, '').slice(0, -1),
33290            objURL = createSourceObject(source);
33291
33292        this[TARGET] = new Worker(objURL);
33293        wrapTerminate(this[TARGET], objURL);
33294        return this[TARGET];
33295      } else {
33296        var selfShim = {
33297          postMessage: function postMessage(m) {
33298            if (o.onmessage) {
33299              setTimeout(function () {
33300                o.onmessage({ data: m, target: selfShim });
33301              });
33302            }
33303          }
33304        };
33305
33306        fn.call(selfShim);
33307        this.postMessage = function (m) {
33308          setTimeout(function () {
33309            selfShim.onmessage({ data: m, target: o });
33310          });
33311        };
33312        this.isThisThread = true;
33313      }
33314    };
33315  }
33316  // Test Worker capabilities
33317  if (Worker) {
33318    var testWorker,
33319        objURL = createSourceObject('self.onmessage = function () {}'),
33320        testArray = new Uint8Array(1);
33321
33322    try {
33323      testWorker = new Worker(objURL);
33324
33325      // Native browser on some Samsung devices throws for transferables, let's detect it
33326      testWorker.postMessage(testArray, [testArray.buffer]);
33327    } catch (e) {
33328      Worker = null;
33329    } finally {
33330      URL.revokeObjectURL(objURL);
33331      if (testWorker) {
33332        testWorker.terminate();
33333      }
33334    }
33335  }
33336
33337  function createSourceObject(str) {
33338    try {
33339      return URL.createObjectURL(new Blob([str], { type: SCRIPT_TYPE }));
33340    } catch (e) {
33341      var blob = new BlobBuilder();
33342      blob.append(str);
33343      return URL.createObjectURL(blob.getBlob(type));
33344    }
33345  }
33346
33347  function wrapTerminate(worker, objURL) {
33348    if (!worker || !objURL) return;
33349    var term = worker.terminate;
33350    worker.objURL = objURL;
33351    worker.terminate = function () {
33352      if (worker.objURL) URL.revokeObjectURL(worker.objURL);
33353      term.call(worker);
33354    };
33355  }
33356
33357  var TransmuxWorker = new shimWorker("./transmuxer-worker.worker.js", function (window, document$$1) {
33358    var self = this;
33359    var transmuxerWorker = function () {
33360
33361      var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
33362
33363      var win;
33364
33365      if (typeof window !== "undefined") {
33366        win = window;
33367      } else if (typeof commonjsGlobal !== "undefined") {
33368        win = commonjsGlobal;
33369      } else if (typeof self !== "undefined") {
33370        win = self;
33371      } else {
33372        win = {};
33373      }
33374
33375      var window_1$$1 = win;
33376
33377      /**
33378       * mux.js
33379       *
33380       * Copyright (c) 2015 Brightcove
33381       * All rights reserved.
33382       *
33383       * Functions that generate fragmented MP4s suitable for use with Media
33384       * Source Extensions.
33385       */
33386
33387      var UINT32_MAX = Math.pow(2, 32) - 1;
33388
33389      var box, dinf, esds, ftyp, mdat, mfhd, minf, moof, moov, mvex, mvhd, trak, tkhd, mdia, mdhd, hdlr, sdtp, stbl, stsd, traf, trex, trun, types, MAJOR_BRAND, MINOR_VERSION, AVC1_BRAND, VIDEO_HDLR, AUDIO_HDLR, HDLR_TYPES, VMHD, SMHD, DREF, STCO, STSC, STSZ, STTS;
33390
33391      // pre-calculate constants
33392      (function () {
33393        var i;
33394        types = {
33395          avc1: [], // codingname
33396          avcC: [],
33397          btrt: [],
33398          dinf: [],
33399          dref: [],
33400          esds: [],
33401          ftyp: [],
33402          hdlr: [],
33403          mdat: [],
33404          mdhd: [],
33405          mdia: [],
33406          mfhd: [],
33407          minf: [],
33408          moof: [],
33409          moov: [],
33410          mp4a: [], // codingname
33411          mvex: [],
33412          mvhd: [],
33413          sdtp: [],
33414          smhd: [],
33415          stbl: [],
33416          stco: [],
33417          stsc: [],
33418          stsd: [],
33419          stsz: [],
33420          stts: [],
33421          styp: [],
33422          tfdt: [],
33423          tfhd: [],
33424          traf: [],
33425          trak: [],
33426          trun: [],
33427          trex: [],
33428          tkhd: [],
33429          vmhd: []
33430        };
33431
33432        // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we
33433        // don't throw an error
33434        if (typeof Uint8Array === 'undefined') {
33435          return;
33436        }
33437
33438        for (i in types) {
33439          if (types.hasOwnProperty(i)) {
33440            types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)];
33441          }
33442        }
33443
33444        MAJOR_BRAND = new Uint8Array(['i'.charCodeAt(0), 's'.charCodeAt(0), 'o'.charCodeAt(0), 'm'.charCodeAt(0)]);
33445        AVC1_BRAND = new Uint8Array(['a'.charCodeAt(0), 'v'.charCodeAt(0), 'c'.charCodeAt(0), '1'.charCodeAt(0)]);
33446        MINOR_VERSION = new Uint8Array([0, 0, 0, 1]);
33447        VIDEO_HDLR = new Uint8Array([0x00, // version 0
33448        0x00, 0x00, 0x00, // flags
33449        0x00, 0x00, 0x00, 0x00, // pre_defined
33450        0x76, 0x69, 0x64, 0x65, // handler_type: 'vide'
33451        0x00, 0x00, 0x00, 0x00, // reserved
33452        0x00, 0x00, 0x00, 0x00, // reserved
33453        0x00, 0x00, 0x00, 0x00, // reserved
33454        0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
33455        ]);
33456        AUDIO_HDLR = new Uint8Array([0x00, // version 0
33457        0x00, 0x00, 0x00, // flags
33458        0x00, 0x00, 0x00, 0x00, // pre_defined
33459        0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun'
33460        0x00, 0x00, 0x00, 0x00, // reserved
33461        0x00, 0x00, 0x00, 0x00, // reserved
33462        0x00, 0x00, 0x00, 0x00, // reserved
33463        0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'
33464        ]);
33465        HDLR_TYPES = {
33466          video: VIDEO_HDLR,
33467          audio: AUDIO_HDLR
33468        };
33469        DREF = new Uint8Array([0x00, // version 0
33470        0x00, 0x00, 0x00, // flags
33471        0x00, 0x00, 0x00, 0x01, // entry_count
33472        0x00, 0x00, 0x00, 0x0c, // entry_size
33473        0x75, 0x72, 0x6c, 0x20, // 'url' type
33474        0x00, // version 0
33475        0x00, 0x00, 0x01 // entry_flags
33476        ]);
33477        SMHD = new Uint8Array([0x00, // version
33478        0x00, 0x00, 0x00, // flags
33479        0x00, 0x00, // balance, 0 means centered
33480        0x00, 0x00 // reserved
33481        ]);
33482        STCO = new Uint8Array([0x00, // version
33483        0x00, 0x00, 0x00, // flags
33484        0x00, 0x00, 0x00, 0x00 // entry_count
33485        ]);
33486        STSC = STCO;
33487        STSZ = new Uint8Array([0x00, // version
33488        0x00, 0x00, 0x00, // flags
33489        0x00, 0x00, 0x00, 0x00, // sample_size
33490        0x00, 0x00, 0x00, 0x00 // sample_count
33491        ]);
33492        STTS = STCO;
33493        VMHD = new Uint8Array([0x00, // version
33494        0x00, 0x00, 0x01, // flags
33495        0x00, 0x00, // graphicsmode
33496        0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor
33497        ]);
33498      })();
33499
33500      box = function box(type) {
33501        var payload = [],
33502            size = 0,
33503            i,
33504            result,
33505            view;
33506
33507        for (i = 1; i < arguments.length; i++) {
33508          payload.push(arguments[i]);
33509        }
33510
33511        i = payload.length;
33512
33513        // calculate the total size we need to allocate
33514        while (i--) {
33515          size += payload[i].byteLength;
33516        }
33517        result = new Uint8Array(size + 8);
33518        view = new DataView(result.buffer, result.byteOffset, result.byteLength);
33519        view.setUint32(0, result.byteLength);
33520        result.set(type, 4);
33521
33522        // copy the payload into the result
33523        for (i = 0, size = 8; i < payload.length; i++) {
33524          result.set(payload[i], size);
33525          size += payload[i].byteLength;
33526        }
33527        return result;
33528      };
33529
33530      dinf = function dinf() {
33531        return box(types.dinf, box(types.dref, DREF));
33532      };
33533
33534      esds = function esds(track) {
33535        return box(types.esds, new Uint8Array([0x00, // version
33536        0x00, 0x00, 0x00, // flags
33537
33538        // ES_Descriptor
33539        0x03, // tag, ES_DescrTag
33540        0x19, // length
33541        0x00, 0x00, // ES_ID
33542        0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority
33543
33544        // DecoderConfigDescriptor
33545        0x04, // tag, DecoderConfigDescrTag
33546        0x11, // length
33547        0x40, // object type
33548        0x15, // streamType
33549        0x00, 0x06, 0x00, // bufferSizeDB
33550        0x00, 0x00, 0xda, 0xc0, // maxBitrate
33551        0x00, 0x00, 0xda, 0xc0, // avgBitrate
33552
33553        // DecoderSpecificInfo
33554        0x05, // tag, DecoderSpecificInfoTag
33555        0x02, // length
33556        // ISO/IEC 14496-3, AudioSpecificConfig
33557        // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35
33558        track.audioobjecttype << 3 | track.samplingfrequencyindex >>> 1, track.samplingfrequencyindex << 7 | track.channelcount << 3, 0x06, 0x01, 0x02 // GASpecificConfig
33559        ]));
33560      };
33561
33562      ftyp = function ftyp() {
33563        return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND);
33564      };
33565
33566      hdlr = function hdlr(type) {
33567        return box(types.hdlr, HDLR_TYPES[type]);
33568      };
33569      mdat = function mdat(data) {
33570        return box(types.mdat, data);
33571      };
33572      mdhd = function mdhd(track) {
33573        var result = new Uint8Array([0x00, // version 0
33574        0x00, 0x00, 0x00, // flags
33575        0x00, 0x00, 0x00, 0x02, // creation_time
33576        0x00, 0x00, 0x00, 0x03, // modification_time
33577        0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
33578
33579        track.duration >>> 24 & 0xFF, track.duration >>> 16 & 0xFF, track.duration >>> 8 & 0xFF, track.duration & 0xFF, // duration
33580        0x55, 0xc4, // 'und' language (undetermined)
33581        0x00, 0x00]);
33582
33583        // Use the sample rate from the track metadata, when it is
33584        // defined. The sample rate can be parsed out of an ADTS header, for
33585        // instance.
33586        if (track.samplerate) {
33587          result[12] = track.samplerate >>> 24 & 0xFF;
33588          result[13] = track.samplerate >>> 16 & 0xFF;
33589          result[14] = track.samplerate >>> 8 & 0xFF;
33590          result[15] = track.samplerate & 0xFF;
33591        }
33592
33593        return box(types.mdhd, result);
33594      };
33595      mdia = function mdia(track) {
33596        return box(types.mdia, mdhd(track), hdlr(track.type), minf(track));
33597      };
33598      mfhd = function mfhd(sequenceNumber) {
33599        return box(types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // flags
33600        (sequenceNumber & 0xFF000000) >> 24, (sequenceNumber & 0xFF0000) >> 16, (sequenceNumber & 0xFF00) >> 8, sequenceNumber & 0xFF // sequence_number
33601        ]));
33602      };
33603      minf = function minf(track) {
33604        return box(types.minf, track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), dinf(), stbl(track));
33605      };
33606      moof = function moof(sequenceNumber, tracks) {
33607        var trackFragments = [],
33608            i = tracks.length;
33609        // build traf boxes for each track fragment
33610        while (i--) {
33611          trackFragments[i] = traf(tracks[i]);
33612        }
33613        return box.apply(null, [types.moof, mfhd(sequenceNumber)].concat(trackFragments));
33614      };
33615      /**
33616       * Returns a movie box.
33617       * @param tracks {array} the tracks associated with this movie
33618       * @see ISO/IEC 14496-12:2012(E), section 8.2.1
33619       */
33620      moov = function moov(tracks) {
33621        var i = tracks.length,
33622            boxes = [];
33623
33624        while (i--) {
33625          boxes[i] = trak(tracks[i]);
33626        }
33627
33628        return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks)));
33629      };
33630      mvex = function mvex(tracks) {
33631        var i = tracks.length,
33632            boxes = [];
33633
33634        while (i--) {
33635          boxes[i] = trex(tracks[i]);
33636        }
33637        return box.apply(null, [types.mvex].concat(boxes));
33638      };
33639      mvhd = function mvhd(duration) {
33640        var bytes = new Uint8Array([0x00, // version 0
33641        0x00, 0x00, 0x00, // flags
33642        0x00, 0x00, 0x00, 0x01, // creation_time
33643        0x00, 0x00, 0x00, 0x02, // modification_time
33644        0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
33645        (duration & 0xFF000000) >> 24, (duration & 0xFF0000) >> 16, (duration & 0xFF00) >> 8, duration & 0xFF, // duration
33646        0x00, 0x01, 0x00, 0x00, // 1.0 rate
33647        0x01, 0x00, // 1.0 volume
33648        0x00, 0x00, // reserved
33649        0x00, 0x00, 0x00, 0x00, // reserved
33650        0x00, 0x00, 0x00, 0x00, // reserved
33651        0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
33652        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined
33653        0xff, 0xff, 0xff, 0xff // next_track_ID
33654        ]);
33655        return box(types.mvhd, bytes);
33656      };
33657
33658      sdtp = function sdtp(track) {
33659        var samples = track.samples || [],
33660            bytes = new Uint8Array(4 + samples.length),
33661            flags,
33662            i;
33663
33664        // leave the full box header (4 bytes) all zero
33665
33666        // write the sample table
33667        for (i = 0; i < samples.length; i++) {
33668          flags = samples[i].flags;
33669
33670          bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy;
33671        }
33672
33673        return box(types.sdtp, bytes);
33674      };
33675
33676      stbl = function stbl(track) {
33677        return box(types.stbl, stsd(track), box(types.stts, STTS), box(types.stsc, STSC), box(types.stsz, STSZ), box(types.stco, STCO));
33678      };
33679
33680      (function () {
33681        var videoSample, audioSample;
33682
33683        stsd = function stsd(track) {
33684
33685          return box(types.stsd, new Uint8Array([0x00, // version 0
33686          0x00, 0x00, 0x00, // flags
33687          0x00, 0x00, 0x00, 0x01]), track.type === 'video' ? videoSample(track) : audioSample(track));
33688        };
33689
33690        videoSample = function videoSample(track) {
33691          var sps = track.sps || [],
33692              pps = track.pps || [],
33693              sequenceParameterSets = [],
33694              pictureParameterSets = [],
33695              i;
33696
33697          // assemble the SPSs
33698          for (i = 0; i < sps.length; i++) {
33699            sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8);
33700            sequenceParameterSets.push(sps[i].byteLength & 0xFF); // sequenceParameterSetLength
33701            sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS
33702          }
33703
33704          // assemble the PPSs
33705          for (i = 0; i < pps.length; i++) {
33706            pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8);
33707            pictureParameterSets.push(pps[i].byteLength & 0xFF);
33708            pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i]));
33709          }
33710
33711          return box(types.avc1, new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
33712          0x00, 0x01, // data_reference_index
33713          0x00, 0x00, // pre_defined
33714          0x00, 0x00, // reserved
33715          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined
33716          (track.width & 0xff00) >> 8, track.width & 0xff, // width
33717          (track.height & 0xff00) >> 8, track.height & 0xff, // height
33718          0x00, 0x48, 0x00, 0x00, // horizresolution
33719          0x00, 0x48, 0x00, 0x00, // vertresolution
33720          0x00, 0x00, 0x00, 0x00, // reserved
33721          0x00, 0x01, // frame_count
33722          0x13, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x6a, 0x73, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x2d, 0x68, 0x6c, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // compressorname
33723          0x00, 0x18, // depth = 24
33724          0x11, 0x11 // pre_defined = -1
33725          ]), box(types.avcC, new Uint8Array([0x01, // configurationVersion
33726          track.profileIdc, // AVCProfileIndication
33727          track.profileCompatibility, // profile_compatibility
33728          track.levelIdc, // AVCLevelIndication
33729          0xff // lengthSizeMinusOne, hard-coded to 4 bytes
33730          ].concat([sps.length // numOfSequenceParameterSets
33731          ]).concat(sequenceParameterSets).concat([pps.length // numOfPictureParameterSets
33732          ]).concat(pictureParameterSets))), // "PPS"
33733          box(types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB
33734          0x00, 0x2d, 0xc6, 0xc0, // maxBitrate
33735          0x00, 0x2d, 0xc6, 0xc0])) // avgBitrate
33736          );
33737        };
33738
33739        audioSample = function audioSample(track) {
33740          return box(types.mp4a, new Uint8Array([
33741
33742          // SampleEntry, ISO/IEC 14496-12
33743          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
33744          0x00, 0x01, // data_reference_index
33745
33746          // AudioSampleEntry, ISO/IEC 14496-12
33747          0x00, 0x00, 0x00, 0x00, // reserved
33748          0x00, 0x00, 0x00, 0x00, // reserved
33749          (track.channelcount & 0xff00) >> 8, track.channelcount & 0xff, // channelcount
33750
33751          (track.samplesize & 0xff00) >> 8, track.samplesize & 0xff, // samplesize
33752          0x00, 0x00, // pre_defined
33753          0x00, 0x00, // reserved
33754
33755          (track.samplerate & 0xff00) >> 8, track.samplerate & 0xff, 0x00, 0x00 // samplerate, 16.16
33756
33757          // MP4AudioSampleEntry, ISO/IEC 14496-14
33758          ]), esds(track));
33759        };
33760      })();
33761
33762      tkhd = function tkhd(track) {
33763        var result = new Uint8Array([0x00, // version 0
33764        0x00, 0x00, 0x07, // flags
33765        0x00, 0x00, 0x00, 0x00, // creation_time
33766        0x00, 0x00, 0x00, 0x00, // modification_time
33767        (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
33768        0x00, 0x00, 0x00, 0x00, // reserved
33769        (track.duration & 0xFF000000) >> 24, (track.duration & 0xFF0000) >> 16, (track.duration & 0xFF00) >> 8, track.duration & 0xFF, // duration
33770        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
33771        0x00, 0x00, // layer
33772        0x00, 0x00, // alternate_group
33773        0x01, 0x00, // non-audio track volume
33774        0x00, 0x00, // reserved
33775        0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
33776        (track.width & 0xFF00) >> 8, track.width & 0xFF, 0x00, 0x00, // width
33777        (track.height & 0xFF00) >> 8, track.height & 0xFF, 0x00, 0x00 // height
33778        ]);
33779
33780        return box(types.tkhd, result);
33781      };
33782
33783      /**
33784       * Generate a track fragment (traf) box. A traf box collects metadata
33785       * about tracks in a movie fragment (moof) box.
33786       */
33787      traf = function traf(track) {
33788        var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable, dataOffset, upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime;
33789
33790        trackFragmentHeader = box(types.tfhd, new Uint8Array([0x00, // version 0
33791        0x00, 0x00, 0x3a, // flags
33792        (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
33793        0x00, 0x00, 0x00, 0x01, // sample_description_index
33794        0x00, 0x00, 0x00, 0x00, // default_sample_duration
33795        0x00, 0x00, 0x00, 0x00, // default_sample_size
33796        0x00, 0x00, 0x00, 0x00 // default_sample_flags
33797        ]));
33798
33799        upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / (UINT32_MAX + 1));
33800        lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % (UINT32_MAX + 1));
33801
33802        trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([0x01, // version 1
33803        0x00, 0x00, 0x00, // flags
33804        // baseMediaDecodeTime
33805        upperWordBaseMediaDecodeTime >>> 24 & 0xFF, upperWordBaseMediaDecodeTime >>> 16 & 0xFF, upperWordBaseMediaDecodeTime >>> 8 & 0xFF, upperWordBaseMediaDecodeTime & 0xFF, lowerWordBaseMediaDecodeTime >>> 24 & 0xFF, lowerWordBaseMediaDecodeTime >>> 16 & 0xFF, lowerWordBaseMediaDecodeTime >>> 8 & 0xFF, lowerWordBaseMediaDecodeTime & 0xFF]));
33806
33807        // the data offset specifies the number of bytes from the start of
33808        // the containing moof to the first payload byte of the associated
33809        // mdat
33810        dataOffset = 32 + // tfhd
33811        20 + // tfdt
33812        8 + // traf header
33813        16 + // mfhd
33814        8 + // moof header
33815        8; // mdat header
33816
33817        // audio tracks require less metadata
33818        if (track.type === 'audio') {
33819          trackFragmentRun = trun(track, dataOffset);
33820          return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun);
33821        }
33822
33823        // video tracks should contain an independent and disposable samples
33824        // box (sdtp)
33825        // generate one and adjust offsets to match
33826        sampleDependencyTable = sdtp(track);
33827        trackFragmentRun = trun(track, sampleDependencyTable.length + dataOffset);
33828        return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable);
33829      };
33830
33831      /**
33832       * Generate a track box.
33833       * @param track {object} a track definition
33834       * @return {Uint8Array} the track box
33835       */
33836      trak = function trak(track) {
33837        track.duration = track.duration || 0xffffffff;
33838        return box(types.trak, tkhd(track), mdia(track));
33839      };
33840
33841      trex = function trex(track) {
33842        var result = new Uint8Array([0x00, // version 0
33843        0x00, 0x00, 0x00, // flags
33844        (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
33845        0x00, 0x00, 0x00, 0x01, // default_sample_description_index
33846        0x00, 0x00, 0x00, 0x00, // default_sample_duration
33847        0x00, 0x00, 0x00, 0x00, // default_sample_size
33848        0x00, 0x01, 0x00, 0x01 // default_sample_flags
33849        ]);
33850        // the last two bytes of default_sample_flags is the sample
33851        // degradation priority, a hint about the importance of this sample
33852        // relative to others. Lower the degradation priority for all sample
33853        // types other than video.
33854        if (track.type !== 'video') {
33855          result[result.length - 1] = 0x00;
33856        }
33857
33858        return box(types.trex, result);
33859      };
33860
33861      (function () {
33862        var audioTrun, videoTrun, trunHeader;
33863
33864        // This method assumes all samples are uniform. That is, if a
33865        // duration is present for the first sample, it will be present for
33866        // all subsequent samples.
33867        // see ISO/IEC 14496-12:2012, Section 8.8.8.1
33868        trunHeader = function trunHeader(samples, offset) {
33869          var durationPresent = 0,
33870              sizePresent = 0,
33871              flagsPresent = 0,
33872              compositionTimeOffset = 0;
33873
33874          // trun flag constants
33875          if (samples.length) {
33876            if (samples[0].duration !== undefined) {
33877              durationPresent = 0x1;
33878            }
33879            if (samples[0].size !== undefined) {
33880              sizePresent = 0x2;
33881            }
33882            if (samples[0].flags !== undefined) {
33883              flagsPresent = 0x4;
33884            }
33885            if (samples[0].compositionTimeOffset !== undefined) {
33886              compositionTimeOffset = 0x8;
33887            }
33888          }
33889
33890          return [0x00, // version 0
33891          0x00, durationPresent | sizePresent | flagsPresent | compositionTimeOffset, 0x01, // flags
33892          (samples.length & 0xFF000000) >>> 24, (samples.length & 0xFF0000) >>> 16, (samples.length & 0xFF00) >>> 8, samples.length & 0xFF, // sample_count
33893          (offset & 0xFF000000) >>> 24, (offset & 0xFF0000) >>> 16, (offset & 0xFF00) >>> 8, offset & 0xFF // data_offset
33894          ];
33895        };
33896
33897        videoTrun = function videoTrun(track, offset) {
33898          var bytes, samples, sample, i;
33899
33900          samples = track.samples || [];
33901          offset += 8 + 12 + 16 * samples.length;
33902
33903          bytes = trunHeader(samples, offset);
33904
33905          for (i = 0; i < samples.length; i++) {
33906            sample = samples[i];
33907            bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration
33908            (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF, // sample_size
33909            sample.flags.isLeading << 2 | sample.flags.dependsOn, sample.flags.isDependedOn << 6 | sample.flags.hasRedundancy << 4 | sample.flags.paddingValue << 1 | sample.flags.isNonSyncSample, sample.flags.degradationPriority & 0xF0 << 8, sample.flags.degradationPriority & 0x0F, // sample_flags
33910            (sample.compositionTimeOffset & 0xFF000000) >>> 24, (sample.compositionTimeOffset & 0xFF0000) >>> 16, (sample.compositionTimeOffset & 0xFF00) >>> 8, sample.compositionTimeOffset & 0xFF // sample_composition_time_offset
33911            ]);
33912          }
33913          return box(types.trun, new Uint8Array(bytes));
33914        };
33915
33916        audioTrun = function audioTrun(track, offset) {
33917          var bytes, samples, sample, i;
33918
33919          samples = track.samples || [];
33920          offset += 8 + 12 + 8 * samples.length;
33921
33922          bytes = trunHeader(samples, offset);
33923
33924          for (i = 0; i < samples.length; i++) {
33925            sample = samples[i];
33926            bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration
33927            (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF]); // sample_size
33928          }
33929
33930          return box(types.trun, new Uint8Array(bytes));
33931        };
33932
33933        trun = function trun(track, offset) {
33934          if (track.type === 'audio') {
33935            return audioTrun(track, offset);
33936          }
33937
33938          return videoTrun(track, offset);
33939        };
33940      })();
33941
33942      var mp4Generator = {
33943        ftyp: ftyp,
33944        mdat: mdat,
33945        moof: moof,
33946        moov: moov,
33947        initSegment: function initSegment(tracks) {
33948          var fileType = ftyp(),
33949              movie = moov(tracks),
33950              result;
33951
33952          result = new Uint8Array(fileType.byteLength + movie.byteLength);
33953          result.set(fileType);
33954          result.set(movie, fileType.byteLength);
33955          return result;
33956        }
33957      };
33958
33959      /**
33960       * mux.js
33961       *
33962       * Copyright (c) 2014 Brightcove
33963       * All rights reserved.
33964       *
33965       * A lightweight readable stream implemention that handles event dispatching.
33966       * Objects that inherit from streams should call init in their constructors.
33967       */
33968
33969      var Stream = function Stream() {
33970        this.init = function () {
33971          var listeners = {};
33972          /**
33973           * Add a listener for a specified event type.
33974           * @param type {string} the event name
33975           * @param listener {function} the callback to be invoked when an event of
33976           * the specified type occurs
33977           */
33978          this.on = function (type, listener) {
33979            if (!listeners[type]) {
33980              listeners[type] = [];
33981            }
33982            listeners[type] = listeners[type].concat(listener);
33983          };
33984          /**
33985           * Remove a listener for a specified event type.
33986           * @param type {string} the event name
33987           * @param listener {function} a function previously registered for this
33988           * type of event through `on`
33989           */
33990          this.off = function (type, listener) {
33991            var index;
33992            if (!listeners[type]) {
33993              return false;
33994            }
33995            index = listeners[type].indexOf(listener);
33996            listeners[type] = listeners[type].slice();
33997            listeners[type].splice(index, 1);
33998            return index > -1;
33999          };
34000          /**
34001           * Trigger an event of the specified type on this stream. Any additional
34002           * arguments to this function are passed as parameters to event listeners.
34003           * @param type {string} the event name
34004           */
34005          this.trigger = function (type) {
34006            var callbacks, i, length, args;
34007            callbacks = listeners[type];
34008            if (!callbacks) {
34009              return;
34010            }
34011            // Slicing the arguments on every invocation of this method
34012            // can add a significant amount of overhead. Avoid the
34013            // intermediate object creation for the common case of a
34014            // single callback argument
34015            if (arguments.length === 2) {
34016              length = callbacks.length;
34017              for (i = 0; i < length; ++i) {
34018                callbacks[i].call(this, arguments[1]);
34019              }
34020            } else {
34021              args = [];
34022              i = arguments.length;
34023              for (i = 1; i < arguments.length; ++i) {
34024                args.push(arguments[i]);
34025              }
34026              length = callbacks.length;
34027              for (i = 0; i < length; ++i) {
34028                callbacks[i].apply(this, args);
34029              }
34030            }
34031          };
34032          /**
34033           * Destroys the stream and cleans up.
34034           */
34035          this.dispose = function () {
34036            listeners = {};
34037          };
34038        };
34039      };
34040
34041      /**
34042       * Forwards all `data` events on this stream to the destination stream. The
34043       * destination stream should provide a method `push` to receive the data
34044       * events as they arrive.
34045       * @param destination {stream} the stream that will receive all `data` events
34046       * @param autoFlush {boolean} if false, we will not call `flush` on the destination
34047       *                            when the current stream emits a 'done' event
34048       * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
34049       */
34050      Stream.prototype.pipe = function (destination) {
34051        this.on('data', function (data) {
34052          destination.push(data);
34053        });
34054
34055        this.on('done', function (flushSource) {
34056          destination.flush(flushSource);
34057        });
34058
34059        return destination;
34060      };
34061
34062      // Default stream functions that are expected to be overridden to perform
34063      // actual work. These are provided by the prototype as a sort of no-op
34064      // implementation so that we don't have to check for their existence in the
34065      // `pipe` function above.
34066      Stream.prototype.push = function (data) {
34067        this.trigger('data', data);
34068      };
34069
34070      Stream.prototype.flush = function (flushSource) {
34071        this.trigger('done', flushSource);
34072      };
34073
34074      var stream = Stream;
34075
34076      // -----------------
34077      // Link To Transport
34078      // -----------------
34079
34080      // Supplemental enhancement information (SEI) NAL units have a
34081      // payload type field to indicate how they are to be
34082      // interpreted. CEAS-708 caption content is always transmitted with
34083      // payload type 0x04.
34084      var USER_DATA_REGISTERED_ITU_T_T35 = 4,
34085          RBSP_TRAILING_BITS = 128;
34086
34087      /**
34088        * Parse a supplemental enhancement information (SEI) NAL unit.
34089        * Stops parsing once a message of type ITU T T35 has been found.
34090        *
34091        * @param bytes {Uint8Array} the bytes of a SEI NAL unit
34092        * @return {object} the parsed SEI payload
34093        * @see Rec. ITU-T H.264, 7.3.2.3.1
34094        */
34095      var parseSei = function parseSei(bytes) {
34096        var i = 0,
34097            result = {
34098          payloadType: -1,
34099          payloadSize: 0
34100        },
34101            payloadType = 0,
34102            payloadSize = 0;
34103
34104        // go through the sei_rbsp parsing each each individual sei_message
34105        while (i < bytes.byteLength) {
34106          // stop once we have hit the end of the sei_rbsp
34107          if (bytes[i] === RBSP_TRAILING_BITS) {
34108            break;
34109          }
34110
34111          // Parse payload type
34112          while (bytes[i] === 0xFF) {
34113            payloadType += 255;
34114            i++;
34115          }
34116          payloadType += bytes[i++];
34117
34118          // Parse payload size
34119          while (bytes[i] === 0xFF) {
34120            payloadSize += 255;
34121            i++;
34122          }
34123          payloadSize += bytes[i++];
34124
34125          // this sei_message is a 608/708 caption so save it and break
34126          // there can only ever be one caption message in a frame's sei
34127          if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) {
34128            result.payloadType = payloadType;
34129            result.payloadSize = payloadSize;
34130            result.payload = bytes.subarray(i, i + payloadSize);
34131            break;
34132          }
34133
34134          // skip the payload and parse the next message
34135          i += payloadSize;
34136          payloadType = 0;
34137          payloadSize = 0;
34138        }
34139
34140        return result;
34141      };
34142
34143      // see ANSI/SCTE 128-1 (2013), section 8.1
34144      var parseUserData = function parseUserData(sei) {
34145        // itu_t_t35_contry_code must be 181 (United States) for
34146        // captions
34147        if (sei.payload[0] !== 181) {
34148          return null;
34149        }
34150
34151        // itu_t_t35_provider_code should be 49 (ATSC) for captions
34152        if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) {
34153          return null;
34154        }
34155
34156        // the user_identifier should be "GA94" to indicate ATSC1 data
34157        if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') {
34158          return null;
34159        }
34160
34161        // finally, user_data_type_code should be 0x03 for caption data
34162        if (sei.payload[7] !== 0x03) {
34163          return null;
34164        }
34165
34166        // return the user_data_type_structure and strip the trailing
34167        // marker bits
34168        return sei.payload.subarray(8, sei.payload.length - 1);
34169      };
34170
34171      // see CEA-708-D, section 4.4
34172      var parseCaptionPackets = function parseCaptionPackets(pts, userData) {
34173        var results = [],
34174            i,
34175            count,
34176            offset,
34177            data;
34178
34179        // if this is just filler, return immediately
34180        if (!(userData[0] & 0x40)) {
34181          return results;
34182        }
34183
34184        // parse out the cc_data_1 and cc_data_2 fields
34185        count = userData[0] & 0x1f;
34186        for (i = 0; i < count; i++) {
34187          offset = i * 3;
34188          data = {
34189            type: userData[offset + 2] & 0x03,
34190            pts: pts
34191          };
34192
34193          // capture cc data when cc_valid is 1
34194          if (userData[offset + 2] & 0x04) {
34195            data.ccData = userData[offset + 3] << 8 | userData[offset + 4];
34196            results.push(data);
34197          }
34198        }
34199        return results;
34200      };
34201
34202      var CaptionStream = function CaptionStream() {
34203
34204        CaptionStream.prototype.init.call(this);
34205
34206        this.captionPackets_ = [];
34207
34208        this.ccStreams_ = [new Cea608Stream(0, 0), // eslint-disable-line no-use-before-define
34209        new Cea608Stream(0, 1), // eslint-disable-line no-use-before-define
34210        new Cea608Stream(1, 0), // eslint-disable-line no-use-before-define
34211        new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define
34212        ];
34213
34214        this.reset();
34215
34216        // forward data and done events from CCs to this CaptionStream
34217        this.ccStreams_.forEach(function (cc) {
34218          cc.on('data', this.trigger.bind(this, 'data'));
34219          cc.on('done', this.trigger.bind(this, 'done'));
34220        }, this);
34221      };
34222
34223      CaptionStream.prototype = new stream();
34224      CaptionStream.prototype.push = function (event) {
34225        var sei, userData;
34226
34227        // only examine SEI NALs
34228        if (event.nalUnitType !== 'sei_rbsp') {
34229          return;
34230        }
34231
34232        // parse the sei
34233        sei = parseSei(event.escapedRBSP);
34234
34235        // ignore everything but user_data_registered_itu_t_t35
34236        if (sei.payloadType !== USER_DATA_REGISTERED_ITU_T_T35) {
34237          return;
34238        }
34239
34240        // parse out the user data payload
34241        userData = parseUserData(sei);
34242
34243        // ignore unrecognized userData
34244        if (!userData) {
34245          return;
34246        }
34247
34248        // Sometimes, the same segment # will be downloaded twice. To stop the
34249        // caption data from being processed twice, we track the latest dts we've
34250        // received and ignore everything with a dts before that. However, since
34251        // data for a specific dts can be split across packets on either side of
34252        // a segment boundary, we need to make sure we *don't* ignore the packets
34253        // from the *next* segment that have dts === this.latestDts_. By constantly
34254        // tracking the number of packets received with dts === this.latestDts_, we
34255        // know how many should be ignored once we start receiving duplicates.
34256        if (event.dts < this.latestDts_) {
34257          // We've started getting older data, so set the flag.
34258          this.ignoreNextEqualDts_ = true;
34259          return;
34260        } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) {
34261          this.numSameDts_--;
34262          if (!this.numSameDts_) {
34263            // We've received the last duplicate packet, time to start processing again
34264            this.ignoreNextEqualDts_ = false;
34265          }
34266          return;
34267        }
34268
34269        // parse out CC data packets and save them for later
34270        this.captionPackets_ = this.captionPackets_.concat(parseCaptionPackets(event.pts, userData));
34271        if (this.latestDts_ !== event.dts) {
34272          this.numSameDts_ = 0;
34273        }
34274        this.numSameDts_++;
34275        this.latestDts_ = event.dts;
34276      };
34277
34278      CaptionStream.prototype.flush = function () {
34279        // make sure we actually parsed captions before proceeding
34280        if (!this.captionPackets_.length) {
34281          this.ccStreams_.forEach(function (cc) {
34282            cc.flush();
34283          }, this);
34284          return;
34285        }
34286
34287        // In Chrome, the Array#sort function is not stable so add a
34288        // presortIndex that we can use to ensure we get a stable-sort
34289        this.captionPackets_.forEach(function (elem, idx) {
34290          elem.presortIndex = idx;
34291        });
34292
34293        // sort caption byte-pairs based on their PTS values
34294        this.captionPackets_.sort(function (a, b) {
34295          if (a.pts === b.pts) {
34296            return a.presortIndex - b.presortIndex;
34297          }
34298          return a.pts - b.pts;
34299        });
34300
34301        this.captionPackets_.forEach(function (packet) {
34302          if (packet.type < 2) {
34303            // Dispatch packet to the right Cea608Stream
34304            this.dispatchCea608Packet(packet);
34305          }
34306          // this is where an 'else' would go for a dispatching packets
34307          // to a theoretical Cea708Stream that handles SERVICEn data
34308        }, this);
34309
34310        this.captionPackets_.length = 0;
34311        this.ccStreams_.forEach(function (cc) {
34312          cc.flush();
34313        }, this);
34314        return;
34315      };
34316
34317      CaptionStream.prototype.reset = function () {
34318        this.latestDts_ = null;
34319        this.ignoreNextEqualDts_ = false;
34320        this.numSameDts_ = 0;
34321        this.activeCea608Channel_ = [null, null];
34322        this.ccStreams_.forEach(function (ccStream) {
34323          ccStream.reset();
34324        });
34325      };
34326
34327      CaptionStream.prototype.dispatchCea608Packet = function (packet) {
34328        // NOTE: packet.type is the CEA608 field
34329        if (this.setsChannel1Active(packet)) {
34330          this.activeCea608Channel_[packet.type] = 0;
34331        } else if (this.setsChannel2Active(packet)) {
34332          this.activeCea608Channel_[packet.type] = 1;
34333        }
34334        if (this.activeCea608Channel_[packet.type] === null) {
34335          // If we haven't received anything to set the active channel, discard the
34336          // data; we don't want jumbled captions
34337          return;
34338        }
34339        this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet);
34340      };
34341
34342      CaptionStream.prototype.setsChannel1Active = function (packet) {
34343        return (packet.ccData & 0x7800) === 0x1000;
34344      };
34345      CaptionStream.prototype.setsChannel2Active = function (packet) {
34346        return (packet.ccData & 0x7800) === 0x1800;
34347      };
34348
34349      // ----------------------
34350      // Session to Application
34351      // ----------------------
34352
34353      var CHARACTER_TRANSLATION = {
34354        0x2a: 0xe1, // á
34355        0x5c: 0xe9, // é
34356        0x5e: 0xed, // í
34357        0x5f: 0xf3, // ó
34358        0x60: 0xfa, // ú
34359        0x7b: 0xe7, // ç
34360        0x7c: 0xf7, // ÷
34361        0x7d: 0xd1, // Ñ
34362        0x7e: 0xf1, // ñ
34363        0x7f: 0x2588, // █
34364        0x0130: 0xae, // ®
34365        0x0131: 0xb0, // °
34366        0x0132: 0xbd, // ½
34367        0x0133: 0xbf, // ¿
34368        0x0134: 0x2122, // ™
34369        0x0135: 0xa2, // ¢
34370        0x0136: 0xa3, // £
34371        0x0137: 0x266a, // ♪
34372        0x0138: 0xe0, // à
34373        0x0139: 0xa0, //
34374        0x013a: 0xe8, // è
34375        0x013b: 0xe2, // â
34376        0x013c: 0xea, // ê
34377        0x013d: 0xee, // î
34378        0x013e: 0xf4, // ô
34379        0x013f: 0xfb, // û
34380        0x0220: 0xc1, // Á
34381        0x0221: 0xc9, // É
34382        0x0222: 0xd3, // Ó
34383        0x0223: 0xda, // Ú
34384        0x0224: 0xdc, // Ü
34385        0x0225: 0xfc, // ü
34386        0x0226: 0x2018, // ‘
34387        0x0227: 0xa1, // ¡
34388        0x0228: 0x2a, // *
34389        0x0229: 0x27, // '
34390        0x022a: 0x2014, // —
34391        0x022b: 0xa9, // ©
34392        0x022c: 0x2120, // ℠
34393        0x022d: 0x2022, // •
34394        0x022e: 0x201c, // “
34395        0x022f: 0x201d, // ”
34396        0x0230: 0xc0, // À
34397        0x0231: 0xc2, // Â
34398        0x0232: 0xc7, // Ç
34399        0x0233: 0xc8, // È
34400        0x0234: 0xca, // Ê
34401        0x0235: 0xcb, // Ë
34402        0x0236: 0xeb, // ë
34403        0x0237: 0xce, // Î
34404        0x0238: 0xcf, // Ï
34405        0x0239: 0xef, // ï
34406        0x023a: 0xd4, // Ô
34407        0x023b: 0xd9, // Ù
34408        0x023c: 0xf9, // ù
34409        0x023d: 0xdb, // Û
34410        0x023e: 0xab, // «
34411        0x023f: 0xbb, // »
34412        0x0320: 0xc3, // Ã
34413        0x0321: 0xe3, // ã
34414        0x0322: 0xcd, // Í
34415        0x0323: 0xcc, // Ì
34416        0x0324: 0xec, // ì
34417        0x0325: 0xd2, // Ò
34418        0x0326: 0xf2, // ò
34419        0x0327: 0xd5, // Õ
34420        0x0328: 0xf5, // õ
34421        0x0329: 0x7b, // {
34422        0x032a: 0x7d, // }
34423        0x032b: 0x5c, // \
34424        0x032c: 0x5e, // ^
34425        0x032d: 0x5f, // _
34426        0x032e: 0x7c, // |
34427        0x032f: 0x7e, // ~
34428        0x0330: 0xc4, // Ä
34429        0x0331: 0xe4, // ä
34430        0x0332: 0xd6, // Ö
34431        0x0333: 0xf6, // ö
34432        0x0334: 0xdf, // ß
34433        0x0335: 0xa5, // ¥
34434        0x0336: 0xa4, // ¤
34435        0x0337: 0x2502, // │
34436        0x0338: 0xc5, // Å
34437        0x0339: 0xe5, // å
34438        0x033a: 0xd8, // Ø
34439        0x033b: 0xf8, // ø
34440        0x033c: 0x250c, // ┌
34441        0x033d: 0x2510, // ┐
34442        0x033e: 0x2514, // └
34443        0x033f: 0x2518 // ┘
34444      };
34445
34446      var getCharFromCode = function getCharFromCode(code) {
34447        if (code === null) {
34448          return '';
34449        }
34450        code = CHARACTER_TRANSLATION[code] || code;
34451        return String.fromCharCode(code);
34452      };
34453
34454      // the index of the last row in a CEA-608 display buffer
34455      var BOTTOM_ROW = 14;
34456
34457      // This array is used for mapping PACs -> row #, since there's no way of
34458      // getting it through bit logic.
34459      var ROWS = [0x1100, 0x1120, 0x1200, 0x1220, 0x1500, 0x1520, 0x1600, 0x1620, 0x1700, 0x1720, 0x1000, 0x1300, 0x1320, 0x1400, 0x1420];
34460
34461      // CEA-608 captions are rendered onto a 34x15 matrix of character
34462      // cells. The "bottom" row is the last element in the outer array.
34463      var createDisplayBuffer = function createDisplayBuffer() {
34464        var result = [],
34465            i = BOTTOM_ROW + 1;
34466        while (i--) {
34467          result.push('');
34468        }
34469        return result;
34470      };
34471
34472      var Cea608Stream = function Cea608Stream(field, dataChannel) {
34473        Cea608Stream.prototype.init.call(this);
34474
34475        this.field_ = field || 0;
34476        this.dataChannel_ = dataChannel || 0;
34477
34478        this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1);
34479
34480        this.setConstants();
34481        this.reset();
34482
34483        this.push = function (packet) {
34484          var data, swap, char0, char1, text;
34485          // remove the parity bits
34486          data = packet.ccData & 0x7f7f;
34487
34488          // ignore duplicate control codes; the spec demands they're sent twice
34489          if (data === this.lastControlCode_) {
34490            this.lastControlCode_ = null;
34491            return;
34492          }
34493
34494          // Store control codes
34495          if ((data & 0xf000) === 0x1000) {
34496            this.lastControlCode_ = data;
34497          } else if (data !== this.PADDING_) {
34498            this.lastControlCode_ = null;
34499          }
34500
34501          char0 = data >>> 8;
34502          char1 = data & 0xff;
vendor: 4,605 bytes, lines 34503-34604
34503
34504          if (data === this.PADDING_) {
34505            return;
34506          } else if (data === this.RESUME_CAPTION_LOADING_) {
34507            this.mode_ = 'popOn';
34508          } else if (data === this.END_OF_CAPTION_) {
34509            this.clearFormatting(packet.pts);
34510            // if a caption was being displayed, it's gone now
34511            this.flushDisplayed(packet.pts);
34512
34513            // flip memory
34514            swap = this.displayed_;
34515            this.displayed_ = this.nonDisplayed_;
34516            this.nonDisplayed_ = swap;
34517
34518            // start measuring the time to display the caption
34519            this.startPts_ = packet.pts;
34520          } else if (data === this.ROLL_UP_2_ROWS_) {
34521            this.topRow_ = BOTTOM_ROW - 1;
34522            this.mode_ = 'rollUp';
34523          } else if (data === this.ROLL_UP_3_ROWS_) {
34524            this.topRow_ = BOTTOM_ROW - 2;
34525            this.mode_ = 'rollUp';
34526          } else if (data === this.ROLL_UP_4_ROWS_) {
34527            this.topRow_ = BOTTOM_ROW - 3;
34528            this.mode_ = 'rollUp';
34529          } else if (data === this.CARRIAGE_RETURN_) {
34530            this.clearFormatting(packet.pts);
34531            this.flushDisplayed(packet.pts);
34532            this.shiftRowsUp_();
34533            this.startPts_ = packet.pts;
34534          } else if (data === this.BACKSPACE_) {
34535            if (this.mode_ === 'popOn') {
34536              this.nonDisplayed_[BOTTOM_ROW] = this.nonDisplayed_[BOTTOM_ROW].slice(0, -1);
34537            } else {
34538              this.displayed_[BOTTOM_ROW] = this.displayed_[BOTTOM_ROW].slice(0, -1);
34539            }
34540          } else if (data === this.ERASE_DISPLAYED_MEMORY_) {
34541            this.flushDisplayed(packet.pts);
34542            this.displayed_ = createDisplayBuffer();
34543          } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) {
34544            this.nonDisplayed_ = createDisplayBuffer();
34545          } else if (data === this.RESUME_DIRECT_CAPTIONING_) {
34546            this.mode_ = 'paintOn';
34547
34548            // Append special characters to caption text
34549          } else if (this.isSpecialCharacter(char0, char1)) {
34550            // Bitmask char0 so that we can apply character transformations
34551            // regardless of field and data channel.
34552            // Then byte-shift to the left and OR with char1 so we can pass the
34553            // entire character code to `getCharFromCode`.
34554            char0 = (char0 & 0x03) << 8;
34555            text = getCharFromCode(char0 | char1);
34556            this[this.mode_](packet.pts, text);
34557            this.column_++;
34558
34559            // Append extended characters to caption text
34560          } else if (this.isExtCharacter(char0, char1)) {
34561            // Extended characters always follow their "non-extended" equivalents.
34562            // IE if a "è" is desired, you'll always receive "eè"; non-compliant
34563            // decoders are supposed to drop the "è", while compliant decoders
34564            // backspace the "e" and insert "è".
34565
34566            // Delete the previous character
34567            if (this.mode_ === 'popOn') {
34568              this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
34569            } else {
34570              this.displayed_[BOTTOM_ROW] = this.displayed_[BOTTOM_ROW].slice(0, -1);
34571            }
34572
34573            // Bitmask char0 so that we can apply character transformations
34574            // regardless of field and data channel.
34575            // Then byte-shift to the left and OR with char1 so we can pass the
34576            // entire character code to `getCharFromCode`.
34577            char0 = (char0 & 0x03) << 8;
34578            text = getCharFromCode(char0 | char1);
34579            this[this.mode_](packet.pts, text);
34580            this.column_++;
34581
34582            // Process mid-row codes
34583          } else if (this.isMidRowCode(char0, char1)) {
34584            // Attributes are not additive, so clear all formatting
34585            this.clearFormatting(packet.pts);
34586
34587            // According to the standard, mid-row codes
34588            // should be replaced with spaces, so add one now
34589            this[this.mode_](packet.pts, ' ');
34590            this.column_++;
34591
34592            if ((char1 & 0xe) === 0xe) {
34593              this.addFormatting(packet.pts, ['i']);
34594            }
34595
34596            if ((char1 & 0x1) === 0x1) {
34597              this.addFormatting(packet.pts, ['u']);
34598            }
34599
34600            // Detect offset control codes and adjust cursor
34601          } else if (this.isOffsetControlCode(char0, char1)) {
34602            // Cursor position is set by indent PAC (see below) in 4-column
34603            // increments, with an additional offset code of 1-3 to reach any
34604            // of the 32 columns specified by CEA-608. So all we need to do
vendor: 97,669 bytes, lines 34605-37275
34605            // here is increment the column cursor by the given offset.
34606            this.column_ += char1 & 0x03;
34607
34608            // Detect PACs (Preamble Address Codes)
34609          } else if (this.isPAC(char0, char1)) {
34610
34611            // There's no logic for PAC -> row mapping, so we have to just
34612            // find the row code in an array and use its index :(
34613            var row = ROWS.indexOf(data & 0x1f20);
34614
34615            if (row !== this.row_) {
34616              // formatting is only persistent for current row
34617              this.clearFormatting(packet.pts);
34618              this.row_ = row;
34619            }
34620            // All PACs can apply underline, so detect and apply
34621            // (All odd-numbered second bytes set underline)
34622            if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) {
34623              this.addFormatting(packet.pts, ['u']);
34624            }
34625
34626            if ((data & 0x10) === 0x10) {
34627              // We've got an indent level code. Each successive even number
34628              // increments the column cursor by 4, so we can get the desired
34629              // column position by bit-shifting to the right (to get n/2)
34630              // and multiplying by 4.
34631              this.column_ = ((data & 0xe) >> 1) * 4;
34632            }
34633
34634            if (this.isColorPAC(char1)) {
34635              // it's a color code, though we only support white, which
34636              // can be either normal or italicized. white italics can be
34637              // either 0x4e or 0x6e depending on the row, so we just
34638              // bitwise-and with 0xe to see if italics should be turned on
34639              if ((char1 & 0xe) === 0xe) {
34640                this.addFormatting(packet.pts, ['i']);
34641              }
34642            }
34643
34644            // We have a normal character in char0, and possibly one in char1
34645          } else if (this.isNormalChar(char0)) {
34646            if (char1 === 0x00) {
34647              char1 = null;
34648            }
34649            text = getCharFromCode(char0);
34650            text += getCharFromCode(char1);
34651            this[this.mode_](packet.pts, text);
34652            this.column_ += text.length;
34653          } // finish data processing
34654        };
34655      };
34656      Cea608Stream.prototype = new stream();
34657      // Trigger a cue point that captures the current state of the
34658      // display buffer
34659      Cea608Stream.prototype.flushDisplayed = function (pts) {
34660        var content = this.displayed_
34661        // remove spaces from the start and end of the string
34662        .map(function (row) {
34663          return row.trim();
34664        })
34665        // combine all text rows to display in one cue
34666        .join('\n')
34667        // and remove blank rows from the start and end, but not the middle
34668        .replace(/^\n+|\n+$/g, '');
34669
34670        if (content.length) {
34671          this.trigger('data', {
34672            startPts: this.startPts_,
34673            endPts: pts,
34674            text: content,
34675            stream: this.name_
34676          });
34677        }
34678      };
34679
34680      /**
34681       * Zero out the data, used for startup and on seek
34682       */
34683      Cea608Stream.prototype.reset = function () {
34684        this.mode_ = 'popOn';
34685        // When in roll-up mode, the index of the last row that will
34686        // actually display captions. If a caption is shifted to a row
34687        // with a lower index than this, it is cleared from the display
34688        // buffer
34689        this.topRow_ = 0;
34690        this.startPts_ = 0;
34691        this.displayed_ = createDisplayBuffer();
34692        this.nonDisplayed_ = createDisplayBuffer();
34693        this.lastControlCode_ = null;
34694
34695        // Track row and column for proper line-breaking and spacing
34696        this.column_ = 0;
34697        this.row_ = BOTTOM_ROW;
34698
34699        // This variable holds currently-applied formatting
34700        this.formatting_ = [];
34701      };
34702
34703      /**
34704       * Sets up control code and related constants for this instance
34705       */
34706      Cea608Stream.prototype.setConstants = function () {
34707        // The following attributes have these uses:
34708        // ext_ :    char0 for mid-row codes, and the base for extended
34709        //           chars (ext_+0, ext_+1, and ext_+2 are char0s for
34710        //           extended codes)
34711        // control_: char0 for control codes, except byte-shifted to the
34712        //           left so that we can do this.control_ | CONTROL_CODE
34713        // offset_:  char0 for tab offset codes
34714        //
34715        // It's also worth noting that control codes, and _only_ control codes,
34716        // differ between field 1 and field2. Field 2 control codes are always
34717        // their field 1 value plus 1. That's why there's the "| field" on the
34718        // control value.
34719        if (this.dataChannel_ === 0) {
34720          this.BASE_ = 0x10;
34721          this.EXT_ = 0x11;
34722          this.CONTROL_ = (0x14 | this.field_) << 8;
34723          this.OFFSET_ = 0x17;
34724        } else if (this.dataChannel_ === 1) {
34725          this.BASE_ = 0x18;
34726          this.EXT_ = 0x19;
34727          this.CONTROL_ = (0x1c | this.field_) << 8;
34728          this.OFFSET_ = 0x1f;
34729        }
34730
34731        // Constants for the LSByte command codes recognized by Cea608Stream. This
34732        // list is not exhaustive. For a more comprehensive listing and semantics see
34733        // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf
34734        // Padding
34735        this.PADDING_ = 0x0000;
34736        // Pop-on Mode
34737        this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20;
34738        this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f;
34739        // Roll-up Mode
34740        this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25;
34741        this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26;
34742        this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27;
34743        this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d;
34744        // paint-on mode (not supported)
34745        this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29;
34746        // Erasure
34747        this.BACKSPACE_ = this.CONTROL_ | 0x21;
34748        this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c;
34749        this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e;
34750      };
34751
34752      /**
34753       * Detects if the 2-byte packet data is a special character
34754       *
34755       * Special characters have a second byte in the range 0x30 to 0x3f,
34756       * with the first byte being 0x11 (for data channel 1) or 0x19 (for
34757       * data channel 2).
34758       *
34759       * @param  {Integer} char0 The first byte
34760       * @param  {Integer} char1 The second byte
34761       * @return {Boolean}       Whether the 2 bytes are an special character
34762       */
34763      Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) {
34764        return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f;
34765      };
34766
34767      /**
34768       * Detects if the 2-byte packet data is an extended character
34769       *
34770       * Extended characters have a second byte in the range 0x20 to 0x3f,
34771       * with the first byte being 0x12 or 0x13 (for data channel 1) or
34772       * 0x1a or 0x1b (for data channel 2).
34773       *
34774       * @param  {Integer} char0 The first byte
34775       * @param  {Integer} char1 The second byte
34776       * @return {Boolean}       Whether the 2 bytes are an extended character
34777       */
34778      Cea608Stream.prototype.isExtCharacter = function (char0, char1) {
34779        return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f;
34780      };
34781
34782      /**
34783       * Detects if the 2-byte packet is a mid-row code
34784       *
34785       * Mid-row codes have a second byte in the range 0x20 to 0x2f, with
34786       * the first byte being 0x11 (for data channel 1) or 0x19 (for data
34787       * channel 2).
34788       *
34789       * @param  {Integer} char0 The first byte
34790       * @param  {Integer} char1 The second byte
34791       * @return {Boolean}       Whether the 2 bytes are a mid-row code
34792       */
34793      Cea608Stream.prototype.isMidRowCode = function (char0, char1) {
34794        return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f;
34795      };
34796
34797      /**
34798       * Detects if the 2-byte packet is an offset control code
34799       *
34800       * Offset control codes have a second byte in the range 0x21 to 0x23,
34801       * with the first byte being 0x17 (for data channel 1) or 0x1f (for
34802       * data channel 2).
34803       *
34804       * @param  {Integer} char0 The first byte
34805       * @param  {Integer} char1 The second byte
34806       * @return {Boolean}       Whether the 2 bytes are an offset control code
34807       */
34808      Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) {
34809        return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23;
34810      };
34811
34812      /**
34813       * Detects if the 2-byte packet is a Preamble Address Code
34814       *
34815       * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1)
34816       * or 0x18 to 0x1f (for data channel 2), with the second byte in the
34817       * range 0x40 to 0x7f.
34818       *
34819       * @param  {Integer} char0 The first byte
34820       * @param  {Integer} char1 The second byte
34821       * @return {Boolean}       Whether the 2 bytes are a PAC
34822       */
34823      Cea608Stream.prototype.isPAC = function (char0, char1) {
34824        return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f;
34825      };
34826
34827      /**
34828       * Detects if a packet's second byte is in the range of a PAC color code
34829       *
34830       * PAC color codes have the second byte be in the range 0x40 to 0x4f, or
34831       * 0x60 to 0x6f.
34832       *
34833       * @param  {Integer} char1 The second byte
34834       * @return {Boolean}       Whether the byte is a color PAC
34835       */
34836      Cea608Stream.prototype.isColorPAC = function (char1) {
34837        return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f;
34838      };
34839
34840      /**
34841       * Detects if a single byte is in the range of a normal character
34842       *
34843       * Normal text bytes are in the range 0x20 to 0x7f.
34844       *
34845       * @param  {Integer} char  The byte
34846       * @return {Boolean}       Whether the byte is a normal character
34847       */
34848      Cea608Stream.prototype.isNormalChar = function (char) {
34849        return char >= 0x20 && char <= 0x7f;
34850      };
34851
34852      // Adds the opening HTML tag for the passed character to the caption text,
34853      // and keeps track of it for later closing
34854      Cea608Stream.prototype.addFormatting = function (pts, format) {
34855        this.formatting_ = this.formatting_.concat(format);
34856        var text = format.reduce(function (text, format) {
34857          return text + '<' + format + '>';
34858        }, '');
34859        this[this.mode_](pts, text);
34860      };
34861
34862      // Adds HTML closing tags for current formatting to caption text and
34863      // clears remembered formatting
34864      Cea608Stream.prototype.clearFormatting = function (pts) {
34865        if (!this.formatting_.length) {
34866          return;
34867        }
34868        var text = this.formatting_.reverse().reduce(function (text, format) {
34869          return text + '</' + format + '>';
34870        }, '');
34871        this.formatting_ = [];
34872        this[this.mode_](pts, text);
34873      };
34874
34875      // Mode Implementations
34876      Cea608Stream.prototype.popOn = function (pts, text) {
34877        var baseRow = this.nonDisplayed_[this.row_];
34878
34879        // buffer characters
34880        baseRow += text;
34881        this.nonDisplayed_[this.row_] = baseRow;
34882      };
34883
34884      Cea608Stream.prototype.rollUp = function (pts, text) {
34885        var baseRow = this.displayed_[BOTTOM_ROW];
34886
34887        baseRow += text;
34888        this.displayed_[BOTTOM_ROW] = baseRow;
34889      };
34890
34891      Cea608Stream.prototype.shiftRowsUp_ = function () {
34892        var i;
34893        // clear out inactive rows
34894        for (i = 0; i < this.topRow_; i++) {
34895          this.displayed_[i] = '';
34896        }
34897        // shift displayed rows up
34898        for (i = this.topRow_; i < BOTTOM_ROW; i++) {
34899          this.displayed_[i] = this.displayed_[i + 1];
34900        }
34901        // clear out the bottom row
34902        this.displayed_[BOTTOM_ROW] = '';
34903      };
34904
34905      // paintOn mode is not implemented
34906      Cea608Stream.prototype.paintOn = function () {};
34907
34908      // exports
34909      var captionStream = {
34910        CaptionStream: CaptionStream,
34911        Cea608Stream: Cea608Stream
34912      };
34913
34914      var streamTypes = {
34915        H264_STREAM_TYPE: 0x1B,
34916        ADTS_STREAM_TYPE: 0x0F,
34917        METADATA_STREAM_TYPE: 0x15
34918      };
34919
34920      var MAX_TS = 8589934592;
34921
34922      var RO_THRESH = 4294967296;
34923
34924      var handleRollover = function handleRollover(value, reference) {
34925        var direction = 1;
34926
34927        if (value > reference) {
34928          // If the current timestamp value is greater than our reference timestamp and we detect a
34929          // timestamp rollover, this means the roll over is happening in the opposite direction.
34930          // Example scenario: Enter a long stream/video just after a rollover occurred. The reference
34931          // point will be set to a small number, e.g. 1. The user then seeks backwards over the
34932          // rollover point. In loading this segment, the timestamp values will be very large,
34933          // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust
34934          // the time stamp to be `value - 2^33`.
34935          direction = -1;
34936        }
34937
34938        // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will
34939        // cause an incorrect adjustment.
34940        while (Math.abs(reference - value) > RO_THRESH) {
34941          value += direction * MAX_TS;
34942        }
34943
34944        return value;
34945      };
34946
34947      var TimestampRolloverStream = function TimestampRolloverStream(type) {
34948        var lastDTS, referenceDTS;
34949
34950        TimestampRolloverStream.prototype.init.call(this);
34951
34952        this.type_ = type;
34953
34954        this.push = function (data) {
34955          if (data.type !== this.type_) {
34956            return;
34957          }
34958
34959          if (referenceDTS === undefined) {
34960            referenceDTS = data.dts;
34961          }
34962
34963          data.dts = handleRollover(data.dts, referenceDTS);
34964          data.pts = handleRollover(data.pts, referenceDTS);
34965
34966          lastDTS = data.dts;
34967
34968          this.trigger('data', data);
34969        };
34970
34971        this.flush = function () {
34972          referenceDTS = lastDTS;
34973          this.trigger('done');
34974        };
34975
34976        this.discontinuity = function () {
34977          referenceDTS = void 0;
34978          lastDTS = void 0;
34979        };
34980      };
34981
34982      TimestampRolloverStream.prototype = new stream();
34983
34984      var timestampRolloverStream = {
34985        TimestampRolloverStream: TimestampRolloverStream,
34986        handleRollover: handleRollover
34987      };
34988
34989      var percentEncode = function percentEncode(bytes, start, end) {
34990        var i,
34991            result = '';
34992        for (i = start; i < end; i++) {
34993          result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
34994        }
34995        return result;
34996      },
34997
34998
34999      // return the string representation of the specified byte range,
35000      // interpreted as UTf-8.
35001      parseUtf8 = function parseUtf8(bytes, start, end) {
35002        return decodeURIComponent(percentEncode(bytes, start, end));
35003      },
35004
35005
35006      // return the string representation of the specified byte range,
35007      // interpreted as ISO-8859-1.
35008      parseIso88591 = function parseIso88591(bytes, start, end) {
35009        return unescape(percentEncode(bytes, start, end)); // jshint ignore:line
35010      },
35011          parseSyncSafeInteger = function parseSyncSafeInteger(data) {
35012        return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
35013      },
35014          tagParsers = {
35015        TXXX: function TXXX(tag) {
35016          var i;
35017          if (tag.data[0] !== 3) {
35018            // ignore frames with unrecognized character encodings
35019            return;
35020          }
35021
35022          for (i = 1; i < tag.data.length; i++) {
35023            if (tag.data[i] === 0) {
35024              // parse the text fields
35025              tag.description = parseUtf8(tag.data, 1, i);
35026              // do not include the null terminator in the tag value
35027              tag.value = parseUtf8(tag.data, i + 1, tag.data.length).replace(/\0*$/, '');
35028              break;
35029            }
35030          }
35031          tag.data = tag.value;
35032        },
35033        WXXX: function WXXX(tag) {
35034          var i;
35035          if (tag.data[0] !== 3) {
35036            // ignore frames with unrecognized character encodings
35037            return;
35038          }
35039
35040          for (i = 1; i < tag.data.length; i++) {
35041            if (tag.data[i] === 0) {
35042              // parse the description and URL fields
35043              tag.description = parseUtf8(tag.data, 1, i);
35044              tag.url = parseUtf8(tag.data, i + 1, tag.data.length);
35045              break;
35046            }
35047          }
35048        },
35049        PRIV: function PRIV(tag) {
35050          var i;
35051
35052          for (i = 0; i < tag.data.length; i++) {
35053            if (tag.data[i] === 0) {
35054              // parse the description and URL fields
35055              tag.owner = parseIso88591(tag.data, 0, i);
35056              break;
35057            }
35058          }
35059          tag.privateData = tag.data.subarray(i + 1);
35060          tag.data = tag.privateData;
35061        }
35062      },
35063          _MetadataStream;
35064
35065      _MetadataStream = function MetadataStream(options) {
35066        var settings = {
35067          debug: !!(options && options.debug),
35068
35069          // the bytes of the program-level descriptor field in MP2T
35070          // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and
35071          // program element descriptors"
35072          descriptor: options && options.descriptor
35073        },
35074
35075
35076        // the total size in bytes of the ID3 tag being parsed
35077        tagSize = 0,
35078
35079
35080        // tag data that is not complete enough to be parsed
35081        buffer = [],
35082
35083
35084        // the total number of bytes currently in the buffer
35085        bufferSize = 0,
35086            i;
35087
35088        _MetadataStream.prototype.init.call(this);
35089
35090        // calculate the text track in-band metadata track dispatch type
35091        // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track
35092        this.dispatchType = streamTypes.METADATA_STREAM_TYPE.toString(16);
35093        if (settings.descriptor) {
35094          for (i = 0; i < settings.descriptor.length; i++) {
35095            this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2);
35096          }
35097        }
35098
35099        this.push = function (chunk) {
35100          var tag, frameStart, frameSize, frame, i, frameHeader;
35101          if (chunk.type !== 'timed-metadata') {
35102            return;
35103          }
35104
35105          // if data_alignment_indicator is set in the PES header,
35106          // we must have the start of a new ID3 tag. Assume anything
35107          // remaining in the buffer was malformed and throw it out
35108          if (chunk.dataAlignmentIndicator) {
35109            bufferSize = 0;
35110            buffer.length = 0;
35111          }
35112
35113          // ignore events that don't look like ID3 data
35114          if (buffer.length === 0 && (chunk.data.length < 10 || chunk.data[0] !== 'I'.charCodeAt(0) || chunk.data[1] !== 'D'.charCodeAt(0) || chunk.data[2] !== '3'.charCodeAt(0))) {
35115            if (settings.debug) {
35116              // eslint-disable-next-line no-console
35117              console.log('Skipping unrecognized metadata packet');
35118            }
35119            return;
35120          }
35121
35122          // add this chunk to the data we've collected so far
35123
35124          buffer.push(chunk);
35125          bufferSize += chunk.data.byteLength;
35126
35127          // grab the size of the entire frame from the ID3 header
35128          if (buffer.length === 1) {
35129            // the frame size is transmitted as a 28-bit integer in the
35130            // last four bytes of the ID3 header.
35131            // The most significant bit of each byte is dropped and the
35132            // results concatenated to recover the actual value.
35133            tagSize = parseSyncSafeInteger(chunk.data.subarray(6, 10));
35134
35135            // ID3 reports the tag size excluding the header but it's more
35136            // convenient for our comparisons to include it
35137            tagSize += 10;
35138          }
35139
35140          // if the entire frame has not arrived, wait for more data
35141          if (bufferSize < tagSize) {
35142            return;
35143          }
35144
35145          // collect the entire frame so it can be parsed
35146          tag = {
35147            data: new Uint8Array(tagSize),
35148            frames: [],
35149            pts: buffer[0].pts,
35150            dts: buffer[0].dts
35151          };
35152          for (i = 0; i < tagSize;) {
35153            tag.data.set(buffer[0].data.subarray(0, tagSize - i), i);
35154            i += buffer[0].data.byteLength;
35155            bufferSize -= buffer[0].data.byteLength;
35156            buffer.shift();
35157          }
35158
35159          // find the start of the first frame and the end of the tag
35160          frameStart = 10;
35161          if (tag.data[5] & 0x40) {
35162            // advance the frame start past the extended header
35163            frameStart += 4; // header size field
35164            frameStart += parseSyncSafeInteger(tag.data.subarray(10, 14));
35165
35166            // clip any padding off the end
35167            tagSize -= parseSyncSafeInteger(tag.data.subarray(16, 20));
35168          }
35169
35170          // parse one or more ID3 frames
35171          // http://id3.org/id3v2.3.0#ID3v2_frame_overview
35172          do {
35173            // determine the number of bytes in this frame
35174            frameSize = parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8));
35175            if (frameSize < 1) {
35176              // eslint-disable-next-line no-console
35177              return console.log('Malformed ID3 frame encountered. Skipping metadata parsing.');
35178            }
35179            frameHeader = String.fromCharCode(tag.data[frameStart], tag.data[frameStart + 1], tag.data[frameStart + 2], tag.data[frameStart + 3]);
35180
35181            frame = {
35182              id: frameHeader,
35183              data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10)
35184            };
35185            frame.key = frame.id;
35186            if (tagParsers[frame.id]) {
35187              tagParsers[frame.id](frame);
35188
35189              // handle the special PRIV frame used to indicate the start
35190              // time for raw AAC data
35191              if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') {
35192                var d = frame.data,
35193                    size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
35194
35195                size *= 4;
35196                size += d[7] & 0x03;
35197                frame.timeStamp = size;
35198                // in raw AAC, all subsequent data will be timestamped based
35199                // on the value of this frame
35200                // we couldn't have known the appropriate pts and dts before
35201                // parsing this ID3 tag so set those values now
35202                if (tag.pts === undefined && tag.dts === undefined) {
35203                  tag.pts = frame.timeStamp;
35204                  tag.dts = frame.timeStamp;
35205                }
35206                this.trigger('timestamp', frame);
35207              }
35208            }
35209            tag.frames.push(frame);
35210
35211            frameStart += 10; // advance past the frame header
35212            frameStart += frameSize; // advance past the frame body
35213          } while (frameStart < tagSize);
35214          this.trigger('data', tag);
35215        };
35216      };
35217      _MetadataStream.prototype = new stream();
35218
35219      var metadataStream = _MetadataStream;
35220
35221      var TimestampRolloverStream$1 = timestampRolloverStream.TimestampRolloverStream;
35222
35223      // object types
35224      var _TransportPacketStream, _TransportParseStream, _ElementaryStream;
35225
35226      // constants
35227      var MP2T_PACKET_LENGTH = 188,
35228
35229
35230      // bytes
35231      SYNC_BYTE = 0x47;
35232
35233      /**
35234       * Splits an incoming stream of binary data into MPEG-2 Transport
35235       * Stream packets.
35236       */
35237      _TransportPacketStream = function TransportPacketStream() {
35238        var buffer = new Uint8Array(MP2T_PACKET_LENGTH),
35239            bytesInBuffer = 0;
35240
35241        _TransportPacketStream.prototype.init.call(this);
35242
35243        // Deliver new bytes to the stream.
35244
35245        this.push = function (bytes) {
35246          var startIndex = 0,
35247              endIndex = MP2T_PACKET_LENGTH,
35248              everything;
35249
35250          // If there are bytes remaining from the last segment, prepend them to the
35251          // bytes that were pushed in
35252          if (bytesInBuffer) {
35253            everything = new Uint8Array(bytes.byteLength + bytesInBuffer);
35254            everything.set(buffer.subarray(0, bytesInBuffer));
35255            everything.set(bytes, bytesInBuffer);
35256            bytesInBuffer = 0;
35257          } else {
35258            everything = bytes;
35259          }
35260
35261          // While we have enough data for a packet
35262          while (endIndex < everything.byteLength) {
35263            // Look for a pair of start and end sync bytes in the data..
35264            if (everything[startIndex] === SYNC_BYTE && everything[endIndex] === SYNC_BYTE) {
35265              // We found a packet so emit it and jump one whole packet forward in
35266              // the stream
35267              this.trigger('data', everything.subarray(startIndex, endIndex));
35268              startIndex += MP2T_PACKET_LENGTH;
35269              endIndex += MP2T_PACKET_LENGTH;
35270              continue;
35271            }
35272            // If we get here, we have somehow become de-synchronized and we need to step
35273            // forward one byte at a time until we find a pair of sync bytes that denote
35274            // a packet
35275            startIndex++;
35276            endIndex++;
35277          }
35278
35279          // If there was some data left over at the end of the segment that couldn't
35280          // possibly be a whole packet, keep it because it might be the start of a packet
35281          // that continues in the next segment
35282          if (startIndex < everything.byteLength) {
35283            buffer.set(everything.subarray(startIndex), 0);
35284            bytesInBuffer = everything.byteLength - startIndex;
35285          }
35286        };
35287
35288        this.flush = function () {
35289          // If the buffer contains a whole packet when we are being flushed, emit it
35290          // and empty the buffer. Otherwise hold onto the data because it may be
35291          // important for decoding the next segment
35292          if (bytesInBuffer === MP2T_PACKET_LENGTH && buffer[0] === SYNC_BYTE) {
35293            this.trigger('data', buffer);
35294            bytesInBuffer = 0;
35295          }
35296          this.trigger('done');
35297        };
35298      };
35299      _TransportPacketStream.prototype = new stream();
35300
35301      /**
35302       * Accepts an MP2T TransportPacketStream and emits data events with parsed
35303       * forms of the individual transport stream packets.
35304       */
35305      _TransportParseStream = function TransportParseStream() {
35306        var parsePsi, parsePat, parsePmt, self;
35307        _TransportParseStream.prototype.init.call(this);
35308        self = this;
35309
35310        this.packetsWaitingForPmt = [];
35311        this.programMapTable = undefined;
35312
35313        parsePsi = function parsePsi(payload, psi) {
35314          var offset = 0;
35315
35316          // PSI packets may be split into multiple sections and those
35317          // sections may be split into multiple packets. If a PSI
35318          // section starts in this packet, the payload_unit_start_indicator
35319          // will be true and the first byte of the payload will indicate
35320          // the offset from the current position to the start of the
35321          // section.
35322          if (psi.payloadUnitStartIndicator) {
35323            offset += payload[offset] + 1;
35324          }
35325
35326          if (psi.type === 'pat') {
35327            parsePat(payload.subarray(offset), psi);
35328          } else {
35329            parsePmt(payload.subarray(offset), psi);
35330          }
35331        };
35332
35333        parsePat = function parsePat(payload, pat) {
35334          pat.section_number = payload[7]; // eslint-disable-line camelcase
35335          pat.last_section_number = payload[8]; // eslint-disable-line camelcase
35336
35337          // skip the PSI header and parse the first PMT entry
35338          self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11];
35339          pat.pmtPid = self.pmtPid;
35340        };
35341
35342        /**
35343         * Parse out the relevant fields of a Program Map Table (PMT).
35344         * @param payload {Uint8Array} the PMT-specific portion of an MP2T
35345         * packet. The first byte in this array should be the table_id
35346         * field.
35347         * @param pmt {object} the object that should be decorated with
35348         * fields parsed from the PMT.
35349         */
35350        parsePmt = function parsePmt(payload, pmt) {
35351          var sectionLength, tableEnd, programInfoLength, offset;
35352
35353          // PMTs can be sent ahead of the time when they should actually
35354          // take effect. We don't believe this should ever be the case
35355          // for HLS but we'll ignore "forward" PMT declarations if we see
35356          // them. Future PMT declarations have the current_next_indicator
35357          // set to zero.
35358          if (!(payload[5] & 0x01)) {
35359            return;
35360          }
35361
35362          // overwrite any existing program map table
35363          self.programMapTable = {
35364            video: null,
35365            audio: null,
35366            'timed-metadata': {}
35367          };
35368
35369          // the mapping table ends at the end of the current section
35370          sectionLength = (payload[1] & 0x0f) << 8 | payload[2];
35371          tableEnd = 3 + sectionLength - 4;
35372
35373          // to determine where the table is, we have to figure out how
35374          // long the program info descriptors are
35375          programInfoLength = (payload[10] & 0x0f) << 8 | payload[11];
35376
35377          // advance the offset to the first entry in the mapping table
35378          offset = 12 + programInfoLength;
35379          while (offset < tableEnd) {
35380            var streamType = payload[offset];
35381            var pid = (payload[offset + 1] & 0x1F) << 8 | payload[offset + 2];
35382
35383            // only map a single elementary_pid for audio and video stream types
35384            // TODO: should this be done for metadata too? for now maintain behavior of
35385            //       multiple metadata streams
35386            if (streamType === streamTypes.H264_STREAM_TYPE && self.programMapTable.video === null) {
35387              self.programMapTable.video = pid;
35388            } else if (streamType === streamTypes.ADTS_STREAM_TYPE && self.programMapTable.audio === null) {
35389              self.programMapTable.audio = pid;
35390            } else if (streamType === streamTypes.METADATA_STREAM_TYPE) {
35391              // map pid to stream type for metadata streams
35392              self.programMapTable['timed-metadata'][pid] = streamType;
35393            }
35394
35395            // move to the next table entry
35396            // skip past the elementary stream descriptors, if present
35397            offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5;
35398          }
35399
35400          // record the map on the packet as well
35401          pmt.programMapTable = self.programMapTable;
35402        };
35403
35404        /**
35405         * Deliver a new MP2T packet to the stream.
35406         */
35407        this.push = function (packet) {
35408          var result = {},
35409              offset = 4;
35410
35411          result.payloadUnitStartIndicator = !!(packet[1] & 0x40);
35412
35413          // pid is a 13-bit field starting at the last bit of packet[1]
35414          result.pid = packet[1] & 0x1f;
35415          result.pid <<= 8;
35416          result.pid |= packet[2];
35417
35418          // if an adaption field is present, its length is specified by the
35419          // fifth byte of the TS packet header. The adaptation field is
35420          // used to add stuffing to PES packets that don't fill a complete
35421          // TS packet, and to specify some forms of timing and control data
35422          // that we do not currently use.
35423          if ((packet[3] & 0x30) >>> 4 > 0x01) {
35424            offset += packet[offset] + 1;
35425          }
35426
35427          // parse the rest of the packet based on the type
35428          if (result.pid === 0) {
35429            result.type = 'pat';
35430            parsePsi(packet.subarray(offset), result);
35431            this.trigger('data', result);
35432          } else if (result.pid === this.pmtPid) {
35433            result.type = 'pmt';
35434            parsePsi(packet.subarray(offset), result);
35435            this.trigger('data', result);
35436
35437            // if there are any packets waiting for a PMT to be found, process them now
35438            while (this.packetsWaitingForPmt.length) {
35439              this.processPes_.apply(this, this.packetsWaitingForPmt.shift());
35440            }
35441          } else if (this.programMapTable === undefined) {
35442            // When we have not seen a PMT yet, defer further processing of
35443            // PES packets until one has been parsed
35444            this.packetsWaitingForPmt.push([packet, offset, result]);
35445          } else {
35446            this.processPes_(packet, offset, result);
35447          }
35448        };
35449
35450        this.processPes_ = function (packet, offset, result) {
35451          // set the appropriate stream type
35452          if (result.pid === this.programMapTable.video) {
35453            result.streamType = streamTypes.H264_STREAM_TYPE;
35454          } else if (result.pid === this.programMapTable.audio) {
35455            result.streamType = streamTypes.ADTS_STREAM_TYPE;
35456          } else {
35457            // if not video or audio, it is timed-metadata or unknown
35458            // if unknown, streamType will be undefined
35459            result.streamType = this.programMapTable['timed-metadata'][result.pid];
35460          }
35461
35462          result.type = 'pes';
35463          result.data = packet.subarray(offset);
35464
35465          this.trigger('data', result);
35466        };
35467      };
35468      _TransportParseStream.prototype = new stream();
35469      _TransportParseStream.STREAM_TYPES = {
35470        h264: 0x1b,
35471        adts: 0x0f
35472      };
35473
35474      /**
35475       * Reconsistutes program elementary stream (PES) packets from parsed
35476       * transport stream packets. That is, if you pipe an
35477       * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output
35478       * events will be events which capture the bytes for individual PES
35479       * packets plus relevant metadata that has been extracted from the
35480       * container.
35481       */
35482      _ElementaryStream = function ElementaryStream() {
35483        var self = this,
35484
35485
35486        // PES packet fragments
35487        video = {
35488          data: [],
35489          size: 0
35490        },
35491            audio = {
35492          data: [],
35493          size: 0
35494        },
35495            timedMetadata = {
35496          data: [],
35497          size: 0
35498        },
35499            parsePes = function parsePes(payload, pes) {
35500          var ptsDtsFlags;
35501
35502          // get the packet length, this will be 0 for video
35503          pes.packetLength = 6 + (payload[4] << 8 | payload[5]);
35504
35505          // find out if this packets starts a new keyframe
35506          pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0;
35507          // PES packets may be annotated with a PTS value, or a PTS value
35508          // and a DTS value. Determine what combination of values is
35509          // available to work with.
35510          ptsDtsFlags = payload[7];
35511
35512          // PTS and DTS are normally stored as a 33-bit number.  Javascript
35513          // performs all bitwise operations on 32-bit integers but javascript
35514          // supports a much greater range (52-bits) of integer using standard
35515          // mathematical operations.
35516          // We construct a 31-bit value using bitwise operators over the 31
35517          // most significant bits and then multiply by 4 (equal to a left-shift
35518          // of 2) before we add the final 2 least significant bits of the
35519          // timestamp (equal to an OR.)
35520          if (ptsDtsFlags & 0xC0) {
35521            // the PTS and DTS are not written out directly. For information
35522            // on how they are encoded, see
35523            // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
35524            pes.pts = (payload[9] & 0x0E) << 27 | (payload[10] & 0xFF) << 20 | (payload[11] & 0xFE) << 12 | (payload[12] & 0xFF) << 5 | (payload[13] & 0xFE) >>> 3;
35525            pes.pts *= 4; // Left shift by 2
35526            pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs
35527            pes.dts = pes.pts;
35528            if (ptsDtsFlags & 0x40) {
35529              pes.dts = (payload[14] & 0x0E) << 27 | (payload[15] & 0xFF) << 20 | (payload[16] & 0xFE) << 12 | (payload[17] & 0xFF) << 5 | (payload[18] & 0xFE) >>> 3;
35530              pes.dts *= 4; // Left shift by 2
35531              pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs
35532            }
35533          }
35534          // the data section starts immediately after the PES header.
35535          // pes_header_data_length specifies the number of header bytes
35536          // that follow the last byte of the field.
35537          pes.data = payload.subarray(9 + payload[8]);
35538        },
35539            flushStream = function flushStream(stream$$1, type, forceFlush) {
35540          var packetData = new Uint8Array(stream$$1.size),
35541              event = {
35542            type: type
35543          },
35544              i = 0,
35545              offset = 0,
35546              packetFlushable = false,
35547              fragment;
35548
35549          // do nothing if there is not enough buffered data for a complete
35550          // PES header
35551          if (!stream$$1.data.length || stream$$1.size < 9) {
35552            return;
35553          }
35554          event.trackId = stream$$1.data[0].pid;
35555
35556          // reassemble the packet
35557          for (i = 0; i < stream$$1.data.length; i++) {
35558            fragment = stream$$1.data[i];
35559
35560            packetData.set(fragment.data, offset);
35561            offset += fragment.data.byteLength;
35562          }
35563
35564          // parse assembled packet's PES header
35565          parsePes(packetData, event);
35566
35567          // non-video PES packets MUST have a non-zero PES_packet_length
35568          // check that there is enough stream data to fill the packet
35569          packetFlushable = type === 'video' || event.packetLength <= stream$$1.size;
35570
35571          // flush pending packets if the conditions are right
35572          if (forceFlush || packetFlushable) {
35573            stream$$1.size = 0;
35574            stream$$1.data.length = 0;
35575          }
35576
35577          // only emit packets that are complete. this is to avoid assembling
35578          // incomplete PES packets due to poor segmentation
35579          if (packetFlushable) {
35580            self.trigger('data', event);
35581          }
35582        };
35583
35584        _ElementaryStream.prototype.init.call(this);
35585
35586        this.push = function (data) {
35587          ({
35588            pat: function pat() {
35589              // we have to wait for the PMT to arrive as well before we
35590              // have any meaningful metadata
35591            },
35592            pes: function pes() {
35593              var stream$$1, streamType;
35594
35595              switch (data.streamType) {
35596                case streamTypes.H264_STREAM_TYPE:
35597                case streamTypes.H264_STREAM_TYPE:
35598                  stream$$1 = video;
35599                  streamType = 'video';
35600                  break;
35601                case streamTypes.ADTS_STREAM_TYPE:
35602                  stream$$1 = audio;
35603                  streamType = 'audio';
35604                  break;
35605                case streamTypes.METADATA_STREAM_TYPE:
35606                  stream$$1 = timedMetadata;
35607                  streamType = 'timed-metadata';
35608                  break;
35609                default:
35610                  // ignore unknown stream types
35611                  return;
35612              }
35613
35614              // if a new packet is starting, we can flush the completed
35615              // packet
35616              if (data.payloadUnitStartIndicator) {
35617                flushStream(stream$$1, streamType, true);
35618              }
35619
35620              // buffer this fragment until we are sure we've received the
35621              // complete payload
35622              stream$$1.data.push(data);
35623              stream$$1.size += data.data.byteLength;
35624            },
35625            pmt: function pmt() {
35626              var event = {
35627                type: 'metadata',
35628                tracks: []
35629              },
35630                  programMapTable = data.programMapTable;
35631
35632              // translate audio and video streams to tracks
35633              if (programMapTable.video !== null) {
35634                event.tracks.push({
35635                  timelineStartInfo: {
35636                    baseMediaDecodeTime: 0
35637                  },
35638                  id: +programMapTable.video,
35639                  codec: 'avc',
35640                  type: 'video'
35641                });
35642              }
35643              if (programMapTable.audio !== null) {
35644                event.tracks.push({
35645                  timelineStartInfo: {
35646                    baseMediaDecodeTime: 0
35647                  },
35648                  id: +programMapTable.audio,
35649                  codec: 'adts',
35650                  type: 'audio'
35651                });
35652              }
35653
35654              self.trigger('data', event);
35655            }
35656          })[data.type]();
35657        };
35658
35659        /**
35660         * Flush any remaining input. Video PES packets may be of variable
35661         * length. Normally, the start of a new video packet can trigger the
35662         * finalization of the previous packet. That is not possible if no
35663         * more video is forthcoming, however. In that case, some other
35664         * mechanism (like the end of the file) has to be employed. When it is
35665         * clear that no additional data is forthcoming, calling this method
35666         * will flush the buffered packets.
35667         */
35668        this.flush = function () {
35669          // !!THIS ORDER IS IMPORTANT!!
35670          // video first then audio
35671          flushStream(video, 'video');
35672          flushStream(audio, 'audio');
35673          flushStream(timedMetadata, 'timed-metadata');
35674          this.trigger('done');
35675        };
35676      };
35677      _ElementaryStream.prototype = new stream();
35678
35679      var m2ts = {
35680        PAT_PID: 0x0000,
35681        MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH,
35682        TransportPacketStream: _TransportPacketStream,
35683        TransportParseStream: _TransportParseStream,
35684        ElementaryStream: _ElementaryStream,
35685        TimestampRolloverStream: TimestampRolloverStream$1,
35686        CaptionStream: captionStream.CaptionStream,
35687        Cea608Stream: captionStream.Cea608Stream,
35688        MetadataStream: metadataStream
35689      };
35690
35691      for (var type in streamTypes) {
35692        if (streamTypes.hasOwnProperty(type)) {
35693          m2ts[type] = streamTypes[type];
35694        }
35695      }
35696
35697      var m2ts_1 = m2ts;
35698
35699      var _AdtsStream;
35700
35701      var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
35702
35703      /*
35704       * Accepts a ElementaryStream and emits data events with parsed
35705       * AAC Audio Frames of the individual packets. Input audio in ADTS
35706       * format is unpacked and re-emitted as AAC frames.
35707       *
35708       * @see http://wiki.multimedia.cx/index.php?title=ADTS
35709       * @see http://wiki.multimedia.cx/?title=Understanding_AAC
35710       */
35711      _AdtsStream = function AdtsStream() {
35712        var buffer;
35713
35714        _AdtsStream.prototype.init.call(this);
35715
35716        this.push = function (packet) {
35717          var i = 0,
35718              frameNum = 0,
35719              frameLength,
35720              protectionSkipBytes,
35721              frameEnd,
35722              oldBuffer,
35723              sampleCount,
35724              adtsFrameDuration;
35725
35726          if (packet.type !== 'audio') {
35727            // ignore non-audio data
35728            return;
35729          }
35730
35731          // Prepend any data in the buffer to the input data so that we can parse
35732          // aac frames the cross a PES packet boundary
35733          if (buffer) {
35734            oldBuffer = buffer;
35735            buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength);
35736            buffer.set(oldBuffer);
35737            buffer.set(packet.data, oldBuffer.byteLength);
35738          } else {
35739            buffer = packet.data;
35740          }
35741
35742          // unpack any ADTS frames which have been fully received
35743          // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS
35744          while (i + 5 < buffer.length) {
35745
35746            // Loook for the start of an ADTS header..
35747            if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) {
35748              // If a valid header was not found,  jump one forward and attempt to
35749              // find a valid ADTS header starting at the next byte
35750              i++;
35751              continue;
35752            }
35753
35754            // The protection skip bit tells us if we have 2 bytes of CRC data at the
35755            // end of the ADTS header
35756            protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2;
35757
35758            // Frame length is a 13 bit integer starting 16 bits from the
35759            // end of the sync sequence
35760            frameLength = (buffer[i + 3] & 0x03) << 11 | buffer[i + 4] << 3 | (buffer[i + 5] & 0xe0) >> 5;
35761
35762            sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024;
35763            adtsFrameDuration = sampleCount * 90000 / ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2];
35764
35765            frameEnd = i + frameLength;
35766
35767            // If we don't have enough data to actually finish this ADTS frame, return
35768            // and wait for more data
35769            if (buffer.byteLength < frameEnd) {
35770              return;
35771            }
35772
35773            // Otherwise, deliver the complete AAC frame
35774            this.trigger('data', {
35775              pts: packet.pts + frameNum * adtsFrameDuration,
35776              dts: packet.dts + frameNum * adtsFrameDuration,
35777              sampleCount: sampleCount,
35778              audioobjecttype: (buffer[i + 2] >>> 6 & 0x03) + 1,
35779              channelcount: (buffer[i + 2] & 1) << 2 | (buffer[i + 3] & 0xc0) >>> 6,
35780              samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2],
35781              samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2,
35782              // assume ISO/IEC 14496-12 AudioSampleEntry default of 16
35783              samplesize: 16,
35784              data: buffer.subarray(i + 7 + protectionSkipBytes, frameEnd)
35785            });
35786
35787            // If the buffer is empty, clear it and return
35788            if (buffer.byteLength === frameEnd) {
35789              buffer = undefined;
35790              return;
35791            }
35792
35793            frameNum++;
35794
35795            // Remove the finished frame from the buffer and start the process again
35796            buffer = buffer.subarray(frameEnd);
35797          }
35798        };
35799        this.flush = function () {
35800          this.trigger('done');
35801        };
35802      };
35803
35804      _AdtsStream.prototype = new stream();
35805
35806      var adts = _AdtsStream;
35807
35808      var ExpGolomb;
35809
35810      /**
35811       * Parser for exponential Golomb codes, a variable-bitwidth number encoding
35812       * scheme used by h264.
35813       */
35814      ExpGolomb = function ExpGolomb(workingData) {
35815        var
35816        // the number of bytes left to examine in workingData
35817        workingBytesAvailable = workingData.byteLength,
35818
35819
35820        // the current word being examined
35821        workingWord = 0,
35822
35823
35824        // :uint
35825
35826        // the number of bits left to examine in the current word
35827        workingBitsAvailable = 0; // :uint;
35828
35829        // ():uint
35830        this.length = function () {
35831          return 8 * workingBytesAvailable;
35832        };
35833
35834        // ():uint
35835        this.bitsAvailable = function () {
35836          return 8 * workingBytesAvailable + workingBitsAvailable;
35837        };
35838
35839        // ():void
35840        this.loadWord = function () {
35841          var position = workingData.byteLength - workingBytesAvailable,
35842              workingBytes = new Uint8Array(4),
35843              availableBytes = Math.min(4, workingBytesAvailable);
35844
35845          if (availableBytes === 0) {
35846            throw new Error('no bytes available');
35847          }
35848
35849          workingBytes.set(workingData.subarray(position, position + availableBytes));
35850          workingWord = new DataView(workingBytes.buffer).getUint32(0);
35851
35852          // track the amount of workingData that has been processed
35853          workingBitsAvailable = availableBytes * 8;
35854          workingBytesAvailable -= availableBytes;
35855        };
35856
35857        // (count:int):void
35858        this.skipBits = function (count) {
35859          var skipBytes; // :int
35860          if (workingBitsAvailable > count) {
35861            workingWord <<= count;
35862            workingBitsAvailable -= count;
35863          } else {
35864            count -= workingBitsAvailable;
35865            skipBytes = Math.floor(count / 8);
35866
35867            count -= skipBytes * 8;
35868            workingBytesAvailable -= skipBytes;
35869
35870            this.loadWord();
35871
35872            workingWord <<= count;
35873            workingBitsAvailable -= count;
35874          }
35875        };
35876
35877        // (size:int):uint
35878        this.readBits = function (size) {
35879          var bits = Math.min(workingBitsAvailable, size),
35880
35881
35882          // :uint
35883          valu = workingWord >>> 32 - bits; // :uint
35884          // if size > 31, handle error
35885          workingBitsAvailable -= bits;
35886          if (workingBitsAvailable > 0) {
35887            workingWord <<= bits;
35888          } else if (workingBytesAvailable > 0) {
35889            this.loadWord();
35890          }
35891
35892          bits = size - bits;
35893          if (bits > 0) {
35894            return valu << bits | this.readBits(bits);
35895          }
35896          return valu;
35897        };
35898
35899        // ():uint
35900        this.skipLeadingZeros = function () {
35901          var leadingZeroCount; // :uint
35902          for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) {
35903            if ((workingWord & 0x80000000 >>> leadingZeroCount) !== 0) {
35904              // the first bit of working word is 1
35905              workingWord <<= leadingZeroCount;
35906              workingBitsAvailable -= leadingZeroCount;
35907              return leadingZeroCount;
35908            }
35909          }
35910
35911          // we exhausted workingWord and still have not found a 1
35912          this.loadWord();
35913          return leadingZeroCount + this.skipLeadingZeros();
35914        };
35915
35916        // ():void
35917        this.skipUnsignedExpGolomb = function () {
35918          this.skipBits(1 + this.skipLeadingZeros());
35919        };
35920
35921        // ():void
35922        this.skipExpGolomb = function () {
35923          this.skipBits(1 + this.skipLeadingZeros());
35924        };
35925
35926        // ():uint
35927        this.readUnsignedExpGolomb = function () {
35928          var clz = this.skipLeadingZeros(); // :uint
35929          return this.readBits(clz + 1) - 1;
35930        };
35931
35932        // ():int
35933        this.readExpGolomb = function () {
35934          var valu = this.readUnsignedExpGolomb(); // :int
35935          if (0x01 & valu) {
35936            // the number is odd if the low order bit is set
35937            return 1 + valu >>> 1; // add 1 to make it even, and divide by 2
35938          }
35939          return -1 * (valu >>> 1); // divide by two then make it negative
35940        };
35941
35942        // Some convenience functions
35943        // :Boolean
35944        this.readBoolean = function () {
35945          return this.readBits(1) === 1;
35946        };
35947
35948        // ():int
35949        this.readUnsignedByte = function () {
35950          return this.readBits(8);
35951        };
35952
35953        this.loadWord();
35954      };
35955
35956      var expGolomb = ExpGolomb;
35957
35958      var _H264Stream, _NalByteStream;
35959      var PROFILES_WITH_OPTIONAL_SPS_DATA;
35960
35961      /**
35962       * Accepts a NAL unit byte stream and unpacks the embedded NAL units.
35963       */
35964      _NalByteStream = function NalByteStream() {
35965        var syncPoint = 0,
35966            i,
35967            buffer;
35968        _NalByteStream.prototype.init.call(this);
35969
35970        this.push = function (data) {
35971          var swapBuffer;
35972
35973          if (!buffer) {
35974            buffer = data.data;
35975          } else {
35976            swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength);
35977            swapBuffer.set(buffer);
35978            swapBuffer.set(data.data, buffer.byteLength);
35979            buffer = swapBuffer;
35980          }
35981
35982          // Rec. ITU-T H.264, Annex B
35983          // scan for NAL unit boundaries
35984
35985          // a match looks like this:
35986          // 0 0 1 .. NAL .. 0 0 1
35987          // ^ sync point        ^ i
35988          // or this:
35989          // 0 0 1 .. NAL .. 0 0 0
35990          // ^ sync point        ^ i
35991
35992          // advance the sync point to a NAL start, if necessary
35993          for (; syncPoint < buffer.byteLength - 3; syncPoint++) {
35994            if (buffer[syncPoint + 2] === 1) {
35995              // the sync point is properly aligned
35996              i = syncPoint + 5;
35997              break;
35998            }
35999          }
36000
36001          while (i < buffer.byteLength) {
36002            // look at the current byte to determine if we've hit the end of
36003            // a NAL unit boundary
36004            switch (buffer[i]) {
36005              case 0:
36006                // skip past non-sync sequences
36007                if (buffer[i - 1] !== 0) {
36008                  i += 2;
36009                  break;
36010                } else if (buffer[i - 2] !== 0) {
36011                  i++;
36012                  break;
36013                }
36014
36015                // deliver the NAL unit if it isn't empty
36016                if (syncPoint + 3 !== i - 2) {
36017                  this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
36018                }
36019
36020                // drop trailing zeroes
36021                do {
36022                  i++;
36023                } while (buffer[i] !== 1 && i < buffer.length);
36024                syncPoint = i - 2;
36025                i += 3;
36026                break;
36027              case 1:
36028                // skip past non-sync sequences
36029                if (buffer[i - 1] !== 0 || buffer[i - 2] !== 0) {
36030                  i += 3;
36031                  break;
36032                }
36033
36034                // deliver the NAL unit
36035                this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
36036                syncPoint = i - 2;
36037                i += 3;
36038                break;
36039              default:
36040                // the current byte isn't a one or zero, so it cannot be part
36041                // of a sync sequence
36042                i += 3;
36043                break;
36044            }
36045          }
36046          // filter out the NAL units that were delivered
36047          buffer = buffer.subarray(syncPoint);
36048          i -= syncPoint;
36049          syncPoint = 0;
36050        };
36051
36052        this.flush = function () {
36053          // deliver the last buffered NAL unit
36054          if (buffer && buffer.byteLength > 3) {
36055            this.trigger('data', buffer.subarray(syncPoint + 3));
36056          }
36057          // reset the stream state
36058          buffer = null;
36059          syncPoint = 0;
36060          this.trigger('done');
36061        };
36062      };
36063      _NalByteStream.prototype = new stream();
36064
36065      // values of profile_idc that indicate additional fields are included in the SPS
36066      // see Recommendation ITU-T H.264 (4/2013),
36067      // 7.3.2.1.1 Sequence parameter set data syntax
36068      PROFILES_WITH_OPTIONAL_SPS_DATA = {
36069        100: true,
36070        110: true,
36071        122: true,
36072        244: true,
36073        44: true,
36074        83: true,
36075        86: true,
36076        118: true,
36077        128: true,
36078        138: true,
36079        139: true,
36080        134: true
36081      };
36082
36083      /**
36084       * Accepts input from a ElementaryStream and produces H.264 NAL unit data
36085       * events.
36086       */
36087      _H264Stream = function H264Stream() {
36088        var nalByteStream = new _NalByteStream(),
36089            self,
36090            trackId,
36091            currentPts,
36092            currentDts,
36093            discardEmulationPreventionBytes,
36094            readSequenceParameterSet,
36095            skipScalingList;
36096
36097        _H264Stream.prototype.init.call(this);
36098        self = this;
36099
36100        this.push = function (packet) {
36101          if (packet.type !== 'video') {
36102            return;
36103          }
36104          trackId = packet.trackId;
36105          currentPts = packet.pts;
36106          currentDts = packet.dts;
36107
36108          nalByteStream.push(packet);
36109        };
36110
36111        nalByteStream.on('data', function (data) {
36112          var event = {
36113            trackId: trackId,
36114            pts: currentPts,
36115            dts: currentDts,
36116            data: data
36117          };
36118
36119          switch (data[0] & 0x1f) {
36120            case 0x05:
36121              event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr';
36122              break;
36123            case 0x06:
36124              event.nalUnitType = 'sei_rbsp';
36125              event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
36126              break;
36127            case 0x07:
36128              event.nalUnitType = 'seq_parameter_set_rbsp';
36129              event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
36130              event.config = readSequenceParameterSet(event.escapedRBSP);
36131              break;
36132            case 0x08:
36133              event.nalUnitType = 'pic_parameter_set_rbsp';
36134              break;
36135            case 0x09:
36136              event.nalUnitType = 'access_unit_delimiter_rbsp';
36137              break;
36138
36139            default:
36140              break;
36141          }
36142          self.trigger('data', event);
36143        });
36144        nalByteStream.on('done', function () {
36145          self.trigger('done');
36146        });
36147
36148        this.flush = function () {
36149          nalByteStream.flush();
36150        };
36151
36152        /**
36153         * Advance the ExpGolomb decoder past a scaling list. The scaling
36154         * list is optionally transmitted as part of a sequence parameter
36155         * set and is not relevant to transmuxing.
36156         * @param count {number} the number of entries in this scaling list
36157         * @param expGolombDecoder {object} an ExpGolomb pointed to the
36158         * start of a scaling list
36159         * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
36160         */
36161        skipScalingList = function skipScalingList(count, expGolombDecoder) {
36162          var lastScale = 8,
36163              nextScale = 8,
36164              j,
36165              deltaScale;
36166
36167          for (j = 0; j < count; j++) {
36168            if (nextScale !== 0) {
36169              deltaScale = expGolombDecoder.readExpGolomb();
36170              nextScale = (lastScale + deltaScale + 256) % 256;
36171            }
36172
36173            lastScale = nextScale === 0 ? lastScale : nextScale;
36174          }
36175        };
36176
36177        /**
36178         * Expunge any "Emulation Prevention" bytes from a "Raw Byte
36179         * Sequence Payload"
36180         * @param data {Uint8Array} the bytes of a RBSP from a NAL
36181         * unit
36182         * @return {Uint8Array} the RBSP without any Emulation
36183         * Prevention Bytes
36184         */
36185        discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
36186          var length = data.byteLength,
36187              emulationPreventionBytesPositions = [],
36188              i = 1,
36189              newLength,
36190              newData;
36191
36192          // Find all `Emulation Prevention Bytes`
36193          while (i < length - 2) {
36194            if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
36195              emulationPreventionBytesPositions.push(i + 2);
36196              i += 2;
36197            } else {
36198              i++;
36199            }
36200          }
36201
36202          // If no Emulation Prevention Bytes were found just return the original
36203          // array
36204          if (emulationPreventionBytesPositions.length === 0) {
36205            return data;
36206          }
36207
36208          // Create a new array to hold the NAL unit data
36209          newLength = length - emulationPreventionBytesPositions.length;
36210          newData = new Uint8Array(newLength);
36211          var sourceIndex = 0;
36212
36213          for (i = 0; i < newLength; sourceIndex++, i++) {
36214            if (sourceIndex === emulationPreventionBytesPositions[0]) {
36215              // Skip this byte
36216              sourceIndex++;
36217              // Remove this position index
36218              emulationPreventionBytesPositions.shift();
36219            }
36220            newData[i] = data[sourceIndex];
36221          }
36222
36223          return newData;
36224        };
36225
36226        /**
36227         * Read a sequence parameter set and return some interesting video
36228         * properties. A sequence parameter set is the H264 metadata that
36229         * describes the properties of upcoming video frames.
36230         * @param data {Uint8Array} the bytes of a sequence parameter set
36231         * @return {object} an object with configuration parsed from the
36232         * sequence parameter set, including the dimensions of the
36233         * associated video frames.
36234         */
36235        readSequenceParameterSet = function readSequenceParameterSet(data) {
36236          var frameCropLeftOffset = 0,
36237              frameCropRightOffset = 0,
36238              frameCropTopOffset = 0,
36239              frameCropBottomOffset = 0,
36240              sarScale = 1,
36241              expGolombDecoder,
36242              profileIdc,
36243              levelIdc,
36244              profileCompatibility,
36245              chromaFormatIdc,
36246              picOrderCntType,
36247              numRefFramesInPicOrderCntCycle,
36248              picWidthInMbsMinus1,
36249              picHeightInMapUnitsMinus1,
36250              frameMbsOnlyFlag,
36251              scalingListCount,
36252              sarRatio,
36253              aspectRatioIdc,
36254              i;
36255
36256          expGolombDecoder = new expGolomb(data);
36257          profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc
36258          profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag
36259          levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8)
36260          expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id
36261
36262          // some profiles have more optional data we don't need
36263          if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) {
36264            chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb();
36265            if (chromaFormatIdc === 3) {
36266              expGolombDecoder.skipBits(1); // separate_colour_plane_flag
36267            }
36268            expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
36269            expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
36270            expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag
36271            if (expGolombDecoder.readBoolean()) {
36272              // seq_scaling_matrix_present_flag
36273              scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
36274              for (i = 0; i < scalingListCount; i++) {
36275                if (expGolombDecoder.readBoolean()) {
36276                  // seq_scaling_list_present_flag[ i ]
36277                  if (i < 6) {
36278                    skipScalingList(16, expGolombDecoder);
36279                  } else {
36280                    skipScalingList(64, expGolombDecoder);
36281                  }
36282                }
36283              }
36284            }
36285          }
36286
36287          expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
36288          picOrderCntType = expGolombDecoder.readUnsignedExpGolomb();
36289
36290          if (picOrderCntType === 0) {
36291            expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4
36292          } else if (picOrderCntType === 1) {
36293            expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag
36294            expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic
36295            expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field
36296            numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb();
36297            for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
36298              expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ]
36299            }
36300          }
36301
36302          expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames
36303          expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag
36304
36305          picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
36306          picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
36307
36308          frameMbsOnlyFlag = expGolombDecoder.readBits(1);
36309          if (frameMbsOnlyFlag === 0) {
36310            expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag
36311          }
36312
36313          expGolombDecoder.skipBits(1); // direct_8x8_inference_flag
36314          if (expGolombDecoder.readBoolean()) {
36315            // frame_cropping_flag
36316            frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb();
36317            frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb();
36318            frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb();
36319            frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb();
36320          }
36321          if (expGolombDecoder.readBoolean()) {
36322            // vui_parameters_present_flag
36323            if (expGolombDecoder.readBoolean()) {
36324              // aspect_ratio_info_present_flag
36325              aspectRatioIdc = expGolombDecoder.readUnsignedByte();
36326              switch (aspectRatioIdc) {
36327                case 1:
36328                  sarRatio = [1, 1];break;
36329                case 2:
36330                  sarRatio = [12, 11];break;
36331                case 3:
36332                  sarRatio = [10, 11];break;
36333                case 4:
36334                  sarRatio = [16, 11];break;
36335                case 5:
36336                  sarRatio = [40, 33];break;
36337                case 6:
36338                  sarRatio = [24, 11];break;
36339                case 7:
36340                  sarRatio = [20, 11];break;
36341                case 8:
36342                  sarRatio = [32, 11];break;
36343                case 9:
36344                  sarRatio = [80, 33];break;
36345                case 10:
36346                  sarRatio = [18, 11];break;
36347                case 11:
36348                  sarRatio = [15, 11];break;
36349                case 12:
36350                  sarRatio = [64, 33];break;
36351                case 13:
36352                  sarRatio = [160, 99];break;
36353                case 14:
36354                  sarRatio = [4, 3];break;
36355                case 15:
36356                  sarRatio = [3, 2];break;
36357                case 16:
36358                  sarRatio = [2, 1];break;
36359                case 255:
36360                  {
36361                    sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte(), expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte()];
36362                    break;
36363                  }
36364              }
36365              if (sarRatio) {
36366                sarScale = sarRatio[0] / sarRatio[1];
36367              }
36368            }
36369          }
36370          return {
36371            profileIdc: profileIdc,
36372            levelIdc: levelIdc,
36373            profileCompatibility: profileCompatibility,
36374            width: Math.ceil(((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale),
36375            height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - frameCropTopOffset * 2 - frameCropBottomOffset * 2
36376          };
36377        };
36378      };
36379      _H264Stream.prototype = new stream();
36380
36381      var h264 = {
36382        H264Stream: _H264Stream,
36383        NalByteStream: _NalByteStream
36384      };
36385
36386      // Constants
36387      var _AacStream;
36388
36389      /**
36390       * Splits an incoming stream of binary data into ADTS and ID3 Frames.
36391       */
36392
36393      _AacStream = function AacStream() {
36394        var everything = new Uint8Array(),
36395            timeStamp = 0;
36396
36397        _AacStream.prototype.init.call(this);
36398
36399        this.setTimestamp = function (timestamp) {
36400          timeStamp = timestamp;
36401        };
36402
36403        this.parseId3TagSize = function (header, byteIndex) {
36404          var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9],
36405              flags = header[byteIndex + 5],
36406              footerPresent = (flags & 16) >> 4;
36407
36408          if (footerPresent) {
36409            return returnSize + 20;
36410          }
36411          return returnSize + 10;
36412        };
36413
36414        this.parseAdtsSize = function (header, byteIndex) {
36415          var lowThree = (header[byteIndex + 5] & 0xE0) >> 5,
36416              middle = header[byteIndex + 4] << 3,
36417              highTwo = header[byteIndex + 3] & 0x3 << 11;
36418
36419          return highTwo | middle | lowThree;
36420        };
36421
36422        this.push = function (bytes) {
36423          var frameSize = 0,
36424              byteIndex = 0,
36425              bytesLeft,
36426              chunk,
36427              packet,
36428              tempLength;
36429
36430          // If there are bytes remaining from the last segment, prepend them to the
36431          // bytes that were pushed in
36432          if (everything.length) {
36433            tempLength = everything.length;
36434            everything = new Uint8Array(bytes.byteLength + tempLength);
36435            everything.set(everything.subarray(0, tempLength));
36436            everything.set(bytes, tempLength);
36437          } else {
36438            everything = bytes;
36439          }
36440
36441          while (everything.length - byteIndex >= 3) {
36442            if (everything[byteIndex] === 'I'.charCodeAt(0) && everything[byteIndex + 1] === 'D'.charCodeAt(0) && everything[byteIndex + 2] === '3'.charCodeAt(0)) {
36443
36444              // Exit early because we don't have enough to parse
36445              // the ID3 tag header
36446              if (everything.length - byteIndex < 10) {
36447                break;
36448              }
36449
36450              // check framesize
36451              frameSize = this.parseId3TagSize(everything, byteIndex);
36452
36453              // Exit early if we don't have enough in the buffer
36454              // to emit a full packet
36455              if (frameSize > everything.length) {
36456                break;
36457              }
36458              chunk = {
36459                type: 'timed-metadata',
36460                data: everything.subarray(byteIndex, byteIndex + frameSize)
36461              };
36462              this.trigger('data', chunk);
36463              byteIndex += frameSize;
36464              continue;
36465            } else if (everything[byteIndex] & 0xff === 0xff && (everything[byteIndex + 1] & 0xf0) === 0xf0) {
36466
36467              // Exit early because we don't have enough to parse
36468              // the ADTS frame header
36469              if (everything.length - byteIndex < 7) {
36470                break;
36471              }
36472
36473              frameSize = this.parseAdtsSize(everything, byteIndex);
36474
36475              // Exit early if we don't have enough in the buffer
36476              // to emit a full packet
36477              if (frameSize > everything.length) {
36478                break;
36479              }
36480
36481              packet = {
36482                type: 'audio',
36483                data: everything.subarray(byteIndex, byteIndex + frameSize),
36484                pts: timeStamp,
36485                dts: timeStamp
36486              };
36487              this.trigger('data', packet);
36488              byteIndex += frameSize;
36489              continue;
36490            }
36491            byteIndex++;
36492          }
36493          bytesLeft = everything.length - byteIndex;
36494
36495          if (bytesLeft > 0) {
36496            everything = everything.subarray(byteIndex);
36497          } else {
36498            everything = new Uint8Array();
36499          }
36500        };
36501      };
36502
36503      _AacStream.prototype = new stream();
36504
36505      var aac = _AacStream;
36506
36507      var highPrefix = [33, 16, 5, 32, 164, 27];
36508      var lowPrefix = [33, 65, 108, 84, 1, 2, 4, 8, 168, 2, 4, 8, 17, 191, 252];
36509      var zeroFill = function zeroFill(count) {
36510        var a = [];
36511        while (count--) {
36512          a.push(0);
36513        }
36514        return a;
36515      };
36516
36517      var makeTable = function makeTable(metaTable) {
36518        return Object.keys(metaTable).reduce(function (obj, key) {
36519          obj[key] = new Uint8Array(metaTable[key].reduce(function (arr, part) {
36520            return arr.concat(part);
36521          }, []));
36522          return obj;
36523        }, {});
36524      };
36525
36526      // Frames-of-silence to use for filling in missing AAC frames
36527      var coneOfSilence = {
36528        96000: [highPrefix, [227, 64], zeroFill(154), [56]],
36529        88200: [highPrefix, [231], zeroFill(170), [56]],
36530        64000: [highPrefix, [248, 192], zeroFill(240), [56]],
36531        48000: [highPrefix, [255, 192], zeroFill(268), [55, 148, 128], zeroFill(54), [112]],
36532        44100: [highPrefix, [255, 192], zeroFill(268), [55, 163, 128], zeroFill(84), [112]],
36533        32000: [highPrefix, [255, 192], zeroFill(268), [55, 234], zeroFill(226), [112]],
36534        24000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 112], zeroFill(126), [224]],
36535        16000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 255], zeroFill(269), [223, 108], zeroFill(195), [1, 192]],
36536        12000: [lowPrefix, zeroFill(268), [3, 127, 248], zeroFill(268), [6, 255, 240], zeroFill(268), [13, 255, 224], zeroFill(268), [27, 253, 128], zeroFill(259), [56]],
36537        11025: [lowPrefix, zeroFill(268), [3, 127, 248], zeroFill(268), [6, 255, 240], zeroFill(268), [13, 255, 224], zeroFill(268), [27, 255, 192], zeroFill(268), [55, 175, 128], zeroFill(108), [112]],
36538        8000: [lowPrefix, zeroFill(268), [3, 121, 16], zeroFill(47), [7]]
36539      };
36540
36541      var silence = makeTable(coneOfSilence);
36542
36543      var ONE_SECOND_IN_TS = 90000,
36544
36545
36546      // 90kHz clock
36547      secondsToVideoTs,
36548          secondsToAudioTs,
36549          videoTsToSeconds,
36550          audioTsToSeconds,
36551          audioTsToVideoTs,
36552          videoTsToAudioTs;
36553
36554      secondsToVideoTs = function secondsToVideoTs(seconds) {
36555        return seconds * ONE_SECOND_IN_TS;
36556      };
36557
36558      secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) {
36559        return seconds * sampleRate;
36560      };
36561
36562      videoTsToSeconds = function videoTsToSeconds(timestamp) {
36563        return timestamp / ONE_SECOND_IN_TS;
36564      };
36565
36566      audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) {
36567        return timestamp / sampleRate;
36568      };
36569
36570      audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) {
36571        return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate));
36572      };
36573
36574      videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) {
36575        return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate);
36576      };
36577
36578      var clock = {
36579        secondsToVideoTs: secondsToVideoTs,
36580        secondsToAudioTs: secondsToAudioTs,
36581        videoTsToSeconds: videoTsToSeconds,
36582        audioTsToSeconds: audioTsToSeconds,
36583        audioTsToVideoTs: audioTsToVideoTs,
36584        videoTsToAudioTs: videoTsToAudioTs
36585      };
36586
36587      var H264Stream = h264.H264Stream;
36588
36589      // constants
36590      var AUDIO_PROPERTIES = ['audioobjecttype', 'channelcount', 'samplerate', 'samplingfrequencyindex', 'samplesize'];
36591
36592      var VIDEO_PROPERTIES = ['width', 'height', 'profileIdc', 'levelIdc', 'profileCompatibility'];
36593
36594      var ONE_SECOND_IN_TS$1 = 90000; // 90kHz clock
36595
36596      // object types
36597      var _VideoSegmentStream, _AudioSegmentStream, _Transmuxer, _CoalesceStream;
36598
36599      // Helper functions
36600      var createDefaultSample, isLikelyAacData, collectDtsInfo, clearDtsInfo, calculateTrackBaseMediaDecodeTime, arrayEquals, sumFrameByteLengths;
36601
36602      /**
36603       * Default sample object
36604       * see ISO/IEC 14496-12:2012, section 8.6.4.3
36605       */
36606      createDefaultSample = function createDefaultSample() {
36607        return {
36608          size: 0,
36609          flags: {
36610            isLeading: 0,
36611            dependsOn: 1,
36612            isDependedOn: 0,
36613            hasRedundancy: 0,
36614            degradationPriority: 0
36615          }
36616        };
36617      };
36618
36619      isLikelyAacData = function isLikelyAacData(data) {
36620        if (data[0] === 'I'.charCodeAt(0) && data[1] === 'D'.charCodeAt(0) && data[2] === '3'.charCodeAt(0)) {
36621          return true;
36622        }
36623        return false;
36624      };
36625
36626      /**
36627       * Compare two arrays (even typed) for same-ness
36628       */
36629      arrayEquals = function arrayEquals(a, b) {
36630        var i;
36631
36632        if (a.length !== b.length) {
36633          return false;
36634        }
36635
36636        // compare the value of each element in the array
36637        for (i = 0; i < a.length; i++) {
36638          if (a[i] !== b[i]) {
36639            return false;
36640          }
36641        }
36642
36643        return true;
36644      };
36645
36646      /**
36647       * Sum the `byteLength` properties of the data in each AAC frame
36648       */
36649      sumFrameByteLengths = function sumFrameByteLengths(array) {
36650        var i,
36651            currentObj,
36652            sum = 0;
36653
36654        // sum the byteLength's all each nal unit in the frame
36655        for (i = 0; i < array.length; i++) {
36656          currentObj = array[i];
36657          sum += currentObj.data.byteLength;
36658        }
36659
36660        return sum;
36661      };
36662
36663      /**
36664       * Constructs a single-track, ISO BMFF media segment from AAC data
36665       * events. The output of this stream can be fed to a SourceBuffer
36666       * configured with a suitable initialization segment.
36667       * @param track {object} track metadata configuration
36668       * @param options {object} transmuxer options object
36669       * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
36670       *        in the source; false to adjust the first segment to start at 0.
36671       */
36672      _AudioSegmentStream = function AudioSegmentStream(track, options) {
36673        var adtsFrames = [],
36674            sequenceNumber = 0,
36675            earliestAllowedDts = 0,
36676            audioAppendStartTs = 0,
36677            videoBaseMediaDecodeTime = Infinity;
36678
36679        options = options || {};
36680
36681        _AudioSegmentStream.prototype.init.call(this);
36682
36683        this.push = function (data) {
36684          collectDtsInfo(track, data);
36685
36686          if (track) {
36687            AUDIO_PROPERTIES.forEach(function (prop) {
36688              track[prop] = data[prop];
36689            });
36690          }
36691
36692          // buffer audio data until end() is called
36693          adtsFrames.push(data);
36694        };
36695
36696        this.setEarliestDts = function (earliestDts) {
36697          earliestAllowedDts = earliestDts - track.timelineStartInfo.baseMediaDecodeTime;
36698        };
36699
36700        this.setVideoBaseMediaDecodeTime = function (baseMediaDecodeTime) {
36701          videoBaseMediaDecodeTime = baseMediaDecodeTime;
36702        };
36703
36704        this.setAudioAppendStart = function (timestamp) {
36705          audioAppendStartTs = timestamp;
36706        };
36707
36708        this.flush = function () {
36709          var frames, moof, mdat, boxes;
36710
36711          // return early if no audio data has been observed
36712          if (adtsFrames.length === 0) {
36713            this.trigger('done', 'AudioSegmentStream');
36714            return;
36715          }
36716
36717          frames = this.trimAdtsFramesByEarliestDts_(adtsFrames);
36718          track.baseMediaDecodeTime = calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
36719
36720          this.prefixWithSilence_(track, frames);
36721
36722          // we have to build the index from byte locations to
36723          // samples (that is, adts frames) in the audio data
36724          track.samples = this.generateSampleTable_(frames);
36725
36726          // concatenate the audio data to constuct the mdat
36727          mdat = mp4Generator.mdat(this.concatenateFrameData_(frames));
36728
36729          adtsFrames = [];
36730
36731          moof = mp4Generator.moof(sequenceNumber, [track]);
36732          boxes = new Uint8Array(moof.byteLength + mdat.byteLength);
36733
36734          // bump the sequence number for next time
36735          sequenceNumber++;
36736
36737          boxes.set(moof);
36738          boxes.set(mdat, moof.byteLength);
36739
36740          clearDtsInfo(track);
36741
36742          this.trigger('data', { track: track, boxes: boxes });
36743          this.trigger('done', 'AudioSegmentStream');
36744        };
36745
36746        // Possibly pad (prefix) the audio track with silence if appending this track
36747        // would lead to the introduction of a gap in the audio buffer
36748        this.prefixWithSilence_ = function (track, frames) {
36749          var baseMediaDecodeTimeTs,
36750              frameDuration = 0,
36751              audioGapDuration = 0,
36752              audioFillFrameCount = 0,
36753              audioFillDuration = 0,
36754              silentFrame,
36755              i;
36756
36757          if (!frames.length) {
36758            return;
36759          }
36760
36761          baseMediaDecodeTimeTs = clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate);
36762          // determine frame clock duration based on sample rate, round up to avoid overfills
36763          frameDuration = Math.ceil(ONE_SECOND_IN_TS$1 / (track.samplerate / 1024));
36764
36765          if (audioAppendStartTs && videoBaseMediaDecodeTime) {
36766            // insert the shortest possible amount (audio gap or audio to video gap)
36767            audioGapDuration = baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime);
36768            // number of full frames in the audio gap
36769            audioFillFrameCount = Math.floor(audioGapDuration / frameDuration);
36770            audioFillDuration = audioFillFrameCount * frameDuration;
36771          }
36772
36773          // don't attempt to fill gaps smaller than a single frame or larger
36774          // than a half second
36775          if (audioFillFrameCount < 1 || audioFillDuration > ONE_SECOND_IN_TS$1 / 2) {
36776            return;
36777          }
36778
36779          silentFrame = silence[track.samplerate];
36780
36781          if (!silentFrame) {
36782            // we don't have a silent frame pregenerated for the sample rate, so use a frame
36783            // from the content instead
36784            silentFrame = frames[0].data;
36785          }
36786
36787          for (i = 0; i < audioFillFrameCount; i++) {
36788            frames.splice(i, 0, {
36789              data: silentFrame
36790            });
36791          }
36792
36793          track.baseMediaDecodeTime -= Math.floor(clock.videoTsToAudioTs(audioFillDuration, track.samplerate));
36794        };
36795
36796        // If the audio segment extends before the earliest allowed dts
36797        // value, remove AAC frames until starts at or after the earliest
36798        // allowed DTS so that we don't end up with a negative baseMedia-
36799        // DecodeTime for the audio track
36800        this.trimAdtsFramesByEarliestDts_ = function (adtsFrames) {
36801          if (track.minSegmentDts >= earliestAllowedDts) {
36802            return adtsFrames;
36803          }
36804
36805          // We will need to recalculate the earliest segment Dts
36806          track.minSegmentDts = Infinity;
36807
36808          return adtsFrames.filter(function (currentFrame) {
36809            // If this is an allowed frame, keep it and record it's Dts
36810            if (currentFrame.dts >= earliestAllowedDts) {
36811              track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts);
36812              track.minSegmentPts = track.minSegmentDts;
36813              return true;
36814            }
36815            // Otherwise, discard it
36816            return false;
36817          });
36818        };
36819
36820        // generate the track's raw mdat data from an array of frames
36821        this.generateSampleTable_ = function (frames) {
36822          var i,
36823              currentFrame,
36824              samples = [];
36825
36826          for (i = 0; i < frames.length; i++) {
36827            currentFrame = frames[i];
36828            samples.push({
36829              size: currentFrame.data.byteLength,
36830              duration: 1024 // For AAC audio, all samples contain 1024 samples
36831            });
36832          }
36833          return samples;
36834        };
36835
36836        // generate the track's sample table from an array of frames
36837        this.concatenateFrameData_ = function (frames) {
36838          var i,
36839              currentFrame,
36840              dataOffset = 0,
36841              data = new Uint8Array(sumFrameByteLengths(frames));
36842
36843          for (i = 0; i < frames.length; i++) {
36844            currentFrame = frames[i];
36845
36846            data.set(currentFrame.data, dataOffset);
36847            dataOffset += currentFrame.data.byteLength;
36848          }
36849          return data;
36850        };
36851      };
36852
36853      _AudioSegmentStream.prototype = new stream();
36854
36855      /**
36856       * Constructs a single-track, ISO BMFF media segment from H264 data
36857       * events. The output of this stream can be fed to a SourceBuffer
36858       * configured with a suitable initialization segment.
36859       * @param track {object} track metadata configuration
36860       * @param options {object} transmuxer options object
36861       * @param options.alignGopsAtEnd {boolean} If true, start from the end of the
36862       *        gopsToAlignWith list when attempting to align gop pts
36863       * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
36864       *        in the source; false to adjust the first segment to start at 0.
36865       */
36866      _VideoSegmentStream = function VideoSegmentStream(track, options) {
36867        var sequenceNumber = 0,
36868            nalUnits = [],
36869            gopsToAlignWith = [],
36870            config,
36871            pps;
36872
36873        options = options || {};
36874
36875        _VideoSegmentStream.prototype.init.call(this);
36876
36877        delete track.minPTS;
36878
36879        this.gopCache_ = [];
36880
36881        this.push = function (nalUnit) {
36882          collectDtsInfo(track, nalUnit);
36883
36884          // record the track config
36885          if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) {
36886            config = nalUnit.config;
36887            track.sps = [nalUnit.data];
36888
36889            VIDEO_PROPERTIES.forEach(function (prop) {
36890              track[prop] = config[prop];
36891            }, this);
36892          }
36893
36894          if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && !pps) {
36895            pps = nalUnit.data;
36896            track.pps = [nalUnit.data];
36897          }
36898
36899          // buffer video until flush() is called
36900          nalUnits.push(nalUnit);
36901        };
36902
36903        this.flush = function () {
36904          var frames, gopForFusion, gops, moof, mdat, boxes;
36905
36906          // Throw away nalUnits at the start of the byte stream until
36907          // we find the first AUD
36908          while (nalUnits.length) {
36909            if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
36910              break;
36911            }
36912            nalUnits.shift();
36913          }
36914
36915          // Return early if no video data has been observed
36916          if (nalUnits.length === 0) {
36917            this.resetStream_();
36918            this.trigger('done', 'VideoSegmentStream');
36919            return;
36920          }
36921
36922          // Organize the raw nal-units into arrays that represent
36923          // higher-level constructs such as frames and gops
36924          // (group-of-pictures)
36925          frames = this.groupNalsIntoFrames_(nalUnits);
36926          gops = this.groupFramesIntoGops_(frames);
36927
36928          // If the first frame of this fragment is not a keyframe we have
36929          // a problem since MSE (on Chrome) requires a leading keyframe.
36930          //
36931          // We have two approaches to repairing this situation:
36932          // 1) GOP-FUSION:
36933          //    This is where we keep track of the GOPS (group-of-pictures)
36934          //    from previous fragments and attempt to find one that we can
36935          //    prepend to the current fragment in order to create a valid
36936          //    fragment.
36937          // 2) KEYFRAME-PULLING:
36938          //    Here we search for the first keyframe in the fragment and
36939          //    throw away all the frames between the start of the fragment
36940          //    and that keyframe. We then extend the duration and pull the
36941          //    PTS of the keyframe forward so that it covers the time range
36942          //    of the frames that were disposed of.
36943          //
36944          // #1 is far prefereable over #2 which can cause "stuttering" but
36945          // requires more things to be just right.
36946          if (!gops[0][0].keyFrame) {
36947            // Search for a gop for fusion from our gopCache
36948            gopForFusion = this.getGopForFusion_(nalUnits[0], track);
36949
36950            if (gopForFusion) {
36951              gops.unshift(gopForFusion);
36952              // Adjust Gops' metadata to account for the inclusion of the
36953              // new gop at the beginning
36954              gops.byteLength += gopForFusion.byteLength;
36955              gops.nalCount += gopForFusion.nalCount;
36956              gops.pts = gopForFusion.pts;
36957              gops.dts = gopForFusion.dts;
36958              gops.duration += gopForFusion.duration;
36959            } else {
36960              // If we didn't find a candidate gop fall back to keyframe-pulling
36961              gops = this.extendFirstKeyFrame_(gops);
36962            }
36963          }
36964
36965          // Trim gops to align with gopsToAlignWith
36966          if (gopsToAlignWith.length) {
36967            var alignedGops;
36968
36969            if (options.alignGopsAtEnd) {
36970              alignedGops = this.alignGopsAtEnd_(gops);
36971            } else {
36972              alignedGops = this.alignGopsAtStart_(gops);
36973            }
36974
36975            if (!alignedGops) {
36976              // save all the nals in the last GOP into the gop cache
36977              this.gopCache_.unshift({
36978                gop: gops.pop(),
36979                pps: track.pps,
36980                sps: track.sps
36981              });
36982
36983              // Keep a maximum of 6 GOPs in the cache
36984              this.gopCache_.length = Math.min(6, this.gopCache_.length);
36985
36986              // Clear nalUnits
36987              nalUnits = [];
36988
36989              // return early no gops can be aligned with desired gopsToAlignWith
36990              this.resetStream_();
36991              this.trigger('done', 'VideoSegmentStream');
36992              return;
36993            }
36994
36995            // Some gops were trimmed. clear dts info so minSegmentDts and pts are correct
36996            // when recalculated before sending off to CoalesceStream
36997            clearDtsInfo(track);
36998
36999            gops = alignedGops;
37000          }
37001
37002          collectDtsInfo(track, gops);
37003
37004          // First, we have to build the index from byte locations to
37005          // samples (that is, frames) in the video data
37006          track.samples = this.generateSampleTable_(gops);
37007
37008          // Concatenate the video data and construct the mdat
37009          mdat = mp4Generator.mdat(this.concatenateNalData_(gops));
37010
37011          track.baseMediaDecodeTime = calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
37012
37013          this.trigger('processedGopsInfo', gops.map(function (gop) {
37014            return {
37015              pts: gop.pts,
37016              dts: gop.dts,
37017              byteLength: gop.byteLength
37018            };
37019          }));
37020
37021          // save all the nals in the last GOP into the gop cache
37022          this.gopCache_.unshift({
37023            gop: gops.pop(),
37024            pps: track.pps,
37025            sps: track.sps
37026          });
37027
37028          // Keep a maximum of 6 GOPs in the cache
37029          this.gopCache_.length = Math.min(6, this.gopCache_.length);
37030
37031          // Clear nalUnits
37032          nalUnits = [];
37033
37034          this.trigger('baseMediaDecodeTime', track.baseMediaDecodeTime);
37035          this.trigger('timelineStartInfo', track.timelineStartInfo);
37036
37037          moof = mp4Generator.moof(sequenceNumber, [track]);
37038
37039          // it would be great to allocate this array up front instead of
37040          // throwing away hundreds of media segment fragments
37041          boxes = new Uint8Array(moof.byteLength + mdat.byteLength);
37042
37043          // Bump the sequence number for next time
37044          sequenceNumber++;
37045
37046          boxes.set(moof);
37047          boxes.set(mdat, moof.byteLength);
37048
37049          this.trigger('data', { track: track, boxes: boxes });
37050
37051          this.resetStream_();
37052
37053          // Continue with the flush process now
37054          this.trigger('done', 'VideoSegmentStream');
37055        };
37056
37057        this.resetStream_ = function () {
37058          clearDtsInfo(track);
37059
37060          // reset config and pps because they may differ across segments
37061          // for instance, when we are rendition switching
37062          config = undefined;
37063          pps = undefined;
37064        };
37065
37066        // Search for a candidate Gop for gop-fusion from the gop cache and
37067        // return it or return null if no good candidate was found
37068        this.getGopForFusion_ = function (nalUnit) {
37069          var halfSecond = 45000,
37070
37071
37072          // Half-a-second in a 90khz clock
37073          allowableOverlap = 10000,
37074
37075
37076          // About 3 frames @ 30fps
37077          nearestDistance = Infinity,
37078              dtsDistance,
37079              nearestGopObj,
37080              currentGop,
37081              currentGopObj,
37082              i;
37083
37084          // Search for the GOP nearest to the beginning of this nal unit
37085          for (i = 0; i < this.gopCache_.length; i++) {
37086            currentGopObj = this.gopCache_[i];
37087            currentGop = currentGopObj.gop;
37088
37089            // Reject Gops with different SPS or PPS
37090            if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) || !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) {
37091              continue;
37092            }
37093
37094            // Reject Gops that would require a negative baseMediaDecodeTime
37095            if (currentGop.dts < track.timelineStartInfo.dts) {
37096              continue;
37097            }
37098
37099            // The distance between the end of the gop and the start of the nalUnit
37100            dtsDistance = nalUnit.dts - currentGop.dts - currentGop.duration;
37101
37102            // Only consider GOPS that start before the nal unit and end within
37103            // a half-second of the nal unit
37104            if (dtsDistance >= -allowableOverlap && dtsDistance <= halfSecond) {
37105
37106              // Always use the closest GOP we found if there is more than
37107              // one candidate
37108              if (!nearestGopObj || nearestDistance > dtsDistance) {
37109                nearestGopObj = currentGopObj;
37110                nearestDistance = dtsDistance;
37111              }
37112            }
37113          }
37114
37115          if (nearestGopObj) {
37116            return nearestGopObj.gop;
37117          }
37118          return null;
37119        };
37120
37121        this.extendFirstKeyFrame_ = function (gops) {
37122          var currentGop;
37123
37124          if (!gops[0][0].keyFrame && gops.length > 1) {
37125            // Remove the first GOP
37126            currentGop = gops.shift();
37127
37128            gops.byteLength -= currentGop.byteLength;
37129            gops.nalCount -= currentGop.nalCount;
37130
37131            // Extend the first frame of what is now the
37132            // first gop to cover the time period of the
37133            // frames we just removed
37134            gops[0][0].dts = currentGop.dts;
37135            gops[0][0].pts = currentGop.pts;
37136            gops[0][0].duration += currentGop.duration;
37137          }
37138
37139          return gops;
37140        };
37141
37142        // Convert an array of nal units into an array of frames with each frame being
37143        // composed of the nal units that make up that frame
37144        // Also keep track of cummulative data about the frame from the nal units such
37145        // as the frame duration, starting pts, etc.
37146        this.groupNalsIntoFrames_ = function (nalUnits) {
37147          var i,
37148              currentNal,
37149              currentFrame = [],
37150              frames = [];
37151
37152          currentFrame.byteLength = 0;
37153
37154          for (i = 0; i < nalUnits.length; i++) {
37155            currentNal = nalUnits[i];
37156
37157            // Split on 'aud'-type nal units
37158            if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
37159              // Since the very first nal unit is expected to be an AUD
37160              // only push to the frames array when currentFrame is not empty
37161              if (currentFrame.length) {
37162                currentFrame.duration = currentNal.dts - currentFrame.dts;
37163                frames.push(currentFrame);
37164              }
37165              currentFrame = [currentNal];
37166              currentFrame.byteLength = currentNal.data.byteLength;
37167              currentFrame.pts = currentNal.pts;
37168              currentFrame.dts = currentNal.dts;
37169            } else {
37170              // Specifically flag key frames for ease of use later
37171              if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
37172                currentFrame.keyFrame = true;
37173              }
37174              currentFrame.duration = currentNal.dts - currentFrame.dts;
37175              currentFrame.byteLength += currentNal.data.byteLength;
37176              currentFrame.push(currentNal);
37177            }
37178          }
37179
37180          // For the last frame, use the duration of the previous frame if we
37181          // have nothing better to go on
37182          if (frames.length && (!currentFrame.duration || currentFrame.duration <= 0)) {
37183            currentFrame.duration = frames[frames.length - 1].duration;
37184          }
37185
37186          // Push the final frame
37187          frames.push(currentFrame);
37188          return frames;
37189        };
37190
37191        // Convert an array of frames into an array of Gop with each Gop being composed
37192        // of the frames that make up that Gop
37193        // Also keep track of cummulative data about the Gop from the frames such as the
37194        // Gop duration, starting pts, etc.
37195        this.groupFramesIntoGops_ = function (frames) {
37196          var i,
37197              currentFrame,
37198              currentGop = [],
37199              gops = [];
37200
37201          // We must pre-set some of the values on the Gop since we
37202          // keep running totals of these values
37203          currentGop.byteLength = 0;
37204          currentGop.nalCount = 0;
37205          currentGop.duration = 0;
37206          currentGop.pts = frames[0].pts;
37207          currentGop.dts = frames[0].dts;
37208
37209          // store some metadata about all the Gops
37210          gops.byteLength = 0;
37211          gops.nalCount = 0;
37212          gops.duration = 0;
37213          gops.pts = frames[0].pts;
37214          gops.dts = frames[0].dts;
37215
37216          for (i = 0; i < frames.length; i++) {
37217            currentFrame = frames[i];
37218
37219            if (currentFrame.keyFrame) {
37220              // Since the very first frame is expected to be an keyframe
37221              // only push to the gops array when currentGop is not empty
37222              if (currentGop.length) {
37223                gops.push(currentGop);
37224                gops.byteLength += currentGop.byteLength;
37225                gops.nalCount += currentGop.nalCount;
37226                gops.duration += currentGop.duration;
37227              }
37228
37229              currentGop = [currentFrame];
37230              currentGop.nalCount = currentFrame.length;
37231              currentGop.byteLength = currentFrame.byteLength;
37232              currentGop.pts = currentFrame.pts;
37233              currentGop.dts = currentFrame.dts;
37234              currentGop.duration = currentFrame.duration;
37235            } else {
37236              currentGop.duration += currentFrame.duration;
37237              currentGop.nalCount += currentFrame.length;
37238              currentGop.byteLength += currentFrame.byteLength;
37239              currentGop.push(currentFrame);
37240            }
37241          }
37242
37243          if (gops.length && currentGop.duration <= 0) {
37244            currentGop.duration = gops[gops.length - 1].duration;
37245          }
37246          gops.byteLength += currentGop.byteLength;
37247          gops.nalCount += currentGop.nalCount;
37248          gops.duration += currentGop.duration;
37249
37250          // push the final Gop
37251          gops.push(currentGop);
37252          return gops;
37253        };
37254
37255        // generate the track's sample table from an array of gops
37256        this.generateSampleTable_ = function (gops, baseDataOffset) {
37257          var h,
37258              i,
37259              sample,
37260              currentGop,
37261              currentFrame,
37262              dataOffset = baseDataOffset || 0,
37263              samples = [];
37264
37265          for (h = 0; h < gops.length; h++) {
37266            currentGop = gops[h];
37267
37268            for (i = 0; i < currentGop.length; i++) {
37269              currentFrame = currentGop[i];
37270
37271              sample = createDefaultSample();
37272
37273              sample.dataOffset = dataOffset;
37274              sample.compositionTimeOffset = currentFrame.pts - currentFrame.dts;
37275              sample.duration = currentFrame.duration;
vendor: 4,205 bytes, lines 37276-37405
37276              sample.size = 4 * currentFrame.length; // Space for nal unit size
37277              sample.size += currentFrame.byteLength;
37278
37279              if (currentFrame.keyFrame) {
37280                sample.flags.dependsOn = 2;
37281              }
37282
37283              dataOffset += sample.size;
37284
37285              samples.push(sample);
37286            }
37287          }
37288          return samples;
37289        };
37290
37291        // generate the track's raw mdat data from an array of gops
37292        this.concatenateNalData_ = function (gops) {
37293          var h,
37294              i,
37295              j,
37296              currentGop,
37297              currentFrame,
37298              currentNal,
37299              dataOffset = 0,
37300              nalsByteLength = gops.byteLength,
37301              numberOfNals = gops.nalCount,
37302              totalByteLength = nalsByteLength + 4 * numberOfNals,
37303              data = new Uint8Array(totalByteLength),
37304              view = new DataView(data.buffer);
37305
37306          // For each Gop..
37307          for (h = 0; h < gops.length; h++) {
37308            currentGop = gops[h];
37309
37310            // For each Frame..
37311            for (i = 0; i < currentGop.length; i++) {
37312              currentFrame = currentGop[i];
37313
37314              // For each NAL..
37315              for (j = 0; j < currentFrame.length; j++) {
37316                currentNal = currentFrame[j];
37317
37318                view.setUint32(dataOffset, currentNal.data.byteLength);
37319                dataOffset += 4;
37320                data.set(currentNal.data, dataOffset);
37321                dataOffset += currentNal.data.byteLength;
37322              }
37323            }
37324          }
37325          return data;
37326        };
37327
37328        // trim gop list to the first gop found that has a matching pts with a gop in the list
37329        // of gopsToAlignWith starting from the START of the list
37330        this.alignGopsAtStart_ = function (gops) {
37331          var alignIndex, gopIndex, align, gop, byteLength, nalCount, duration, alignedGops;
37332
37333          byteLength = gops.byteLength;
37334          nalCount = gops.nalCount;
37335          duration = gops.duration;
37336          alignIndex = gopIndex = 0;
37337
37338          while (alignIndex < gopsToAlignWith.length && gopIndex < gops.length) {
37339            align = gopsToAlignWith[alignIndex];
37340            gop = gops[gopIndex];
37341
37342            if (align.pts === gop.pts) {
37343              break;
37344            }
37345
37346            if (gop.pts > align.pts) {
37347              // this current gop starts after the current gop we want to align on, so increment
37348              // align index
37349              alignIndex++;
37350              continue;
37351            }
37352
37353            // current gop starts before the current gop we want to align on. so increment gop
37354            // index
37355            gopIndex++;
37356            byteLength -= gop.byteLength;
37357            nalCount -= gop.nalCount;
37358            duration -= gop.duration;
37359          }
37360
37361          if (gopIndex === 0) {
37362            // no gops to trim
37363            return gops;
37364          }
37365
37366          if (gopIndex === gops.length) {
37367            // all gops trimmed, skip appending all gops
37368            return null;
37369          }
37370
37371          alignedGops = gops.slice(gopIndex);
37372          alignedGops.byteLength = byteLength;
37373          alignedGops.duration = duration;
37374          alignedGops.nalCount = nalCount;
37375          alignedGops.pts = alignedGops[0].pts;
37376          alignedGops.dts = alignedGops[0].dts;
37377
37378          return alignedGops;
37379        };
37380
37381        // trim gop list to the first gop found that has a matching pts with a gop in the list
37382        // of gopsToAlignWith starting from the END of the list
37383        this.alignGopsAtEnd_ = function (gops) {
37384          var alignIndex, gopIndex, align, gop, alignEndIndex, matchFound;
37385
37386          alignIndex = gopsToAlignWith.length - 1;
37387          gopIndex = gops.length - 1;
37388          alignEndIndex = null;
37389          matchFound = false;
37390
37391          while (alignIndex >= 0 && gopIndex >= 0) {
37392            align = gopsToAlignWith[alignIndex];
37393            gop = gops[gopIndex];
37394
37395            if (align.pts === gop.pts) {
37396              matchFound = true;
37397              break;
37398            }
37399
37400            if (align.pts > gop.pts) {
37401              alignIndex--;
37402              continue;
37403            }
37404
37405            if (alignIndex === gopsToAlignWith.length - 1) {
vendor: 310,676 bytes, lines 37406-46331
37406              // gop.pts is greater than the last alignment candidate. If no match is found
37407              // by the end of this loop, we still want to append gops that come after this
37408              // point
37409              alignEndIndex = gopIndex;
37410            }
37411
37412            gopIndex--;
37413          }
37414
37415          if (!matchFound && alignEndIndex === null) {
37416            return null;
37417          }
37418
37419          var trimIndex;
37420
37421          if (matchFound) {
37422            trimIndex = gopIndex;
37423          } else {
37424            trimIndex = alignEndIndex;
37425          }
37426
37427          if (trimIndex === 0) {
37428            return gops;
37429          }
37430
37431          var alignedGops = gops.slice(trimIndex);
37432          var metadata = alignedGops.reduce(function (total, gop) {
37433            total.byteLength += gop.byteLength;
37434            total.duration += gop.duration;
37435            total.nalCount += gop.nalCount;
37436            return total;
37437          }, { byteLength: 0, duration: 0, nalCount: 0 });
37438
37439          alignedGops.byteLength = metadata.byteLength;
37440          alignedGops.duration = metadata.duration;
37441          alignedGops.nalCount = metadata.nalCount;
37442          alignedGops.pts = alignedGops[0].pts;
37443          alignedGops.dts = alignedGops[0].dts;
37444
37445          return alignedGops;
37446        };
37447
37448        this.alignGopsWith = function (newGopsToAlignWith) {
37449          gopsToAlignWith = newGopsToAlignWith;
37450        };
37451      };
37452
37453      _VideoSegmentStream.prototype = new stream();
37454
37455      /**
37456       * Store information about the start and end of the track and the
37457       * duration for each frame/sample we process in order to calculate
37458       * the baseMediaDecodeTime
37459       */
37460      collectDtsInfo = function collectDtsInfo(track, data) {
37461        if (typeof data.pts === 'number') {
37462          if (track.timelineStartInfo.pts === undefined) {
37463            track.timelineStartInfo.pts = data.pts;
37464          }
37465
37466          if (track.minSegmentPts === undefined) {
37467            track.minSegmentPts = data.pts;
37468          } else {
37469            track.minSegmentPts = Math.min(track.minSegmentPts, data.pts);
37470          }
37471
37472          if (track.maxSegmentPts === undefined) {
37473            track.maxSegmentPts = data.pts;
37474          } else {
37475            track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts);
37476          }
37477        }
37478
37479        if (typeof data.dts === 'number') {
37480          if (track.timelineStartInfo.dts === undefined) {
37481            track.timelineStartInfo.dts = data.dts;
37482          }
37483
37484          if (track.minSegmentDts === undefined) {
37485            track.minSegmentDts = data.dts;
37486          } else {
37487            track.minSegmentDts = Math.min(track.minSegmentDts, data.dts);
37488          }
37489
37490          if (track.maxSegmentDts === undefined) {
37491            track.maxSegmentDts = data.dts;
37492          } else {
37493            track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts);
37494          }
37495        }
37496      };
37497
37498      /**
37499       * Clear values used to calculate the baseMediaDecodeTime between
37500       * tracks
37501       */
37502      clearDtsInfo = function clearDtsInfo(track) {
37503        delete track.minSegmentDts;
37504        delete track.maxSegmentDts;
37505        delete track.minSegmentPts;
37506        delete track.maxSegmentPts;
37507      };
37508
37509      /**
37510       * Calculate the track's baseMediaDecodeTime based on the earliest
37511       * DTS the transmuxer has ever seen and the minimum DTS for the
37512       * current track
37513       * @param track {object} track metadata configuration
37514       * @param keepOriginalTimestamps {boolean} If true, keep the timestamps
37515       *        in the source; false to adjust the first segment to start at 0.
37516       */
37517      calculateTrackBaseMediaDecodeTime = function calculateTrackBaseMediaDecodeTime(track, keepOriginalTimestamps) {
37518        var baseMediaDecodeTime,
37519            scale,
37520            minSegmentDts = track.minSegmentDts;
37521
37522        // Optionally adjust the time so the first segment starts at zero.
37523        if (!keepOriginalTimestamps) {
37524          minSegmentDts -= track.timelineStartInfo.dts;
37525        }
37526
37527        // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where
37528        // we want the start of the first segment to be placed
37529        baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime;
37530
37531        // Add to that the distance this segment is from the very first
37532        baseMediaDecodeTime += minSegmentDts;
37533
37534        // baseMediaDecodeTime must not become negative
37535        baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime);
37536
37537        if (track.type === 'audio') {
37538          // Audio has a different clock equal to the sampling_rate so we need to
37539          // scale the PTS values into the clock rate of the track
37540          scale = track.samplerate / ONE_SECOND_IN_TS$1;
37541          baseMediaDecodeTime *= scale;
37542          baseMediaDecodeTime = Math.floor(baseMediaDecodeTime);
37543        }
37544
37545        return baseMediaDecodeTime;
37546      };
37547
37548      /**
37549       * A Stream that can combine multiple streams (ie. audio & video)
37550       * into a single output segment for MSE. Also supports audio-only
37551       * and video-only streams.
37552       */
37553      _CoalesceStream = function CoalesceStream(options, metadataStream) {
37554        // Number of Tracks per output segment
37555        // If greater than 1, we combine multiple
37556        // tracks into a single segment
37557        this.numberOfTracks = 0;
37558        this.metadataStream = metadataStream;
37559
37560        if (typeof options.remux !== 'undefined') {
37561          this.remuxTracks = !!options.remux;
37562        } else {
37563          this.remuxTracks = true;
37564        }
37565
37566        this.pendingTracks = [];
37567        this.videoTrack = null;
37568        this.pendingBoxes = [];
37569        this.pendingCaptions = [];
37570        this.pendingMetadata = [];
37571        this.pendingBytes = 0;
37572        this.emittedTracks = 0;
37573
37574        _CoalesceStream.prototype.init.call(this);
37575
37576        // Take output from multiple
37577        this.push = function (output) {
37578          // buffer incoming captions until the associated video segment
37579          // finishes
37580          if (output.text) {
37581            return this.pendingCaptions.push(output);
37582          }
37583          // buffer incoming id3 tags until the final flush
37584          if (output.frames) {
37585            return this.pendingMetadata.push(output);
37586          }
37587
37588          // Add this track to the list of pending tracks and store
37589          // important information required for the construction of
37590          // the final segment
37591          this.pendingTracks.push(output.track);
37592          this.pendingBoxes.push(output.boxes);
37593          this.pendingBytes += output.boxes.byteLength;
37594
37595          if (output.track.type === 'video') {
37596            this.videoTrack = output.track;
37597          }
37598          if (output.track.type === 'audio') {
37599            this.audioTrack = output.track;
37600          }
37601        };
37602      };
37603
37604      _CoalesceStream.prototype = new stream();
37605      _CoalesceStream.prototype.flush = function (flushSource) {
37606        var offset = 0,
37607            event = {
37608          captions: [],
37609          captionStreams: {},
37610          metadata: [],
37611          info: {}
37612        },
37613            caption,
37614            id3,
37615            initSegment,
37616            timelineStartPts = 0,
37617            i;
37618
37619        if (this.pendingTracks.length < this.numberOfTracks) {
37620          if (flushSource !== 'VideoSegmentStream' && flushSource !== 'AudioSegmentStream') {
37621            // Return because we haven't received a flush from a data-generating
37622            // portion of the segment (meaning that we have only recieved meta-data
37623            // or captions.)
37624            return;
37625          } else if (this.remuxTracks) {
37626            // Return until we have enough tracks from the pipeline to remux (if we
37627            // are remuxing audio and video into a single MP4)
37628            return;
37629          } else if (this.pendingTracks.length === 0) {
37630            // In the case where we receive a flush without any data having been
37631            // received we consider it an emitted track for the purposes of coalescing
37632            // `done` events.
37633            // We do this for the case where there is an audio and video track in the
37634            // segment but no audio data. (seen in several playlists with alternate
37635            // audio tracks and no audio present in the main TS segments.)
37636            this.emittedTracks++;
37637
37638            if (this.emittedTracks >= this.numberOfTracks) {
37639              this.trigger('done');
37640              this.emittedTracks = 0;
37641            }
37642            return;
37643          }
37644        }
37645
37646        if (this.videoTrack) {
37647          timelineStartPts = this.videoTrack.timelineStartInfo.pts;
37648          VIDEO_PROPERTIES.forEach(function (prop) {
37649            event.info[prop] = this.videoTrack[prop];
37650          }, this);
37651        } else if (this.audioTrack) {
37652          timelineStartPts = this.audioTrack.timelineStartInfo.pts;
37653          AUDIO_PROPERTIES.forEach(function (prop) {
37654            event.info[prop] = this.audioTrack[prop];
37655          }, this);
37656        }
37657
37658        if (this.pendingTracks.length === 1) {
37659          event.type = this.pendingTracks[0].type;
37660        } else {
37661          event.type = 'combined';
37662        }
37663
37664        this.emittedTracks += this.pendingTracks.length;
37665
37666        initSegment = mp4Generator.initSegment(this.pendingTracks);
37667
37668        // Create a new typed array to hold the init segment
37669        event.initSegment = new Uint8Array(initSegment.byteLength);
37670
37671        // Create an init segment containing a moov
37672        // and track definitions
37673        event.initSegment.set(initSegment);
37674
37675        // Create a new typed array to hold the moof+mdats
37676        event.data = new Uint8Array(this.pendingBytes);
37677
37678        // Append each moof+mdat (one per track) together
37679        for (i = 0; i < this.pendingBoxes.length; i++) {
37680          event.data.set(this.pendingBoxes[i], offset);
37681          offset += this.pendingBoxes[i].byteLength;
37682        }
37683
37684        // Translate caption PTS times into second offsets into the
37685        // video timeline for the segment, and add track info
37686        for (i = 0; i < this.pendingCaptions.length; i++) {
37687          caption = this.pendingCaptions[i];
37688          caption.startTime = caption.startPts - timelineStartPts;
37689          caption.startTime /= 90e3;
37690          caption.endTime = caption.endPts - timelineStartPts;
37691          caption.endTime /= 90e3;
37692          event.captionStreams[caption.stream] = true;
37693          event.captions.push(caption);
37694        }
37695
37696        // Translate ID3 frame PTS times into second offsets into the
37697        // video timeline for the segment
37698        for (i = 0; i < this.pendingMetadata.length; i++) {
37699          id3 = this.pendingMetadata[i];
37700          id3.cueTime = id3.pts - timelineStartPts;
37701          id3.cueTime /= 90e3;
37702          event.metadata.push(id3);
37703        }
37704        // We add this to every single emitted segment even though we only need
37705        // it for the first
37706        event.metadata.dispatchType = this.metadataStream.dispatchType;
37707
37708        // Reset stream state
37709        this.pendingTracks.length = 0;
37710        this.videoTrack = null;
37711        this.pendingBoxes.length = 0;
37712        this.pendingCaptions.length = 0;
37713        this.pendingBytes = 0;
37714        this.pendingMetadata.length = 0;
37715
37716        // Emit the built segment
37717        this.trigger('data', event);
37718
37719        // Only emit `done` if all tracks have been flushed and emitted
37720        if (this.emittedTracks >= this.numberOfTracks) {
37721          this.trigger('done');
37722          this.emittedTracks = 0;
37723        }
37724      };
37725      /**
37726       * A Stream that expects MP2T binary data as input and produces
37727       * corresponding media segments, suitable for use with Media Source
37728       * Extension (MSE) implementations that support the ISO BMFF byte
37729       * stream format, like Chrome.
37730       */
37731      _Transmuxer = function Transmuxer(options) {
37732        var self = this,
37733            hasFlushed = true,
37734            videoTrack,
37735            audioTrack;
37736
37737        _Transmuxer.prototype.init.call(this);
37738
37739        options = options || {};
37740        this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0;
37741        this.transmuxPipeline_ = {};
37742
37743        this.setupAacPipeline = function () {
37744          var pipeline = {};
37745          this.transmuxPipeline_ = pipeline;
37746
37747          pipeline.type = 'aac';
37748          pipeline.metadataStream = new m2ts_1.MetadataStream();
37749
37750          // set up the parsing pipeline
37751          pipeline.aacStream = new aac();
37752          pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio');
37753          pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata');
37754          pipeline.adtsStream = new adts();
37755          pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream);
37756          pipeline.headOfPipeline = pipeline.aacStream;
37757
37758          pipeline.aacStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
37759          pipeline.aacStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream);
37760
37761          pipeline.metadataStream.on('timestamp', function (frame) {
37762            pipeline.aacStream.setTimestamp(frame.timeStamp);
37763          });
37764
37765          pipeline.aacStream.on('data', function (data) {
37766            if (data.type === 'timed-metadata' && !pipeline.audioSegmentStream) {
37767              audioTrack = audioTrack || {
37768                timelineStartInfo: {
37769                  baseMediaDecodeTime: self.baseMediaDecodeTime
37770                },
37771                codec: 'adts',
37772                type: 'audio'
37773              };
37774              // hook up the audio segment stream to the first track with aac data
37775              pipeline.coalesceStream.numberOfTracks++;
37776              pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options);
37777              // Set up the final part of the audio pipeline
37778              pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
37779            }
37780          });
37781
37782          // Re-emit any data coming from the coalesce stream to the outside world
37783          pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data'));
37784          // Let the consumer know we have finished flushing the entire pipeline
37785          pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
37786        };
37787
37788        this.setupTsPipeline = function () {
37789          var pipeline = {};
37790          this.transmuxPipeline_ = pipeline;
37791
37792          pipeline.type = 'ts';
37793          pipeline.metadataStream = new m2ts_1.MetadataStream();
37794
37795          // set up the parsing pipeline
37796          pipeline.packetStream = new m2ts_1.TransportPacketStream();
37797          pipeline.parseStream = new m2ts_1.TransportParseStream();
37798          pipeline.elementaryStream = new m2ts_1.ElementaryStream();
37799          pipeline.videoTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('video');
37800          pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio');
37801          pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata');
37802          pipeline.adtsStream = new adts();
37803          pipeline.h264Stream = new H264Stream();
37804          pipeline.captionStream = new m2ts_1.CaptionStream();
37805          pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream);
37806          pipeline.headOfPipeline = pipeline.packetStream;
37807
37808          // disassemble MPEG2-TS packets into elementary streams
37809          pipeline.packetStream.pipe(pipeline.parseStream).pipe(pipeline.elementaryStream);
37810
37811          // !!THIS ORDER IS IMPORTANT!!
37812          // demux the streams
37813          pipeline.elementaryStream.pipe(pipeline.videoTimestampRolloverStream).pipe(pipeline.h264Stream);
37814          pipeline.elementaryStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
37815
37816          pipeline.elementaryStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream);
37817
37818          // Hook up CEA-608/708 caption stream
37819          pipeline.h264Stream.pipe(pipeline.captionStream).pipe(pipeline.coalesceStream);
37820
37821          pipeline.elementaryStream.on('data', function (data) {
37822            var i;
37823
37824            if (data.type === 'metadata') {
37825              i = data.tracks.length;
37826
37827              // scan the tracks listed in the metadata
37828              while (i--) {
37829                if (!videoTrack && data.tracks[i].type === 'video') {
37830                  videoTrack = data.tracks[i];
37831                  videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
37832                } else if (!audioTrack && data.tracks[i].type === 'audio') {
37833                  audioTrack = data.tracks[i];
37834                  audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
37835                }
37836              }
37837
37838              // hook up the video segment stream to the first track with h264 data
37839              if (videoTrack && !pipeline.videoSegmentStream) {
37840                pipeline.coalesceStream.numberOfTracks++;
37841                pipeline.videoSegmentStream = new _VideoSegmentStream(videoTrack, options);
37842
37843                pipeline.videoSegmentStream.on('timelineStartInfo', function (timelineStartInfo) {
37844                  // When video emits timelineStartInfo data after a flush, we forward that
37845                  // info to the AudioSegmentStream, if it exists, because video timeline
37846                  // data takes precedence.
37847                  if (audioTrack) {
37848                    audioTrack.timelineStartInfo = timelineStartInfo;
37849                    // On the first segment we trim AAC frames that exist before the
37850                    // very earliest DTS we have seen in video because Chrome will
37851                    // interpret any video track with a baseMediaDecodeTime that is
37852                    // non-zero as a gap.
37853                    pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts);
37854                  }
37855                });
37856
37857                pipeline.videoSegmentStream.on('processedGopsInfo', self.trigger.bind(self, 'gopInfo'));
37858
37859                pipeline.videoSegmentStream.on('baseMediaDecodeTime', function (baseMediaDecodeTime) {
37860                  if (audioTrack) {
37861                    pipeline.audioSegmentStream.setVideoBaseMediaDecodeTime(baseMediaDecodeTime);
37862                  }
37863                });
37864
37865                // Set up the final part of the video pipeline
37866                pipeline.h264Stream.pipe(pipeline.videoSegmentStream).pipe(pipeline.coalesceStream);
37867              }
37868
37869              if (audioTrack && !pipeline.audioSegmentStream) {
37870                // hook up the audio segment stream to the first track with aac data
37871                pipeline.coalesceStream.numberOfTracks++;
37872                pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options);
37873
37874                // Set up the final part of the audio pipeline
37875                pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
37876              }
37877            }
37878          });
37879
37880          // Re-emit any data coming from the coalesce stream to the outside world
37881          pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data'));
37882          // Let the consumer know we have finished flushing the entire pipeline
37883          pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
37884        };
37885
37886        // hook up the segment streams once track metadata is delivered
37887        this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) {
37888          var pipeline = this.transmuxPipeline_;
37889
37890          this.baseMediaDecodeTime = baseMediaDecodeTime;
37891          if (audioTrack) {
37892            audioTrack.timelineStartInfo.dts = undefined;
37893            audioTrack.timelineStartInfo.pts = undefined;
37894            clearDtsInfo(audioTrack);
37895            audioTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime;
37896            if (pipeline.audioTimestampRolloverStream) {
37897              pipeline.audioTimestampRolloverStream.discontinuity();
37898            }
37899          }
37900          if (videoTrack) {
37901            if (pipeline.videoSegmentStream) {
37902              pipeline.videoSegmentStream.gopCache_ = [];
37903              pipeline.videoTimestampRolloverStream.discontinuity();
37904            }
37905            videoTrack.timelineStartInfo.dts = undefined;
37906            videoTrack.timelineStartInfo.pts = undefined;
37907            clearDtsInfo(videoTrack);
37908            pipeline.captionStream.reset();
37909            videoTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime;
37910          }
37911
37912          if (pipeline.timedMetadataTimestampRolloverStream) {
37913            pipeline.timedMetadataTimestampRolloverStream.discontinuity();
37914          }
37915        };
37916
37917        this.setAudioAppendStart = function (timestamp) {
37918          if (audioTrack) {
37919            this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(timestamp);
37920          }
37921        };
37922
37923        this.alignGopsWith = function (gopsToAlignWith) {
37924          if (videoTrack && this.transmuxPipeline_.videoSegmentStream) {
37925            this.transmuxPipeline_.videoSegmentStream.alignGopsWith(gopsToAlignWith);
37926          }
37927        };
37928
37929        // feed incoming data to the front of the parsing pipeline
37930        this.push = function (data) {
37931          if (hasFlushed) {
37932            var isAac = isLikelyAacData(data);
37933
37934            if (isAac && this.transmuxPipeline_.type !== 'aac') {
37935              this.setupAacPipeline();
37936            } else if (!isAac && this.transmuxPipeline_.type !== 'ts') {
37937              this.setupTsPipeline();
37938            }
37939            hasFlushed = false;
37940          }
37941          this.transmuxPipeline_.headOfPipeline.push(data);
37942        };
37943
37944        // flush any buffered data
37945        this.flush = function () {
37946          hasFlushed = true;
37947          // Start at the top of the pipeline and flush all pending work
37948          this.transmuxPipeline_.headOfPipeline.flush();
37949        };
37950
37951        // Caption data has to be reset when seeking outside buffered range
37952        this.resetCaptions = function () {
37953          if (this.transmuxPipeline_.captionStream) {
37954            this.transmuxPipeline_.captionStream.reset();
37955          }
37956        };
37957      };
37958      _Transmuxer.prototype = new stream();
37959
37960      var transmuxer = {
37961        Transmuxer: _Transmuxer,
37962        VideoSegmentStream: _VideoSegmentStream,
37963        AudioSegmentStream: _AudioSegmentStream,
37964        AUDIO_PROPERTIES: AUDIO_PROPERTIES,
37965        VIDEO_PROPERTIES: VIDEO_PROPERTIES
37966      };
37967
37968      var mp4 = {
37969        generator: mp4Generator,
37970        Transmuxer: transmuxer.Transmuxer,
37971        AudioSegmentStream: transmuxer.AudioSegmentStream,
37972        VideoSegmentStream: transmuxer.VideoSegmentStream
37973      };
37974
37975      var classCallCheck$$1 = function classCallCheck$$1(instance, Constructor) {
37976        if (!(instance instanceof Constructor)) {
37977          throw new TypeError("Cannot call a class as a function");
37978        }
37979      };
37980
37981      var createClass$$1 = function () {
37982        function defineProperties(target, props) {
37983          for (var i = 0; i < props.length; i++) {
37984            var descriptor = props[i];
37985            descriptor.enumerable = descriptor.enumerable || false;
37986            descriptor.configurable = true;
37987            if ("value" in descriptor) descriptor.writable = true;
37988            Object.defineProperty(target, descriptor.key, descriptor);
37989          }
37990        }
37991
37992        return function (Constructor, protoProps, staticProps) {
37993          if (protoProps) defineProperties(Constructor.prototype, protoProps);
37994          if (staticProps) defineProperties(Constructor, staticProps);
37995          return Constructor;
37996        };
37997      }();
37998
37999      /**
38000       * @file transmuxer-worker.js
38001       */
38002
38003      /**
38004       * Re-emits transmuxer events by converting them into messages to the
38005       * world outside the worker.
38006       *
38007       * @param {Object} transmuxer the transmuxer to wire events on
38008       * @private
38009       */
38010      var wireTransmuxerEvents = function wireTransmuxerEvents(transmuxer) {
38011        transmuxer.on('data', function (segment) {
38012          // transfer ownership of the underlying ArrayBuffer
38013          // instead of doing a copy to save memory
38014          // ArrayBuffers are transferable but generic TypedArrays are not
38015          // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects)
38016          var initArray = segment.initSegment;
38017
38018          segment.initSegment = {
38019            data: initArray.buffer,
38020            byteOffset: initArray.byteOffset,
38021            byteLength: initArray.byteLength
38022          };
38023
38024          var typedArray = segment.data;
38025
38026          segment.data = typedArray.buffer;
38027          window_1$$1.postMessage({
38028            action: 'data',
38029            segment: segment,
38030            byteOffset: typedArray.byteOffset,
38031            byteLength: typedArray.byteLength
38032          }, [segment.data]);
38033        });
38034
38035        if (transmuxer.captionStream) {
38036          transmuxer.captionStream.on('data', function (caption) {
38037            window_1$$1.postMessage({
38038              action: 'caption',
38039              data: caption
38040            });
38041          });
38042        }
38043
38044        transmuxer.on('done', function (data) {
38045          window_1$$1.postMessage({ action: 'done' });
38046        });
38047
38048        transmuxer.on('gopInfo', function (gopInfo) {
38049          window_1$$1.postMessage({
38050            action: 'gopInfo',
38051            gopInfo: gopInfo
38052          });
38053        });
38054      };
38055
38056      /**
38057       * All incoming messages route through this hash. If no function exists
38058       * to handle an incoming message, then we ignore the message.
38059       *
38060       * @class MessageHandlers
38061       * @param {Object} options the options to initialize with
38062       */
38063
38064      var MessageHandlers = function () {
38065        function MessageHandlers(options) {
38066          classCallCheck$$1(this, MessageHandlers);
38067
38068          this.options = options || {};
38069          this.init();
38070        }
38071
38072        /**
38073         * initialize our web worker and wire all the events.
38074         */
38075
38076        createClass$$1(MessageHandlers, [{
38077          key: 'init',
38078          value: function init() {
38079            if (this.transmuxer) {
38080              this.transmuxer.dispose();
38081            }
38082            this.transmuxer = new mp4.Transmuxer(this.options);
38083            wireTransmuxerEvents(this.transmuxer);
38084          }
38085
38086          /**
38087           * Adds data (a ts segment) to the start of the transmuxer pipeline for
38088           * processing.
38089           *
38090           * @param {ArrayBuffer} data data to push into the muxer
38091           */
38092
38093        }, {
38094          key: 'push',
38095          value: function push(data) {
38096            // Cast array buffer to correct type for transmuxer
38097            var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);
38098
38099            this.transmuxer.push(segment);
38100          }
38101
38102          /**
38103           * Recreate the transmuxer so that the next segment added via `push`
38104           * start with a fresh transmuxer.
38105           */
38106
38107        }, {
38108          key: 'reset',
38109          value: function reset() {
38110            this.init();
38111          }
38112
38113          /**
38114           * Set the value that will be used as the `baseMediaDecodeTime` time for the
38115           * next segment pushed in. Subsequent segments will have their `baseMediaDecodeTime`
38116           * set relative to the first based on the PTS values.
38117           *
38118           * @param {Object} data used to set the timestamp offset in the muxer
38119           */
38120
38121        }, {
38122          key: 'setTimestampOffset',
38123          value: function setTimestampOffset(data) {
38124            var timestampOffset = data.timestampOffset || 0;
38125
38126            this.transmuxer.setBaseMediaDecodeTime(Math.round(timestampOffset * 90000));
38127          }
38128        }, {
38129          key: 'setAudioAppendStart',
38130          value: function setAudioAppendStart(data) {
38131            this.transmuxer.setAudioAppendStart(Math.ceil(data.appendStart * 90000));
38132          }
38133
38134          /**
38135           * Forces the pipeline to finish processing the last segment and emit it's
38136           * results.
38137           *
38138           * @param {Object} data event data, not really used
38139           */
38140
38141        }, {
38142          key: 'flush',
38143          value: function flush(data) {
38144            this.transmuxer.flush();
38145          }
38146        }, {
38147          key: 'resetCaptions',
38148          value: function resetCaptions() {
38149            this.transmuxer.resetCaptions();
38150          }
38151        }, {
38152          key: 'alignGopsWith',
38153          value: function alignGopsWith(data) {
38154            this.transmuxer.alignGopsWith(data.gopsToAlignWith.slice());
38155          }
38156        }]);
38157        return MessageHandlers;
38158      }();
38159
38160      /**
38161       * Our web wroker interface so that things can talk to mux.js
38162       * that will be running in a web worker. the scope is passed to this by
38163       * webworkify.
38164       *
38165       * @param {Object} self the scope for the web worker
38166       */
38167
38168      var TransmuxerWorker = function TransmuxerWorker(self) {
38169        self.onmessage = function (event) {
38170          if (event.data.action === 'init' && event.data.options) {
38171            this.messageHandlers = new MessageHandlers(event.data.options);
38172            return;
38173          }
38174
38175          if (!this.messageHandlers) {
38176            this.messageHandlers = new MessageHandlers();
38177          }
38178
38179          if (event.data && event.data.action && event.data.action !== 'init') {
38180            if (this.messageHandlers[event.data.action]) {
38181              this.messageHandlers[event.data.action](event.data);
38182            }
38183          }
38184        };
38185      };
38186
38187      var transmuxerWorker = new TransmuxerWorker(self);
38188
38189      return transmuxerWorker;
38190    }();
38191  });
38192
38193  /**
38194   * @file codec-utils.js
38195   */
38196
38197  /**
38198   * Check if a codec string refers to an audio codec.
38199   *
38200   * @param {String} codec codec string to check
38201   * @return {Boolean} if this is an audio codec
38202   * @private
38203   */
38204  var isAudioCodec = function isAudioCodec(codec) {
38205    return (/mp4a\.\d+.\d+/i.test(codec)
38206    );
38207  };
38208
38209  /**
38210   * Check if a codec string refers to a video codec.
38211   *
38212   * @param {String} codec codec string to check
38213   * @return {Boolean} if this is a video codec
38214   * @private
38215   */
38216  var isVideoCodec = function isVideoCodec(codec) {
38217    return (/avc1\.[\da-f]+/i.test(codec)
38218    );
38219  };
38220
38221  /**
38222   * Parse a content type header into a type and parameters
38223   * object
38224   *
38225   * @param {String} type the content type header
38226   * @return {Object} the parsed content-type
38227   * @private
38228   */
38229  var parseContentType = function parseContentType(type) {
38230    var object = { type: '', parameters: {} };
38231    var parameters = type.trim().split(';');
38232
38233    // first parameter should always be content-type
38234    object.type = parameters.shift().trim();
38235    parameters.forEach(function (parameter) {
38236      var pair = parameter.trim().split('=');
38237
38238      if (pair.length > 1) {
38239        var name = pair[0].replace(/"/g, '').trim();
38240        var value = pair[1].replace(/"/g, '').trim();
38241
38242        object.parameters[name] = value;
38243      }
38244    });
38245
38246    return object;
38247  };
38248
38249  /**
38250   * Replace the old apple-style `avc1.<dd>.<dd>` codec string with the standard
38251   * `avc1.<hhhhhh>`
38252   *
38253   * @param {Array} codecs an array of codec strings to fix
38254   * @return {Array} the translated codec array
38255   * @private
38256   */
38257  var translateLegacyCodecs = function translateLegacyCodecs(codecs) {
38258    return codecs.map(function (codec) {
38259      return codec.replace(/avc1\.(\d+)\.(\d+)/i, function (orig, profile, avcLevel) {
38260        var profileHex = ('00' + Number(profile).toString(16)).slice(-2);
38261        var avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2);
38262
38263        return 'avc1.' + profileHex + '00' + avcLevelHex;
38264      });
38265    });
38266  };
38267
38268  /**
38269   * @file virtual-source-buffer.js
38270   */
38271
38272  // We create a wrapper around the SourceBuffer so that we can manage the
38273  // state of the `updating` property manually. We have to do this because
38274  // Firefox changes `updating` to false long before triggering `updateend`
38275  // events and that was causing strange problems in videojs-contrib-hls
38276  var makeWrappedSourceBuffer = function makeWrappedSourceBuffer(mediaSource, mimeType) {
38277    var sourceBuffer = mediaSource.addSourceBuffer(mimeType);
38278    var wrapper = Object.create(null);
38279
38280    wrapper.updating = false;
38281    wrapper.realBuffer_ = sourceBuffer;
38282
38283    var _loop = function _loop(key) {
38284      if (typeof sourceBuffer[key] === 'function') {
38285        wrapper[key] = function () {
38286          return sourceBuffer[key].apply(sourceBuffer, arguments);
38287        };
38288      } else if (typeof wrapper[key] === 'undefined') {
38289        Object.defineProperty(wrapper, key, {
38290          get: function get$$1() {
38291            return sourceBuffer[key];
38292          },
38293          set: function set$$1(v) {
38294            return sourceBuffer[key] = v;
38295          }
38296        });
38297      }
38298    };
38299
38300    for (var key in sourceBuffer) {
38301      _loop(key);
38302    }
38303
38304    return wrapper;
38305  };
38306
38307  /**
38308   * Returns a list of gops in the buffer that have a pts value of 3 seconds or more in
38309   * front of current time.
38310   *
38311   * @param {Array} buffer
38312   *        The current buffer of gop information
38313   * @param {Player} player
38314   *        The player instance
38315   * @param {Double} mapping
38316   *        Offset to map display time to stream presentation time
38317   * @return {Array}
38318   *         List of gops considered safe to append over
38319   */
38320  var gopsSafeToAlignWith = function gopsSafeToAlignWith(buffer, player, mapping) {
38321    if (!player || !buffer.length) {
38322      return [];
38323    }
38324
38325    // pts value for current time + 3 seconds to give a bit more wiggle room
38326    var currentTimePts = Math.ceil((player.currentTime() - mapping + 3) * 90000);
38327
38328    var i = void 0;
38329
38330    for (i = 0; i < buffer.length; i++) {
38331      if (buffer[i].pts > currentTimePts) {
38332        break;
38333      }
38334    }
38335
38336    return buffer.slice(i);
38337  };
38338
38339  /**
38340   * Appends gop information (timing and byteLength) received by the transmuxer for the
38341   * gops appended in the last call to appendBuffer
38342   *
38343   * @param {Array} buffer
38344   *        The current buffer of gop information
38345   * @param {Array} gops
38346   *        List of new gop information
38347   * @param {boolean} replace
38348   *        If true, replace the buffer with the new gop information. If false, append the
38349   *        new gop information to the buffer in the right location of time.
38350   * @return {Array}
38351   *         Updated list of gop information
38352   */
38353  var updateGopBuffer = function updateGopBuffer(buffer, gops, replace) {
38354    if (!gops.length) {
38355      return buffer;
38356    }
38357
38358    if (replace) {
38359      // If we are in safe append mode, then completely overwrite the gop buffer
38360      // with the most recent appeneded data. This will make sure that when appending
38361      // future segments, we only try to align with gops that are both ahead of current
38362      // time and in the last segment appended.
38363      return gops.slice();
38364    }
38365
38366    var start = gops[0].pts;
38367
38368    var i = 0;
38369
38370    for (i; i < buffer.length; i++) {
38371      if (buffer[i].pts >= start) {
38372        break;
38373      }
38374    }
38375
38376    return buffer.slice(0, i).concat(gops);
38377  };
38378
38379  /**
38380   * Removes gop information in buffer that overlaps with provided start and end
38381   *
38382   * @param {Array} buffer
38383   *        The current buffer of gop information
38384   * @param {Double} start
38385   *        position to start the remove at
38386   * @param {Double} end
38387   *        position to end the remove at
38388   * @param {Double} mapping
38389   *        Offset to map display time to stream presentation time
38390   */
38391  var removeGopBuffer = function removeGopBuffer(buffer, start, end, mapping) {
38392    var startPts = Math.ceil((start - mapping) * 90000);
38393    var endPts = Math.ceil((end - mapping) * 90000);
38394    var updatedBuffer = buffer.slice();
38395
38396    var i = buffer.length;
38397
38398    while (i--) {
38399      if (buffer[i].pts <= endPts) {
38400        break;
38401      }
38402    }
38403
38404    if (i === -1) {
38405      // no removal because end of remove range is before start of buffer
38406      return updatedBuffer;
38407    }
38408
38409    var j = i + 1;
38410
38411    while (j--) {
38412      if (buffer[j].pts <= startPts) {
38413        break;
38414      }
38415    }
38416
38417    // clamp remove range start to 0 index
38418    j = Math.max(j, 0);
38419
38420    updatedBuffer.splice(j, i - j + 1);
38421
38422    return updatedBuffer;
38423  };
38424
38425  /**
38426   * VirtualSourceBuffers exist so that we can transmux non native formats
38427   * into a native format, but keep the same api as a native source buffer.
38428   * It creates a transmuxer, that works in its own thread (a web worker) and
38429   * that transmuxer muxes the data into a native format. VirtualSourceBuffer will
38430   * then send all of that data to the naive sourcebuffer so that it is
38431   * indestinguishable from a natively supported format.
38432   *
38433   * @param {HtmlMediaSource} mediaSource the parent mediaSource
38434   * @param {Array} codecs array of codecs that we will be dealing with
38435   * @class VirtualSourceBuffer
38436   * @extends video.js.EventTarget
38437   */
38438
38439  var VirtualSourceBuffer = function (_videojs$EventTarget) {
38440    inherits$3(VirtualSourceBuffer, _videojs$EventTarget);
38441
38442    function VirtualSourceBuffer(mediaSource, codecs) {
38443      classCallCheck$3(this, VirtualSourceBuffer);
38444
38445      var _this = possibleConstructorReturn$3(this, (VirtualSourceBuffer.__proto__ || Object.getPrototypeOf(VirtualSourceBuffer)).call(this, videojs$1.EventTarget));
38446
38447      _this.timestampOffset_ = 0;
38448      _this.pendingBuffers_ = [];
38449      _this.bufferUpdating_ = false;
38450
38451      _this.mediaSource_ = mediaSource;
38452      _this.codecs_ = codecs;
38453      _this.audioCodec_ = null;
38454      _this.videoCodec_ = null;
38455      _this.audioDisabled_ = false;
38456      _this.appendAudioInitSegment_ = true;
38457      _this.gopBuffer_ = [];
38458      _this.timeMapping_ = 0;
38459      _this.safeAppend_ = videojs$1.browser.IE_VERSION >= 11;
38460
38461      var options = {
38462        remux: false,
38463        alignGopsAtEnd: _this.safeAppend_
38464      };
38465
38466      _this.codecs_.forEach(function (codec) {
38467        if (isAudioCodec(codec)) {
38468          _this.audioCodec_ = codec;
38469        } else if (isVideoCodec(codec)) {
38470          _this.videoCodec_ = codec;
38471        }
38472      });
38473
38474      // append muxed segments to their respective native buffers as
38475      // soon as they are available
38476      _this.transmuxer_ = new TransmuxWorker();
38477      _this.transmuxer_.postMessage({ action: 'init', options: options });
38478
38479      _this.transmuxer_.onmessage = function (event) {
38480        if (event.data.action === 'data') {
38481          return _this.data_(event);
38482        }
38483
38484        if (event.data.action === 'done') {
38485          return _this.done_(event);
38486        }
38487
38488        if (event.data.action === 'gopInfo') {
38489          return _this.appendGopInfo_(event);
38490        }
38491      };
38492
38493      // this timestampOffset is a property with the side-effect of resetting
38494      // baseMediaDecodeTime in the transmuxer on the setter
38495      Object.defineProperty(_this, 'timestampOffset', {
38496        get: function get$$1() {
38497          return this.timestampOffset_;
38498        },
38499        set: function set$$1(val) {
38500          if (typeof val === 'number' && val >= 0) {
38501            this.timestampOffset_ = val;
38502            this.appendAudioInitSegment_ = true;
38503
38504            // reset gop buffer on timestampoffset as this signals a change in timeline
38505            this.gopBuffer_.length = 0;
38506            this.timeMapping_ = 0;
38507
38508            // We have to tell the transmuxer to set the baseMediaDecodeTime to
38509            // the desired timestampOffset for the next segment
38510            this.transmuxer_.postMessage({
38511              action: 'setTimestampOffset',
38512              timestampOffset: val
38513            });
38514          }
38515        }
38516      });
38517
38518      // setting the append window affects both source buffers
38519      Object.defineProperty(_this, 'appendWindowStart', {
38520        get: function get$$1() {
38521          return (this.videoBuffer_ || this.audioBuffer_).appendWindowStart;
38522        },
38523        set: function set$$1(start) {
38524          if (this.videoBuffer_) {
38525            this.videoBuffer_.appendWindowStart = start;
38526          }
38527          if (this.audioBuffer_) {
38528            this.audioBuffer_.appendWindowStart = start;
38529          }
38530        }
38531      });
38532
38533      // this buffer is "updating" if either of its native buffers are
38534      Object.defineProperty(_this, 'updating', {
38535        get: function get$$1() {
38536          return !!(this.bufferUpdating_ || !this.audioDisabled_ && this.audioBuffer_ && this.audioBuffer_.updating || this.videoBuffer_ && this.videoBuffer_.updating);
38537        }
38538      });
38539
38540      // the buffered property is the intersection of the buffered
38541      // ranges of the native source buffers
38542      Object.defineProperty(_this, 'buffered', {
38543        get: function get$$1() {
38544          var start = null;
38545          var end = null;
38546          var arity = 0;
38547          var extents = [];
38548          var ranges = [];
38549
38550          // neither buffer has been created yet
38551          if (!this.videoBuffer_ && !this.audioBuffer_) {
38552            return videojs$1.createTimeRange();
38553          }
38554
38555          // only one buffer is configured
38556          if (!this.videoBuffer_) {
38557            return this.audioBuffer_.buffered;
38558          }
38559          if (!this.audioBuffer_) {
38560            return this.videoBuffer_.buffered;
38561          }
38562
38563          // both buffers are configured
38564          if (this.audioDisabled_) {
38565            return this.videoBuffer_.buffered;
38566          }
38567
38568          // both buffers are empty
38569          if (this.videoBuffer_.buffered.length === 0 && this.audioBuffer_.buffered.length === 0) {
38570            return videojs$1.createTimeRange();
38571          }
38572
38573          // Handle the case where we have both buffers and create an
38574          // intersection of the two
38575          var videoBuffered = this.videoBuffer_.buffered;
38576          var audioBuffered = this.audioBuffer_.buffered;
38577          var count = videoBuffered.length;
38578
38579          // A) Gather up all start and end times
38580          while (count--) {
38581            extents.push({ time: videoBuffered.start(count), type: 'start' });
38582            extents.push({ time: videoBuffered.end(count), type: 'end' });
38583          }
38584          count = audioBuffered.length;
38585          while (count--) {
38586            extents.push({ time: audioBuffered.start(count), type: 'start' });
38587            extents.push({ time: audioBuffered.end(count), type: 'end' });
38588          }
38589          // B) Sort them by time
38590          extents.sort(function (a, b) {
38591            return a.time - b.time;
38592          });
38593
38594          // C) Go along one by one incrementing arity for start and decrementing
38595          //    arity for ends
38596          for (count = 0; count < extents.length; count++) {
38597            if (extents[count].type === 'start') {
38598              arity++;
38599
38600              // D) If arity is ever incremented to 2 we are entering an
38601              //    overlapping range
38602              if (arity === 2) {
38603                start = extents[count].time;
38604              }
38605            } else if (extents[count].type === 'end') {
38606              arity--;
38607
38608              // E) If arity is ever decremented to 1 we leaving an
38609              //    overlapping range
38610              if (arity === 1) {
38611                end = extents[count].time;
38612              }
38613            }
38614
38615            // F) Record overlapping ranges
38616            if (start !== null && end !== null) {
38617              ranges.push([start, end]);
38618              start = null;
38619              end = null;
38620            }
38621          }
38622
38623          return videojs$1.createTimeRanges(ranges);
38624        }
38625      });
38626      return _this;
38627    }
38628
38629    /**
38630     * When we get a data event from the transmuxer
38631     * we call this function and handle the data that
38632     * was sent to us
38633     *
38634     * @private
38635     * @param {Event} event the data event from the transmuxer
38636     */
38637
38638    createClass$2(VirtualSourceBuffer, [{
38639      key: 'data_',
38640      value: function data_(event) {
38641        var segment = event.data.segment;
38642
38643        // Cast ArrayBuffer to TypedArray
38644        segment.data = new Uint8Array(segment.data, event.data.byteOffset, event.data.byteLength);
38645
38646        segment.initSegment = new Uint8Array(segment.initSegment.data, segment.initSegment.byteOffset, segment.initSegment.byteLength);
38647
38648        createTextTracksIfNecessary(this, this.mediaSource_, segment);
38649
38650        // Add the segments to the pendingBuffers array
38651        this.pendingBuffers_.push(segment);
38652        return;
38653      }
38654
38655      /**
38656       * When we get a done event from the transmuxer
38657       * we call this function and we process all
38658       * of the pending data that we have been saving in the
38659       * data_ function
38660       *
38661       * @private
38662       * @param {Event} event the done event from the transmuxer
38663       */
38664
38665    }, {
38666      key: 'done_',
38667      value: function done_(event) {
38668        // Don't process and append data if the mediaSource is closed
38669        if (this.mediaSource_.readyState === 'closed') {
38670          this.pendingBuffers_.length = 0;
38671          return;
38672        }
38673
38674        // All buffers should have been flushed from the muxer
38675        // start processing anything we have received
38676        this.processPendingSegments_();
38677        return;
38678      }
38679
38680      /**
38681       * Create our internal native audio/video source buffers and add
38682       * event handlers to them with the following conditions:
38683       * 1. they do not already exist on the mediaSource
38684       * 2. this VSB has a codec for them
38685       *
38686       * @private
38687       */
38688
38689    }, {
38690      key: 'createRealSourceBuffers_',
38691      value: function createRealSourceBuffers_() {
38692        var _this2 = this;
38693
38694        var types = ['audio', 'video'];
38695
38696        types.forEach(function (type) {
38697          // Don't create a SourceBuffer of this type if we don't have a
38698          // codec for it
38699          if (!_this2[type + 'Codec_']) {
38700            return;
38701          }
38702
38703          // Do nothing if a SourceBuffer of this type already exists
38704          if (_this2[type + 'Buffer_']) {
38705            return;
38706          }
38707
38708          var buffer = null;
38709
38710          // If the mediasource already has a SourceBuffer for the codec
38711          // use that
38712          if (_this2.mediaSource_[type + 'Buffer_']) {
38713            buffer = _this2.mediaSource_[type + 'Buffer_'];
38714            // In multiple audio track cases, the audio source buffer is disabled
38715            // on the main VirtualSourceBuffer by the HTMLMediaSource much earlier
38716            // than createRealSourceBuffers_ is called to create the second
38717            // VirtualSourceBuffer because that happens as a side-effect of
38718            // videojs-contrib-hls starting the audioSegmentLoader. As a result,
38719            // the audioBuffer is essentially "ownerless" and no one will toggle
38720            // the `updating` state back to false once the `updateend` event is received
38721            //
38722            // Setting `updating` to false manually will work around this
38723            // situation and allow work to continue
38724            buffer.updating = false;
38725          } else {
38726            var codecProperty = type + 'Codec_';
38727            var mimeType = type + '/mp4;codecs="' + _this2[codecProperty] + '"';
38728
38729            buffer = makeWrappedSourceBuffer(_this2.mediaSource_.nativeMediaSource_, mimeType);
38730
38731            _this2.mediaSource_[type + 'Buffer_'] = buffer;
38732          }
38733
38734          _this2[type + 'Buffer_'] = buffer;
38735
38736          // Wire up the events to the SourceBuffer
38737          ['update', 'updatestart', 'updateend'].forEach(function (event) {
38738            buffer.addEventListener(event, function () {
38739              // if audio is disabled
38740              if (type === 'audio' && _this2.audioDisabled_) {
38741                return;
38742              }
38743
38744              if (event === 'updateend') {
38745                _this2[type + 'Buffer_'].updating = false;
38746              }
38747
38748              var shouldTrigger = types.every(function (t) {
38749                // skip checking audio's updating status if audio
38750                // is not enabled
38751                if (t === 'audio' && _this2.audioDisabled_) {
38752                  return true;
38753                }
38754                // if the other type if updating we don't trigger
38755                if (type !== t && _this2[t + 'Buffer_'] && _this2[t + 'Buffer_'].updating) {
38756                  return false;
38757                }
38758                return true;
38759              });
38760
38761              if (shouldTrigger) {
38762                return _this2.trigger(event);
38763              }
38764            });
38765          });
38766        });
38767      }
38768
38769      /**
38770       * Emulate the native mediasource function, but our function will
38771       * send all of the proposed segments to the transmuxer so that we
38772       * can transmux them before we append them to our internal
38773       * native source buffers in the correct format.
38774       *
38775       * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/appendBuffer
38776       * @param {Uint8Array} segment the segment to append to the buffer
38777       */
38778
38779    }, {
38780      key: 'appendBuffer',
38781      value: function appendBuffer(segment) {
38782        // Start the internal "updating" state
38783        this.bufferUpdating_ = true;
38784
38785        if (this.audioBuffer_ && this.audioBuffer_.buffered.length) {
38786          var audioBuffered = this.audioBuffer_.buffered;
38787
38788          this.transmuxer_.postMessage({
38789            action: 'setAudioAppendStart',
38790            appendStart: audioBuffered.end(audioBuffered.length - 1)
38791          });
38792        }
38793
38794        if (this.videoBuffer_) {
38795          this.transmuxer_.postMessage({
38796            action: 'alignGopsWith',
38797            gopsToAlignWith: gopsSafeToAlignWith(this.gopBuffer_, this.mediaSource_.player_, this.timeMapping_)
38798          });
38799        }
38800
38801        this.transmuxer_.postMessage({
38802          action: 'push',
38803          // Send the typed-array of data as an ArrayBuffer so that
38804          // it can be sent as a "Transferable" and avoid the costly
38805          // memory copy
38806          data: segment.buffer,
38807
38808          // To recreate the original typed-array, we need information
38809          // about what portion of the ArrayBuffer it was a view into
38810          byteOffset: segment.byteOffset,
38811          byteLength: segment.byteLength
38812        }, [segment.buffer]);
38813        this.transmuxer_.postMessage({ action: 'flush' });
38814      }
38815
38816      /**
38817       * Appends gop information (timing and byteLength) received by the transmuxer for the
38818       * gops appended in the last call to appendBuffer
38819       *
38820       * @param {Event} event
38821       *        The gopInfo event from the transmuxer
38822       * @param {Array} event.data.gopInfo
38823       *        List of gop info to append
38824       */
38825
38826    }, {
38827      key: 'appendGopInfo_',
38828      value: function appendGopInfo_(event) {
38829        this.gopBuffer_ = updateGopBuffer(this.gopBuffer_, event.data.gopInfo, this.safeAppend_);
38830      }
38831
38832      /**
38833       * Emulate the native mediasource function and remove parts
38834       * of the buffer from any of our internal buffers that exist
38835       *
38836       * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/remove
38837       * @param {Double} start position to start the remove at
38838       * @param {Double} end position to end the remove at
38839       */
38840
38841    }, {
38842      key: 'remove',
38843      value: function remove(start, end) {
38844        if (this.videoBuffer_) {
38845          this.videoBuffer_.updating = true;
38846          this.videoBuffer_.remove(start, end);
38847          this.gopBuffer_ = removeGopBuffer(this.gopBuffer_, start, end, this.timeMapping_);
38848        }
38849        if (!this.audioDisabled_ && this.audioBuffer_) {
38850          this.audioBuffer_.updating = true;
38851          this.audioBuffer_.remove(start, end);
38852        }
38853
38854        // Remove Metadata Cues (id3)
38855        removeCuesFromTrack(start, end, this.metadataTrack_);
38856
38857        // Remove Any Captions
38858        if (this.inbandTextTracks_) {
38859          for (var track in this.inbandTextTracks_) {
38860            removeCuesFromTrack(start, end, this.inbandTextTracks_[track]);
38861          }
38862        }
38863      }
38864
38865      /**
38866       * Process any segments that the muxer has output
38867       * Concatenate segments together based on type and append them into
38868       * their respective sourceBuffers
38869       *
38870       * @private
38871       */
38872
38873    }, {
38874      key: 'processPendingSegments_',
38875      value: function processPendingSegments_() {
38876        var sortedSegments = {
38877          video: {
38878            segments: [],
38879            bytes: 0
38880          },
38881          audio: {
38882            segments: [],
38883            bytes: 0
38884          },
38885          captions: [],
38886          metadata: []
38887        };
38888
38889        // Sort segments into separate video/audio arrays and
38890        // keep track of their total byte lengths
38891        sortedSegments = this.pendingBuffers_.reduce(function (segmentObj, segment) {
38892          var type = segment.type;
38893          var data = segment.data;
38894          var initSegment = segment.initSegment;
38895
38896          segmentObj[type].segments.push(data);
38897          segmentObj[type].bytes += data.byteLength;
38898
38899          segmentObj[type].initSegment = initSegment;
38900
38901          // Gather any captions into a single array
38902          if (segment.captions) {
38903            segmentObj.captions = segmentObj.captions.concat(segment.captions);
38904          }
38905
38906          if (segment.info) {
38907            segmentObj[type].info = segment.info;
38908          }
38909
38910          // Gather any metadata into a single array
38911          if (segment.metadata) {
38912            segmentObj.metadata = segmentObj.metadata.concat(segment.metadata);
38913          }
38914
38915          return segmentObj;
38916        }, sortedSegments);
38917
38918        // Create the real source buffers if they don't exist by now since we
38919        // finally are sure what tracks are contained in the source
38920        if (!this.videoBuffer_ && !this.audioBuffer_) {
38921          // Remove any codecs that may have been specified by default but
38922          // are no longer applicable now
38923          if (sortedSegments.video.bytes === 0) {
38924            this.videoCodec_ = null;
38925          }
38926          if (sortedSegments.audio.bytes === 0) {
38927            this.audioCodec_ = null;
38928          }
38929
38930          this.createRealSourceBuffers_();
38931        }
38932
38933        if (sortedSegments.audio.info) {
38934          this.mediaSource_.trigger({ type: 'audioinfo', info: sortedSegments.audio.info });
38935        }
38936        if (sortedSegments.video.info) {
38937          this.mediaSource_.trigger({ type: 'videoinfo', info: sortedSegments.video.info });
38938        }
38939
38940        if (this.appendAudioInitSegment_) {
38941          if (!this.audioDisabled_ && this.audioBuffer_) {
38942            sortedSegments.audio.segments.unshift(sortedSegments.audio.initSegment);
38943            sortedSegments.audio.bytes += sortedSegments.audio.initSegment.byteLength;
38944          }
38945          this.appendAudioInitSegment_ = false;
38946        }
38947
38948        var triggerUpdateend = false;
38949
38950        // Merge multiple video and audio segments into one and append
38951        if (this.videoBuffer_ && sortedSegments.video.bytes) {
38952          sortedSegments.video.segments.unshift(sortedSegments.video.initSegment);
38953          sortedSegments.video.bytes += sortedSegments.video.initSegment.byteLength;
38954          this.concatAndAppendSegments_(sortedSegments.video, this.videoBuffer_);
38955          // TODO: are video tracks the only ones with text tracks?
38956          addTextTrackData(this, sortedSegments.captions, sortedSegments.metadata);
38957        } else if (this.videoBuffer_ && (this.audioDisabled_ || !this.audioBuffer_)) {
38958          // The transmuxer did not return any bytes of video, meaning it was all trimmed
38959          // for gop alignment. Since we have a video buffer and audio is disabled, updateend
38960          // will never be triggered by this source buffer, which will cause contrib-hls
38961          // to be stuck forever waiting for updateend. If audio is not disabled, updateend
38962          // will be triggered by the audio buffer, which will be sent upwards since the video
38963          // buffer will not be in an updating state.
38964          triggerUpdateend = true;
38965        }
38966
38967        if (!this.audioDisabled_ && this.audioBuffer_) {
38968          this.concatAndAppendSegments_(sortedSegments.audio, this.audioBuffer_);
38969        }
38970
38971        this.pendingBuffers_.length = 0;
38972
38973        if (triggerUpdateend) {
38974          this.trigger('updateend');
38975        }
38976
38977        // We are no longer in the internal "updating" state
38978        this.bufferUpdating_ = false;
38979      }
38980
38981      /**
38982       * Combine all segments into a single Uint8Array and then append them
38983       * to the destination buffer
38984       *
38985       * @param {Object} segmentObj
38986       * @param {SourceBuffer} destinationBuffer native source buffer to append data to
38987       * @private
38988       */
38989
38990    }, {
38991      key: 'concatAndAppendSegments_',
38992      value: function concatAndAppendSegments_(segmentObj, destinationBuffer) {
38993        var offset = 0;
38994        var tempBuffer = void 0;
38995
38996        if (segmentObj.bytes) {
38997          tempBuffer = new Uint8Array(segmentObj.bytes);
38998
38999          // Combine the individual segments into one large typed-array
39000          segmentObj.segments.forEach(function (segment) {
39001            tempBuffer.set(segment, offset);
39002            offset += segment.byteLength;
39003          });
39004
39005          try {
39006            destinationBuffer.updating = true;
39007            destinationBuffer.appendBuffer(tempBuffer);
39008          } catch (error) {
39009            if (this.mediaSource_.player_) {
39010              this.mediaSource_.player_.error({
39011                code: -3,
39012                type: 'APPEND_BUFFER_ERR',
39013                message: error.message,
39014                originalError: error
39015              });
39016            }
39017          }
39018        }
39019      }
39020
39021      /**
39022       * Emulate the native mediasource function. abort any soureBuffer
39023       * actions and throw out any un-appended data.
39024       *
39025       * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/abort
39026       */
39027
39028    }, {
39029      key: 'abort',
39030      value: function abort() {
39031        if (this.videoBuffer_) {
39032          this.videoBuffer_.abort();
39033        }
39034        if (!this.audioDisabled_ && this.audioBuffer_) {
39035          this.audioBuffer_.abort();
39036        }
39037        if (this.transmuxer_) {
39038          this.transmuxer_.postMessage({ action: 'reset' });
39039        }
39040        this.pendingBuffers_.length = 0;
39041        this.bufferUpdating_ = false;
39042      }
39043    }]);
39044    return VirtualSourceBuffer;
39045  }(videojs$1.EventTarget);
39046
39047  /**
39048   * @file html-media-source.js
39049   */
39050
39051  /**
39052   * Our MediaSource implementation in HTML, mimics native
39053   * MediaSource where/if possible.
39054   *
39055   * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource
39056   * @class HtmlMediaSource
39057   * @extends videojs.EventTarget
39058   */
39059
39060  var HtmlMediaSource = function (_videojs$EventTarget) {
39061    inherits$3(HtmlMediaSource, _videojs$EventTarget);
39062
39063    function HtmlMediaSource() {
39064      classCallCheck$3(this, HtmlMediaSource);
39065
39066      var _this = possibleConstructorReturn$3(this, (HtmlMediaSource.__proto__ || Object.getPrototypeOf(HtmlMediaSource)).call(this));
39067
39068      var property = void 0;
39069
39070      _this.nativeMediaSource_ = new window_1.MediaSource();
39071      // delegate to the native MediaSource's methods by default
39072      for (property in _this.nativeMediaSource_) {
39073        if (!(property in HtmlMediaSource.prototype) && typeof _this.nativeMediaSource_[property] === 'function') {
39074          _this[property] = _this.nativeMediaSource_[property].bind(_this.nativeMediaSource_);
39075        }
39076      }
39077
39078      // emulate `duration` and `seekable` until seeking can be
39079      // handled uniformly for live streams
39080      // see https://github.com/w3c/media-source/issues/5
39081      _this.duration_ = NaN;
39082      Object.defineProperty(_this, 'duration', {
39083        get: function get$$1() {
39084          if (this.duration_ === Infinity) {
39085            return this.duration_;
39086          }
39087          return this.nativeMediaSource_.duration;
39088        },
39089        set: function set$$1(duration) {
39090          this.duration_ = duration;
39091          if (duration !== Infinity) {
39092            this.nativeMediaSource_.duration = duration;
39093            return;
39094          }
39095        }
39096      });
39097      Object.defineProperty(_this, 'seekable', {
39098        get: function get$$1() {
39099          if (this.duration_ === Infinity) {
39100            return videojs$1.createTimeRanges([[0, this.nativeMediaSource_.duration]]);
39101          }
39102          return this.nativeMediaSource_.seekable;
39103        }
39104      });
39105
39106      Object.defineProperty(_this, 'readyState', {
39107        get: function get$$1() {
39108          return this.nativeMediaSource_.readyState;
39109        }
39110      });
39111
39112      Object.defineProperty(_this, 'activeSourceBuffers', {
39113        get: function get$$1() {
39114          return this.activeSourceBuffers_;
39115        }
39116      });
39117
39118      // the list of virtual and native SourceBuffers created by this
39119      // MediaSource
39120      _this.sourceBuffers = [];
39121
39122      _this.activeSourceBuffers_ = [];
39123
39124      /**
39125       * update the list of active source buffers based upon various
39126       * imformation from HLS and video.js
39127       *
39128       * @private
39129       */
39130      _this.updateActiveSourceBuffers_ = function () {
39131        // Retain the reference but empty the array
39132        _this.activeSourceBuffers_.length = 0;
39133
39134        // If there is only one source buffer, then it will always be active and audio will
39135        // be disabled based on the codec of the source buffer
39136        if (_this.sourceBuffers.length === 1) {
39137          var sourceBuffer = _this.sourceBuffers[0];
39138
39139          sourceBuffer.appendAudioInitSegment_ = true;
39140          sourceBuffer.audioDisabled_ = !sourceBuffer.audioCodec_;
39141          _this.activeSourceBuffers_.push(sourceBuffer);
39142          return;
39143        }
39144
39145        // There are 2 source buffers, a combined (possibly video only) source buffer and
39146        // and an audio only source buffer.
39147        // By default, the audio in the combined virtual source buffer is enabled
39148        // and the audio-only source buffer (if it exists) is disabled.
39149        var disableCombined = false;
39150        var disableAudioOnly = true;
39151
39152        // TODO: maybe we can store the sourcebuffers on the track objects?
39153        // safari may do something like this
39154        for (var i = 0; i < _this.player_.audioTracks().length; i++) {
39155          var track = _this.player_.audioTracks()[i];
39156
39157          if (track.enabled && track.kind !== 'main') {
39158            // The enabled track is an alternate audio track so disable the audio in
39159            // the combined source buffer and enable the audio-only source buffer.
39160            disableCombined = true;
39161            disableAudioOnly = false;
39162            break;
39163          }
39164        }
39165
39166        _this.sourceBuffers.forEach(function (sourceBuffer, index) {
39167          /* eslinst-disable */
39168          // TODO once codecs are required, we can switch to using the codecs to determine
39169          //      what stream is the video stream, rather than relying on videoTracks
39170          /* eslinst-enable */
39171
39172          sourceBuffer.appendAudioInitSegment_ = true;
39173
39174          if (sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) {
39175            // combined
39176            sourceBuffer.audioDisabled_ = disableCombined;
39177          } else if (sourceBuffer.videoCodec_ && !sourceBuffer.audioCodec_) {
39178            // If the "combined" source buffer is video only, then we do not want
39179            // disable the audio-only source buffer (this is mostly for demuxed
39180            // audio and video hls)
39181            sourceBuffer.audioDisabled_ = true;
39182            disableAudioOnly = false;
39183          } else if (!sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) {
39184            // audio only
39185            // In the case of audio only with alternate audio and disableAudioOnly is true
39186            // this means we want to disable the audio on the alternate audio sourcebuffer
39187            // but not the main "combined" source buffer. The "combined" source buffer is
39188            // always at index 0, so this ensures audio won't be disabled in both source
39189            // buffers.
39190            sourceBuffer.audioDisabled_ = index ? disableAudioOnly : !disableAudioOnly;
39191            if (sourceBuffer.audioDisabled_) {
39192              return;
39193            }
39194          }
39195
39196          _this.activeSourceBuffers_.push(sourceBuffer);
39197        });
39198      };
39199
39200      _this.onPlayerMediachange_ = function () {
39201        _this.sourceBuffers.forEach(function (sourceBuffer) {
39202          sourceBuffer.appendAudioInitSegment_ = true;
39203        });
39204      };
39205
39206      _this.onHlsReset_ = function () {
39207        _this.sourceBuffers.forEach(function (sourceBuffer) {
39208          if (sourceBuffer.transmuxer_) {
39209            sourceBuffer.transmuxer_.postMessage({ action: 'resetCaptions' });
39210          }
39211        });
39212      };
39213
39214      _this.onHlsSegmentTimeMapping_ = function (event) {
39215        _this.sourceBuffers.forEach(function (buffer) {
39216          return buffer.timeMapping_ = event.mapping;
39217        });
39218      };
39219
39220      // Re-emit MediaSource events on the polyfill
39221      ['sourceopen', 'sourceclose', 'sourceended'].forEach(function (eventName) {
39222        this.nativeMediaSource_.addEventListener(eventName, this.trigger.bind(this));
39223      }, _this);
39224
39225      // capture the associated player when the MediaSource is
39226      // successfully attached
39227      _this.on('sourceopen', function (event) {
39228        // Get the player this MediaSource is attached to
39229        var video = document_1.querySelector('[src="' + _this.url_ + '"]');
39230
39231        if (!video) {
39232          return;
39233        }
39234
39235        _this.player_ = videojs$1(video.parentNode);
39236
39237        // hls-reset is fired by videojs.Hls on to the tech after the main SegmentLoader
39238        // resets its state and flushes the buffer
39239        _this.player_.tech_.on('hls-reset', _this.onHlsReset_);
39240        // hls-segment-time-mapping is fired by videojs.Hls on to the tech after the main
39241        // SegmentLoader inspects an MTS segment and has an accurate stream to display
39242        // time mapping
39243        _this.player_.tech_.on('hls-segment-time-mapping', _this.onHlsSegmentTimeMapping_);
39244
39245        if (_this.player_.audioTracks && _this.player_.audioTracks()) {
39246          _this.player_.audioTracks().on('change', _this.updateActiveSourceBuffers_);
39247          _this.player_.audioTracks().on('addtrack', _this.updateActiveSourceBuffers_);
39248          _this.player_.audioTracks().on('removetrack', _this.updateActiveSourceBuffers_);
39249        }
39250
39251        _this.player_.on('mediachange', _this.onPlayerMediachange_);
39252      });
39253
39254      _this.on('sourceended', function (event) {
39255        var duration = durationOfVideo(_this.duration);
39256
39257        for (var i = 0; i < _this.sourceBuffers.length; i++) {
39258          var sourcebuffer = _this.sourceBuffers[i];
39259          var cues = sourcebuffer.metadataTrack_ && sourcebuffer.metadataTrack_.cues;
39260
39261          if (cues && cues.length) {
39262            cues[cues.length - 1].endTime = duration;
39263          }
39264        }
39265      });
39266
39267      // explicitly terminate any WebWorkers that were created
39268      // by SourceHandlers
39269      _this.on('sourceclose', function (event) {
39270        this.sourceBuffers.forEach(function (sourceBuffer) {
39271          if (sourceBuffer.transmuxer_) {
39272            sourceBuffer.transmuxer_.terminate();
39273          }
39274        });
39275
39276        this.sourceBuffers.length = 0;
39277        if (!this.player_) {
39278          return;
39279        }
39280
39281        if (this.player_.audioTracks && this.player_.audioTracks()) {
39282          this.player_.audioTracks().off('change', this.updateActiveSourceBuffers_);
39283          this.player_.audioTracks().off('addtrack', this.updateActiveSourceBuffers_);
39284          this.player_.audioTracks().off('removetrack', this.updateActiveSourceBuffers_);
39285        }
39286
39287        // We can only change this if the player hasn't been disposed of yet
39288        // because `off` eventually tries to use the el_ property. If it has
39289        // been disposed of, then don't worry about it because there are no
39290        // event handlers left to unbind anyway
39291        if (this.player_.el_) {
39292          this.player_.off('mediachange', this.onPlayerMediachange_);
39293          this.player_.tech_.off('hls-reset', this.onHlsReset_);
39294          this.player_.tech_.off('hls-segment-time-mapping', this.onHlsSegmentTimeMapping_);
39295        }
39296      });
39297      return _this;
39298    }
39299
39300    /**
39301     * Add a range that that can now be seeked to.
39302     *
39303     * @param {Double} start where to start the addition
39304     * @param {Double} end where to end the addition
39305     * @private
39306     */
39307
39308    createClass$2(HtmlMediaSource, [{
39309      key: 'addSeekableRange_',
39310      value: function addSeekableRange_(start, end) {
39311        var error = void 0;
39312
39313        if (this.duration !== Infinity) {
39314          error = new Error('MediaSource.addSeekableRange() can only be invoked ' + 'when the duration is Infinity');
39315          error.name = 'InvalidStateError';
39316          error.code = 11;
39317          throw error;
39318        }
39319
39320        if (end > this.nativeMediaSource_.duration || isNaN(this.nativeMediaSource_.duration)) {
39321          this.nativeMediaSource_.duration = end;
39322        }
39323      }
39324
39325      /**
39326       * Add a source buffer to the media source.
39327       *
39328       * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/addSourceBuffer
39329       * @param {String} type the content-type of the content
39330       * @return {Object} the created source buffer
39331       */
39332
39333    }, {
39334      key: 'addSourceBuffer',
39335      value: function addSourceBuffer(type) {
39336        var buffer = void 0;
39337        var parsedType = parseContentType(type);
39338
39339        // Create a VirtualSourceBuffer to transmux MPEG-2 transport
39340        // stream segments into fragmented MP4s
39341        if (/^(video|audio)\/mp2t$/i.test(parsedType.type)) {
39342          var codecs = [];
39343
39344          if (parsedType.parameters && parsedType.parameters.codecs) {
39345            codecs = parsedType.parameters.codecs.split(',');
39346            codecs = translateLegacyCodecs(codecs);
39347            codecs = codecs.filter(function (codec) {
39348              return isAudioCodec(codec) || isVideoCodec(codec);
39349            });
39350          }
39351
39352          if (codecs.length === 0) {
39353            codecs = ['avc1.4d400d', 'mp4a.40.2'];
39354          }
39355
39356          buffer = new VirtualSourceBuffer(this, codecs);
39357
39358          if (this.sourceBuffers.length !== 0) {
39359            // If another VirtualSourceBuffer already exists, then we are creating a
39360            // SourceBuffer for an alternate audio track and therefore we know that
39361            // the source has both an audio and video track.
39362            // That means we should trigger the manual creation of the real
39363            // SourceBuffers instead of waiting for the transmuxer to return data
39364            this.sourceBuffers[0].createRealSourceBuffers_();
39365            buffer.createRealSourceBuffers_();
39366
39367            // Automatically disable the audio on the first source buffer if
39368            // a second source buffer is ever created
39369            this.sourceBuffers[0].audioDisabled_ = true;
39370          }
39371        } else {
39372          // delegate to the native implementation
39373          buffer = this.nativeMediaSource_.addSourceBuffer(type);
39374        }
39375
39376        this.sourceBuffers.push(buffer);
39377        return buffer;
39378      }
39379    }]);
39380    return HtmlMediaSource;
39381  }(videojs$1.EventTarget);
39382
39383  /**
39384   * @file videojs-contrib-media-sources.js
39385   */
39386  var urlCount = 0;
39387
39388  // ------------
39389  // Media Source
39390  // ------------
39391
39392  // store references to the media sources so they can be connected
39393  // to a video element (a swf object)
39394  // TODO: can we store this somewhere local to this module?
39395  videojs$1.mediaSources = {};
39396
39397  /**
39398   * Provide a method for a swf object to notify JS that a
39399   * media source is now open.
39400   *
39401   * @param {String} msObjectURL string referencing the MSE Object URL
39402   * @param {String} swfId the swf id
39403   */
39404  var open = function open(msObjectURL, swfId) {
39405    var mediaSource = videojs$1.mediaSources[msObjectURL];
39406
39407    if (mediaSource) {
39408      mediaSource.trigger({ type: 'sourceopen', swfId: swfId });
39409    } else {
39410      throw new Error('Media Source not found (Video.js)');
39411    }
39412  };
39413
39414  /**
39415   * Check to see if the native MediaSource object exists and supports
39416   * an MP4 container with both H.264 video and AAC-LC audio.
39417   *
39418   * @return {Boolean} if  native media sources are supported
39419   */
39420  var supportsNativeMediaSources = function supportsNativeMediaSources() {
39421    return !!window_1.MediaSource && !!window_1.MediaSource.isTypeSupported && window_1.MediaSource.isTypeSupported('video/mp4;codecs="avc1.4d400d,mp4a.40.2"');
39422  };
39423
39424  /**
39425   * An emulation of the MediaSource API so that we can support
39426   * native and non-native functionality. returns an instance of
39427   * HtmlMediaSource.
39428   *
39429   * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/MediaSource
39430   */
39431  var MediaSource = function MediaSource() {
39432    this.MediaSource = {
39433      open: open,
39434      supportsNativeMediaSources: supportsNativeMediaSources
39435    };
39436
39437    if (supportsNativeMediaSources()) {
39438      return new HtmlMediaSource();
39439    }
39440
39441    throw new Error('Cannot use create a virtual MediaSource for this video');
39442  };
39443
39444  MediaSource.open = open;
39445  MediaSource.supportsNativeMediaSources = supportsNativeMediaSources;
39446
39447  /**
39448   * A wrapper around the native URL for our MSE object
39449   * implementation, this object is exposed under videojs.URL
39450   *
39451   * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/URL
39452   */
39453  var URL$1 = {
39454    /**
39455     * A wrapper around the native createObjectURL for our objects.
39456     * This function maps a native or emulated mediaSource to a blob
39457     * url so that it can be loaded into video.js
39458     *
39459     * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL
39460     * @param {MediaSource} object the object to create a blob url to
39461     */
39462    createObjectURL: function createObjectURL(object) {
39463      var objectUrlPrefix = 'blob:vjs-media-source/';
39464      var url = void 0;
39465
39466      // use the native MediaSource to generate an object URL
39467      if (object instanceof HtmlMediaSource) {
39468        url = window_1.URL.createObjectURL(object.nativeMediaSource_);
39469        object.url_ = url;
39470        return url;
39471      }
39472      // if the object isn't an emulated MediaSource, delegate to the
39473      // native implementation
39474      if (!(object instanceof HtmlMediaSource)) {
39475        url = window_1.URL.createObjectURL(object);
39476        object.url_ = url;
39477        return url;
39478      }
39479
39480      // build a URL that can be used to map back to the emulated
39481      // MediaSource
39482      url = objectUrlPrefix + urlCount;
39483
39484      urlCount++;
39485
39486      // setup the mapping back to object
39487      videojs$1.mediaSources[url] = object;
39488
39489      return url;
39490    }
39491  };
39492
39493  videojs$1.MediaSource = MediaSource;
39494  videojs$1.URL = URL$1;
39495
39496  var EventTarget$1$1 = videojs$1.EventTarget,
39497      mergeOptions$2 = videojs$1.mergeOptions;
39498
39499  /**
39500   * Returns a new master manifest that is the result of merging an updated master manifest
39501   * into the original version.
39502   *
39503   * @param {Object} oldMaster
39504   *        The old parsed mpd object
39505   * @param {Object} newMaster
39506   *        The updated parsed mpd object
39507   * @return {Object}
39508   *         A new object representing the original master manifest with the updated media
39509   *         playlists merged in
39510   */
39511
39512  var updateMaster$1 = function updateMaster$$1(oldMaster, newMaster) {
39513    var update = mergeOptions$2(oldMaster, {
39514      // These are top level properties that can be updated
39515      duration: newMaster.duration,
39516      minimumUpdatePeriod: newMaster.minimumUpdatePeriod
39517    });
39518
39519    // First update the playlists in playlist list
39520    for (var i = 0; i < newMaster.playlists.length; i++) {
39521      var playlistUpdate = updateMaster(update, newMaster.playlists[i]);
39522
39523      if (playlistUpdate) {
39524        update = playlistUpdate;
39525      }
39526    }
39527
39528    // Then update media group playlists
39529    forEachMediaGroup(newMaster, function (properties, type, group, label) {
39530      if (properties.playlists && properties.playlists.length) {
39531        var uri = properties.playlists[0].uri;
39532        var _playlistUpdate = updateMaster(update, properties.playlists[0]);
39533
39534        if (_playlistUpdate) {
39535          update = _playlistUpdate;
39536          // update the playlist reference within media groups
39537          update.mediaGroups[type][group][label].playlists[0] = update.playlists[uri];
39538        }
39539      }
39540    });
39541
39542    return update;
39543  };
39544
39545  var DashPlaylistLoader = function (_EventTarget) {
39546    inherits$3(DashPlaylistLoader, _EventTarget);
39547
39548    // DashPlaylistLoader must accept either a src url or a playlist because subsequent
39549    // playlist loader setups from media groups will expect to be able to pass a playlist
39550    // (since there aren't external URLs to media playlists with DASH)
39551    function DashPlaylistLoader(srcUrlOrPlaylist, hls, withCredentials, masterPlaylistLoader) {
39552      classCallCheck$3(this, DashPlaylistLoader);
39553
39554      var _this = possibleConstructorReturn$3(this, (DashPlaylistLoader.__proto__ || Object.getPrototypeOf(DashPlaylistLoader)).call(this));
39555
39556      _this.hls_ = hls;
39557      _this.withCredentials = withCredentials;
39558
39559      if (!srcUrlOrPlaylist) {
39560        throw new Error('A non-empty playlist URL or playlist is required');
39561      }
39562
39563      // event naming?
39564      _this.on('minimumUpdatePeriod', function () {
39565        _this.refreshXml_();
39566      });
39567
39568      // live playlist staleness timeout
39569      _this.on('mediaupdatetimeout', function () {
39570        _this.refreshMedia_();
39571      });
39572
39573      // initialize the loader state
39574      if (typeof srcUrlOrPlaylist === 'string') {
39575        _this.srcUrl = srcUrlOrPlaylist;
39576        _this.state = 'HAVE_NOTHING';
39577        return possibleConstructorReturn$3(_this);
39578      }
39579
39580      _this.masterPlaylistLoader_ = masterPlaylistLoader;
39581
39582      _this.state = 'HAVE_METADATA';
39583      _this.started = true;
39584      // we only should have one playlist so select it
39585      _this.media(srcUrlOrPlaylist);
39586      // trigger async to mimic behavior of HLS, where it must request a playlist
39587      window_1.setTimeout(function () {
39588        _this.trigger('loadedmetadata');
39589      }, 0);
39590      return _this;
39591    }
39592
39593    createClass$2(DashPlaylistLoader, [{
39594      key: 'dispose',
39595      value: function dispose() {
39596        this.stopRequest();
39597        window_1.clearTimeout(this.mediaUpdateTimeout);
39598      }
39599    }, {
39600      key: 'stopRequest',
39601      value: function stopRequest() {
39602        if (this.request) {
39603          var oldRequest = this.request;
39604
39605          this.request = null;
39606          oldRequest.onreadystatechange = null;
39607          oldRequest.abort();
39608        }
39609      }
39610    }, {
39611      key: 'media',
39612      value: function media(playlist) {
39613        // getter
39614        if (!playlist) {
39615          return this.media_;
39616        }
39617
39618        // setter
39619        if (this.state === 'HAVE_NOTHING') {
39620          throw new Error('Cannot switch media playlist from ' + this.state);
39621        }
39622
39623        var startingState = this.state;
39624
39625        // find the playlist object if the target playlist has been specified by URI
39626        if (typeof playlist === 'string') {
39627          if (!this.master.playlists[playlist]) {
39628            throw new Error('Unknown playlist URI: ' + playlist);
39629          }
39630          playlist = this.master.playlists[playlist];
39631        }
39632
39633        var mediaChange = !this.media_ || playlist.uri !== this.media_.uri;
39634
39635        this.state = 'HAVE_METADATA';
39636
39637        // switching to the active playlist is a no-op
39638        if (!mediaChange) {
39639          return;
39640        }
39641
39642        // switching from an already loaded playlist
39643        if (this.media_) {
39644          this.trigger('mediachanging');
39645        }
39646
39647        this.media_ = playlist;
39648
39649        this.refreshMedia_();
39650
39651        // trigger media change if the active media has been updated
39652        if (startingState !== 'HAVE_MASTER') {
39653          this.trigger('mediachange');
39654        }
39655      }
39656    }, {
39657      key: 'pause',
39658      value: function pause() {
39659        this.stopRequest();
39660        if (this.state === 'HAVE_NOTHING') {
39661          // If we pause the loader before any data has been retrieved, its as if we never
39662          // started, so reset to an unstarted state.
39663          this.started = false;
39664        }
39665      }
39666    }, {
39667      key: 'load',
39668      value: function load() {
39669        // because the playlists are internal to the manifest, load should either load the
39670        // main manifest, or do nothing but trigger an event
39671        if (!this.started) {
39672          this.start();
39673          return;
39674        }
39675
39676        this.trigger('loadedplaylist');
39677      }
39678
39679      /**
39680       * Parses the master xml string and updates playlist uri references
39681       *
39682       * @return {Object}
39683       *         The parsed mpd manifest object
39684       */
39685
39686    }, {
39687      key: 'parseMasterXml',
39688      value: function parseMasterXml() {
39689        var master = parse(this.masterXml_, {
39690          manifestUri: this.srcUrl,
39691          clientOffset: this.clientOffset_
39692        });
39693
39694        master.uri = this.srcUrl;
39695
39696        // Set up phony URIs for the playlists since we won't have external URIs for DASH
39697        // but reference playlists by their URI throughout the project
39698        // TODO: Should we create the dummy uris in mpd-parser as well (leaning towards yes).
39699        for (var i = 0; i < master.playlists.length; i++) {
39700          var phonyUri = 'placeholder-uri-' + i;
39701
39702          master.playlists[i].uri = phonyUri;
39703          // set up by URI references
39704          master.playlists[phonyUri] = master.playlists[i];
39705        }
39706
39707        // set up phony URIs for the media group playlists since we won't have external
39708        // URIs for DASH but reference playlists by their URI throughout the project
39709        forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) {
39710          if (properties.playlists && properties.playlists.length) {
39711            var _phonyUri = 'placeholder-uri-' + mediaType + '-' + groupKey + '-' + labelKey;
39712
39713            properties.playlists[0].uri = _phonyUri;
39714            // setup URI references
39715            master.playlists[_phonyUri] = properties.playlists[0];
39716          }
39717        });
39718
39719        setupMediaPlaylists(master);
39720        resolveMediaGroupUris(master);
39721
39722        return master;
39723      }
39724    }, {
39725      key: 'start',
39726      value: function start() {
39727        var _this2 = this;
39728
39729        this.started = true;
39730
39731        // request the specified URL
39732        this.request = this.hls_.xhr({
39733          uri: this.srcUrl,
39734          withCredentials: this.withCredentials
39735        }, function (error, req) {
39736          // disposed
39737          if (!_this2.request) {
39738            return;
39739          }
39740
39741          // clear the loader's request reference
39742          _this2.request = null;
39743
39744          if (error) {
39745            _this2.error = {
39746              status: req.status,
39747              message: 'DASH playlist request error at URL: ' + _this2.srcUrl,
39748              responseText: req.responseText,
39749              // MEDIA_ERR_NETWORK
39750              code: 2
39751            };
39752            if (_this2.state === 'HAVE_NOTHING') {
39753              _this2.started = false;
39754            }
39755            return _this2.trigger('error');
39756          }
39757
39758          _this2.masterXml_ = req.responseText;
39759
39760          if (req.responseHeaders && req.responseHeaders.date) {
39761            _this2.masterLoaded_ = Date.parse(req.responseHeaders.date);
39762          } else {
39763            _this2.masterLoaded_ = Date.now();
39764          }
39765
39766          _this2.syncClientServerClock_(_this2.onClientServerClockSync_.bind(_this2));
39767        });
39768      }
39769
39770      /**
39771       * Parses the master xml for UTCTiming node to sync the client clock to the server
39772       * clock. If the UTCTiming node requires a HEAD or GET request, that request is made.
39773       *
39774       * @param {Function} done
39775       *        Function to call when clock sync has completed
39776       */
39777
39778    }, {
39779      key: 'syncClientServerClock_',
39780      value: function syncClientServerClock_(done) {
39781        var _this3 = this;
39782
39783        var utcTiming = parseUTCTiming(this.masterXml_);
39784
39785        // No UTCTiming element found in the mpd. Use Date header from mpd request as the
39786        // server clock
39787        if (utcTiming === null) {
39788          this.clientOffset_ = this.masterLoaded_ - Date.now();
39789          return done();
39790        }
39791
39792        if (utcTiming.method === 'DIRECT') {
39793          this.clientOffset_ = utcTiming.value - Date.now();
39794          return done();
39795        }
39796
39797        this.request = this.hls_.xhr({
39798          uri: resolveUrl$1(this.srcUrl, utcTiming.value),
39799          method: utcTiming.method,
39800          withCredentials: this.withCredentials
39801        }, function (error, req) {
39802          // disposed
39803          if (!_this3.request) {
39804            return;
39805          }
39806
39807          if (error) {
39808            // sync request failed, fall back to using date header from mpd
39809            // TODO: log warning
39810            _this3.clientOffset_ = _this3.masterLoaded_ - Date.now();
39811            return done();
39812          }
39813
39814          var serverTime = void 0;
39815
39816          if (utcTiming.method === 'HEAD') {
39817            if (!req.responseHeaders || !req.responseHeaders.date) {
39818              // expected date header not preset, fall back to using date header from mpd
39819              // TODO: log warning
39820              serverTime = _this3.masterLoaded_;
39821            } else {
39822              serverTime = Date.parse(req.responseHeaders.date);
39823            }
39824          } else {
39825            serverTime = Date.parse(req.responseText);
39826          }
39827
39828          _this3.clientOffset_ = serverTime - Date.now();
39829
39830          done();
39831        });
39832      }
39833
39834      /**
39835       * Handler for after client/server clock synchronization has happened. Sets up
39836       * xml refresh timer if specificed by the manifest.
39837       */
39838
39839    }, {
39840      key: 'onClientServerClockSync_',
39841      value: function onClientServerClockSync_() {
39842        var _this4 = this;
39843
39844        this.master = this.parseMasterXml();
39845
39846        this.state = 'HAVE_MASTER';
39847
39848        this.trigger('loadedplaylist');
39849
39850        if (!this.media_) {
39851          // no media playlist was specifically selected so start
39852          // from the first listed one
39853          this.media(this.master.playlists[0]);
39854        }
39855        // trigger loadedmetadata to resolve setup of media groups
39856        // trigger async to mimic behavior of HLS, where it must request a playlist
39857        window_1.setTimeout(function () {
39858          _this4.trigger('loadedmetadata');
39859        }, 0);
39860
39861        // TODO: minimumUpdatePeriod can have a value of 0. Currently the manifest will not
39862        // be refreshed when this is the case. The inter-op guide says that when the
39863        // minimumUpdatePeriod is 0, the manifest should outline all currently available
39864        // segments, but future segments may require an update. I think a good solution
39865        // would be to update the manifest at the same rate that the media playlists
39866        // are "refreshed", i.e. every targetDuration.
39867        if (this.master.minimumUpdatePeriod) {
39868          window_1.setTimeout(function () {
39869            _this4.trigger('minimumUpdatePeriod');
39870          }, this.master.minimumUpdatePeriod);
39871        }
39872      }
39873
39874      /**
39875       * Sends request to refresh the master xml and updates the parsed master manifest
39876       * TODO: Does the client offset need to be recalculated when the xml is refreshed?
39877       */
39878
39879    }, {
39880      key: 'refreshXml_',
39881      value: function refreshXml_() {
39882        var _this5 = this;
39883
39884        this.request = this.hls_.xhr({
39885          uri: this.srcUrl,
39886          withCredentials: this.withCredentials
39887        }, function (error, req) {
39888          // disposed
39889          if (!_this5.request) {
39890            return;
39891          }
39892
39893          // clear the loader's request reference
39894          _this5.request = null;
39895
39896          if (error) {
39897            _this5.error = {
39898              status: req.status,
39899              message: 'DASH playlist request error at URL: ' + _this5.srcUrl,
39900              responseText: req.responseText,
39901              // MEDIA_ERR_NETWORK
39902              code: 2
39903            };
39904            if (_this5.state === 'HAVE_NOTHING') {
39905              _this5.started = false;
39906            }
39907            return _this5.trigger('error');
39908          }
39909
39910          _this5.masterXml_ = req.responseText;
39911
39912          var newMaster = _this5.parseMasterXml();
39913
39914          _this5.master = updateMaster$1(_this5.master, newMaster);
39915
39916          window_1.setTimeout(function () {
39917            _this5.trigger('minimumUpdatePeriod');
39918          }, _this5.master.minimumUpdatePeriod);
39919        });
39920      }
39921
39922      /**
39923       * Refreshes the media playlist by re-parsing the master xml and updating playlist
39924       * references. If this is an alternate loader, the updated parsed manifest is retrieved
39925       * from the master loader.
39926       */
39927
39928    }, {
39929      key: 'refreshMedia_',
39930      value: function refreshMedia_() {
39931        var _this6 = this;
39932
39933        var oldMaster = void 0;
39934        var newMaster = void 0;
39935
39936        if (this.masterPlaylistLoader_) {
39937          oldMaster = this.masterPlaylistLoader_.master;
39938          newMaster = this.masterPlaylistLoader_.parseMasterXml();
39939        } else {
39940          oldMaster = this.master;
39941          newMaster = this.parseMasterXml();
39942        }
39943
39944        var updatedMaster = updateMaster$1(oldMaster, newMaster);
39945
39946        if (updatedMaster) {
39947          if (this.masterPlaylistLoader_) {
39948            this.masterPlaylistLoader_.master = updatedMaster;
39949          } else {
39950            this.master = updatedMaster;
39951          }
39952          this.media_ = updatedMaster.playlists[this.media_.uri];
39953        } else {
39954          this.trigger('playlistunchanged');
39955        }
39956
39957        if (!this.media().endList) {
39958          this.mediaUpdateTimeout = window_1.setTimeout(function () {
39959            _this6.trigger('mediaupdatetimeout');
39960          }, refreshDelay(this.media(), !!updatedMaster));
39961        }
39962
39963        this.trigger('loadedplaylist');
39964      }
39965    }]);
39966    return DashPlaylistLoader;
39967  }(EventTarget$1$1);
39968
39969  var logger = function logger(source) {
39970    if (videojs$1.log.debug) {
39971      return videojs$1.log.debug.bind(videojs$1, 'VHS:', source + ' >');
39972    }
39973
39974    return function () {};
39975  };
39976
39977  function noop$1() {}
39978
39979  /**
39980   * @file source-updater.js
39981   */
39982
39983  /**
39984   * A queue of callbacks to be serialized and applied when a
39985   * MediaSource and its associated SourceBuffers are not in the
39986   * updating state. It is used by the segment loader to update the
39987   * underlying SourceBuffers when new data is loaded, for instance.
39988   *
39989   * @class SourceUpdater
39990   * @param {MediaSource} mediaSource the MediaSource to create the
39991   * SourceBuffer from
39992   * @param {String} mimeType the desired MIME type of the underlying
39993   * SourceBuffer
39994   * @param {Object} sourceBufferEmitter an event emitter that fires when a source buffer is
39995   * added to the media source
39996   */
39997
39998  var SourceUpdater = function () {
39999    function SourceUpdater(mediaSource, mimeType, type, sourceBufferEmitter) {
40000      classCallCheck$3(this, SourceUpdater);
40001
40002      this.callbacks_ = [];
40003      this.pendingCallback_ = null;
40004      this.timestampOffset_ = 0;
40005      this.mediaSource = mediaSource;
40006      this.processedAppend_ = false;
40007      this.type_ = type;
40008      this.mimeType_ = mimeType;
40009      this.logger_ = logger('SourceUpdater[' + type + '][' + mimeType + ']');
40010
40011      if (mediaSource.readyState === 'closed') {
40012        mediaSource.addEventListener('sourceopen', this.createSourceBuffer_.bind(this, mimeType, sourceBufferEmitter));
40013      } else {
40014        this.createSourceBuffer_(mimeType, sourceBufferEmitter);
40015      }
40016    }
40017
40018    createClass$2(SourceUpdater, [{
40019      key: 'createSourceBuffer_',
40020      value: function createSourceBuffer_(mimeType, sourceBufferEmitter) {
40021        var _this = this;
40022
40023        this.sourceBuffer_ = this.mediaSource.addSourceBuffer(mimeType);
40024
40025        this.logger_('created SourceBuffer');
40026
40027        if (sourceBufferEmitter) {
40028          sourceBufferEmitter.trigger('sourcebufferadded');
40029
40030          if (this.mediaSource.sourceBuffers.length < 2) {
40031            // There's another source buffer we must wait for before we can start updating
40032            // our own (or else we can get into a bad state, i.e., appending video/audio data
40033            // before the other video/audio source buffer is available and leading to a video
40034            // or audio only buffer).
40035            sourceBufferEmitter.on('sourcebufferadded', function () {
40036              _this.start_();
40037            });
40038            return;
40039          }
40040        }
40041
40042        this.start_();
40043      }
40044    }, {
40045      key: 'start_',
40046      value: function start_() {
40047        var _this2 = this;
40048
40049        this.started_ = true;
40050
40051        // run completion handlers and process callbacks as updateend
40052        // events fire
40053        this.onUpdateendCallback_ = function () {
40054          var pendingCallback = _this2.pendingCallback_;
40055
40056          _this2.pendingCallback_ = null;
40057
40058          _this2.logger_('buffered [' + printableRange(_this2.buffered()) + ']');
40059
40060          if (pendingCallback) {
40061            pendingCallback();
40062          }
40063
40064          _this2.runCallback_();
40065        };
40066
40067        this.sourceBuffer_.addEventListener('updateend', this.onUpdateendCallback_);
40068
40069        this.runCallback_();
40070      }
40071
40072      /**
40073       * Aborts the current segment and resets the segment parser.
40074       *
40075       * @param {Function} done function to call when done
40076       * @see http://w3c.github.io/media-source/#widl-SourceBuffer-abort-void
40077       */
40078
40079    }, {
40080      key: 'abort',
40081      value: function abort(done) {
40082        var _this3 = this;
40083
40084        if (this.processedAppend_) {
40085          this.queueCallback_(function () {
40086            _this3.sourceBuffer_.abort();
40087          }, done);
40088        }
40089      }
40090
40091      /**
40092       * Queue an update to append an ArrayBuffer.
40093       *
40094       * @param {ArrayBuffer} bytes
40095       * @param {Function} done the function to call when done
40096       * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data
40097       */
40098
40099    }, {
40100      key: 'appendBuffer',
40101      value: function appendBuffer(bytes, done) {
40102        var _this4 = this;
40103
40104        this.processedAppend_ = true;
40105        this.queueCallback_(function () {
40106          _this4.sourceBuffer_.appendBuffer(bytes);
40107        }, done);
40108      }
40109
40110      /**
40111       * Indicates what TimeRanges are buffered in the managed SourceBuffer.
40112       *
40113       * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-buffered
40114       */
40115
40116    }, {
40117      key: 'buffered',
40118      value: function buffered() {
40119        if (!this.sourceBuffer_) {
40120          return videojs$1.createTimeRanges();
40121        }
40122        return this.sourceBuffer_.buffered;
40123      }
40124
40125      /**
40126       * Queue an update to remove a time range from the buffer.
40127       *
40128       * @param {Number} start where to start the removal
40129       * @param {Number} end where to end the removal
40130       * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end
40131       */
40132
40133    }, {
40134      key: 'remove',
40135      value: function remove(start, end) {
40136        var _this5 = this;
40137
40138        if (this.processedAppend_) {
40139          this.queueCallback_(function () {
40140            _this5.logger_('remove [' + start + ' => ' + end + ']');
40141            _this5.sourceBuffer_.remove(start, end);
40142          }, noop$1);
40143        }
40144      }
40145
40146      /**
40147       * Whether the underlying sourceBuffer is updating or not
40148       *
40149       * @return {Boolean} the updating status of the SourceBuffer
40150       */
40151
40152    }, {
40153      key: 'updating',
40154      value: function updating() {
40155        return !this.sourceBuffer_ || this.sourceBuffer_.updating || this.pendingCallback_;
40156      }
40157
40158      /**
40159       * Set/get the timestampoffset on the SourceBuffer
40160       *
40161       * @return {Number} the timestamp offset
40162       */
40163
40164    }, {
40165      key: 'timestampOffset',
40166      value: function timestampOffset(offset) {
40167        var _this6 = this;
40168
40169        if (typeof offset !== 'undefined') {
40170          this.queueCallback_(function () {
40171            _this6.sourceBuffer_.timestampOffset = offset;
40172          });
40173          this.timestampOffset_ = offset;
40174        }
40175        return this.timestampOffset_;
40176      }
40177
40178      /**
40179       * Queue a callback to run
40180       */
40181
40182    }, {
40183      key: 'queueCallback_',
40184      value: function queueCallback_(callback, done) {
40185        this.callbacks_.push([callback.bind(this), done]);
40186        this.runCallback_();
40187      }
40188
40189      /**
40190       * Run a queued callback
40191       */
40192
40193    }, {
40194      key: 'runCallback_',
40195      value: function runCallback_() {
40196        var callbacks = void 0;
40197
40198        if (!this.updating() && this.callbacks_.length && this.started_) {
40199          callbacks = this.callbacks_.shift();
40200          this.pendingCallback_ = callbacks[1];
40201          callbacks[0]();
40202        }
40203      }
40204
40205      /**
40206       * dispose of the source updater and the underlying sourceBuffer
40207       */
40208
40209    }, {
40210      key: 'dispose',
40211      value: function dispose() {
40212        this.sourceBuffer_.removeEventListener('updateend', this.onUpdateendCallback_);
40213        if (this.sourceBuffer_ && this.mediaSource.readyState === 'open') {
40214          this.sourceBuffer_.abort();
40215        }
40216      }
40217    }]);
40218    return SourceUpdater;
40219  }();
40220
40221  var Config = {
40222    GOAL_BUFFER_LENGTH: 30,
40223    MAX_GOAL_BUFFER_LENGTH: 60,
40224    GOAL_BUFFER_LENGTH_RATE: 1,
40225    // A fudge factor to apply to advertised playlist bitrates to account for
40226    // temporary flucations in client bandwidth
40227    BANDWIDTH_VARIANCE: 1.2,
40228    // How much of the buffer must be filled before we consider upswitching
40229    BUFFER_LOW_WATER_LINE: 0,
40230    MAX_BUFFER_LOW_WATER_LINE: 30,
40231    BUFFER_LOW_WATER_LINE_RATE: 1
40232  };
40233
40234  var REQUEST_ERRORS = {
40235    FAILURE: 2,
40236    TIMEOUT: -101,
40237    ABORTED: -102
40238  };
40239
40240  /**
40241   * Turns segment byterange into a string suitable for use in
40242   * HTTP Range requests
40243   *
40244   * @param {Object} byterange - an object with two values defining the start and end
40245   *                             of a byte-range
40246   */
40247  var byterangeStr = function byterangeStr(byterange) {
40248    var byterangeStart = void 0;
40249    var byterangeEnd = void 0;
40250
40251    // `byterangeEnd` is one less than `offset + length` because the HTTP range
40252    // header uses inclusive ranges
40253    byterangeEnd = byterange.offset + byterange.length - 1;
40254    byterangeStart = byterange.offset;
40255    return 'bytes=' + byterangeStart + '-' + byterangeEnd;
40256  };
40257
40258  /**
40259   * Defines headers for use in the xhr request for a particular segment.
40260   *
40261   * @param {Object} segment - a simplified copy of the segmentInfo object
40262   *                           from SegmentLoader
40263   */
40264  var segmentXhrHeaders = function segmentXhrHeaders(segment) {
40265    var headers = {};
40266
40267    if (segment.byterange) {
40268      headers.Range = byterangeStr(segment.byterange);
40269    }
40270    return headers;
40271  };
40272
40273  /**
40274   * Abort all requests
40275   *
40276   * @param {Object} activeXhrs - an object that tracks all XHR requests
40277   */
40278  var abortAll = function abortAll(activeXhrs) {
40279    activeXhrs.forEach(function (xhr) {
40280      xhr.abort();
40281    });
40282  };
40283
40284  /**
40285   * Gather important bandwidth stats once a request has completed
40286   *
40287   * @param {Object} request - the XHR request from which to gather stats
40288   */
40289  var getRequestStats = function getRequestStats(request) {
40290    return {
40291      bandwidth: request.bandwidth,
40292      bytesReceived: request.bytesReceived || 0,
40293      roundTripTime: request.roundTripTime || 0
40294    };
40295  };
40296
40297  /**
40298   * If possible gather bandwidth stats as a request is in
40299   * progress
40300   *
40301   * @param {Event} progressEvent - an event object from an XHR's progress event
40302   */
40303  var getProgressStats = function getProgressStats(progressEvent) {
40304    var request = progressEvent.target;
40305    var roundTripTime = Date.now() - request.requestTime;
40306    var stats = {
40307      bandwidth: Infinity,
40308      bytesReceived: 0,
40309      roundTripTime: roundTripTime || 0
40310    };
40311
40312    stats.bytesReceived = progressEvent.loaded;
40313    // This can result in Infinity if stats.roundTripTime is 0 but that is ok
40314    // because we should only use bandwidth stats on progress to determine when
40315    // abort a request early due to insufficient bandwidth
40316    stats.bandwidth = Math.floor(stats.bytesReceived / stats.roundTripTime * 8 * 1000);
40317
40318    return stats;
40319  };
40320
40321  /**
40322   * Handle all error conditions in one place and return an object
40323   * with all the information
40324   *
40325   * @param {Error|null} error - if non-null signals an error occured with the XHR
40326   * @param {Object} request -  the XHR request that possibly generated the error
40327   */
40328  var handleErrors = function handleErrors(error, request) {
40329    if (request.timedout) {
40330      return {
40331        status: request.status,
40332        message: 'HLS request timed-out at URL: ' + request.uri,
40333        code: REQUEST_ERRORS.TIMEOUT,
40334        xhr: request
40335      };
40336    }
40337
40338    if (request.aborted) {
40339      return {
40340        status: request.status,
40341        message: 'HLS request aborted at URL: ' + request.uri,
40342        code: REQUEST_ERRORS.ABORTED,
40343        xhr: request
40344      };
40345    }
40346
40347    if (error) {
40348      return {
40349        status: request.status,
40350        message: 'HLS request errored at URL: ' + request.uri,
40351        code: REQUEST_ERRORS.FAILURE,
40352        xhr: request
40353      };
40354    }
40355
40356    return null;
40357  };
40358
40359  /**
40360   * Handle responses for key data and convert the key data to the correct format
40361   * for the decryption step later
40362   *
40363   * @param {Object} segment - a simplified copy of the segmentInfo object
40364   *                           from SegmentLoader
40365   * @param {Function} finishProcessingFn - a callback to execute to continue processing
40366   *                                        this request
40367   */
40368  var handleKeyResponse = function handleKeyResponse(segment, finishProcessingFn) {
40369    return function (error, request) {
40370      var response = request.response;
40371      var errorObj = handleErrors(error, request);
40372
40373      if (errorObj) {
40374        return finishProcessingFn(errorObj, segment);
40375      }
40376
40377      if (response.byteLength !== 16) {
40378        return finishProcessingFn({
40379          status: request.status,
40380          message: 'Invalid HLS key at URL: ' + request.uri,
40381          code: REQUEST_ERRORS.FAILURE,
40382          xhr: request
40383        }, segment);
40384      }
40385
40386      var view = new DataView(response);
40387
40388      segment.key.bytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]);
40389      return finishProcessingFn(null, segment);
40390    };
40391  };
40392
40393  /**
40394   * Handle init-segment responses
40395   *
40396   * @param {Object} segment - a simplified copy of the segmentInfo object
40397   *                           from SegmentLoader
40398   * @param {Function} finishProcessingFn - a callback to execute to continue processing
40399   *                                        this request
40400   */
40401  var handleInitSegmentResponse = function handleInitSegmentResponse(segment, finishProcessingFn) {
40402    return function (error, request) {
40403      var response = request.response;
40404      var errorObj = handleErrors(error, request);
40405
40406      if (errorObj) {
40407        return finishProcessingFn(errorObj, segment);
40408      }
40409
40410      // stop processing if received empty content
40411      if (response.byteLength === 0) {
40412        return finishProcessingFn({
40413          status: request.status,
40414          message: 'Empty HLS segment content at URL: ' + request.uri,
40415          code: REQUEST_ERRORS.FAILURE,
40416          xhr: request
40417        }, segment);
40418      }
40419
40420      segment.map.bytes = new Uint8Array(request.response);
40421      return finishProcessingFn(null, segment);
40422    };
40423  };
40424
40425  /**
40426   * Response handler for segment-requests being sure to set the correct
40427   * property depending on whether the segment is encryped or not
40428   * Also records and keeps track of stats that are used for ABR purposes
40429   *
40430   * @param {Object} segment - a simplified copy of the segmentInfo object
40431   *                           from SegmentLoader
40432   * @param {Function} finishProcessingFn - a callback to execute to continue processing
40433   *                                        this request
40434   */
40435  var handleSegmentResponse = function handleSegmentResponse(segment, finishProcessingFn) {
40436    return function (error, request) {
40437      var response = request.response;
40438      var errorObj = handleErrors(error, request);
40439
40440      if (errorObj) {
40441        return finishProcessingFn(errorObj, segment);
40442      }
40443
40444      // stop processing if received empty content
40445      if (response.byteLength === 0) {
40446        return finishProcessingFn({
40447          status: request.status,
40448          message: 'Empty HLS segment content at URL: ' + request.uri,
40449          code: REQUEST_ERRORS.FAILURE,
40450          xhr: request
40451        }, segment);
40452      }
40453
40454      segment.stats = getRequestStats(request);
40455
40456      if (segment.key) {
40457        segment.encryptedBytes = new Uint8Array(request.response);
40458      } else {
40459        segment.bytes = new Uint8Array(request.response);
40460      }
40461
40462      return finishProcessingFn(null, segment);
40463    };
40464  };
40465
40466  /**
40467   * Decrypt the segment via the decryption web worker
40468   *
40469   * @param {WebWorker} decrypter - a WebWorker interface to AES-128 decryption routines
40470   * @param {Object} segment - a simplified copy of the segmentInfo object
40471   *                           from SegmentLoader
40472   * @param {Function} doneFn - a callback that is executed after decryption has completed
40473   */
40474  var decryptSegment = function decryptSegment(decrypter, segment, doneFn) {
40475    var decryptionHandler = function decryptionHandler(event) {
40476      if (event.data.source === segment.requestId) {
40477        decrypter.removeEventListener('message', decryptionHandler);
40478        var decrypted = event.data.decrypted;
40479
40480        segment.bytes = new Uint8Array(decrypted.bytes, decrypted.byteOffset, decrypted.byteLength);
40481        return doneFn(null, segment);
40482      }
40483    };
40484
40485    decrypter.addEventListener('message', decryptionHandler);
40486
40487    // this is an encrypted segment
40488    // incrementally decrypt the segment
40489    decrypter.postMessage(createTransferableMessage({
40490      source: segment.requestId,
40491      encrypted: segment.encryptedBytes,
40492      key: segment.key.bytes,
40493      iv: segment.key.iv
40494    }), [segment.encryptedBytes.buffer, segment.key.bytes.buffer]);
40495  };
40496
40497  /**
40498   * The purpose of this function is to get the most pertinent error from the
40499   * array of errors.
40500   * For instance if a timeout and two aborts occur, then the aborts were
40501   * likely triggered by the timeout so return that error object.
40502   */
40503  var getMostImportantError = function getMostImportantError(errors) {
40504    return errors.reduce(function (prev, err) {
40505      return err.code > prev.code ? err : prev;
40506    });
40507  };
40508
40509  /**
40510   * This function waits for all XHRs to finish (with either success or failure)
40511   * before continueing processing via it's callback. The function gathers errors
40512   * from each request into a single errors array so that the error status for
40513   * each request can be examined later.
40514   *
40515   * @param {Object} activeXhrs - an object that tracks all XHR requests
40516   * @param {WebWorker} decrypter - a WebWorker interface to AES-128 decryption routines
40517   * @param {Function} doneFn - a callback that is executed after all resources have been
40518   *                            downloaded and any decryption completed
40519   */
40520  var waitForCompletion = function waitForCompletion(activeXhrs, decrypter, doneFn) {
40521    var errors = [];
40522    var count = 0;
40523
40524    return function (error, segment) {
40525      if (error) {
40526        // If there are errors, we have to abort any outstanding requests
40527        abortAll(activeXhrs);
40528        errors.push(error);
40529      }
40530      count += 1;
40531
40532      if (count === activeXhrs.length) {
40533        // Keep track of when *all* of the requests have completed
40534        segment.endOfAllRequests = Date.now();
40535
40536        if (errors.length > 0) {
40537          var worstError = getMostImportantError(errors);
40538
40539          return doneFn(worstError, segment);
40540        }
40541        if (segment.encryptedBytes) {
40542          return decryptSegment(decrypter, segment, doneFn);
40543        }
40544        // Otherwise, everything is ready just continue
40545        return doneFn(null, segment);
40546      }
40547    };
40548  };
40549
40550  /**
40551   * Simple progress event callback handler that gathers some stats before
40552   * executing a provided callback with the `segment` object
40553   *
40554   * @param {Object} segment - a simplified copy of the segmentInfo object
40555   *                           from SegmentLoader
40556   * @param {Function} progressFn - a callback that is executed each time a progress event
40557   *                                is received
40558   * @param {Event} event - the progress event object from XMLHttpRequest
40559   */
40560  var handleProgress = function handleProgress(segment, progressFn) {
40561    return function (event) {
40562      segment.stats = videojs$1.mergeOptions(segment.stats, getProgressStats(event));
40563
40564      // record the time that we receive the first byte of data
40565      if (!segment.stats.firstBytesReceivedAt && segment.stats.bytesReceived) {
40566        segment.stats.firstBytesReceivedAt = Date.now();
40567      }
40568
40569      return progressFn(event, segment);
40570    };
40571  };
40572
40573  /**
40574   * Load all resources and does any processing necessary for a media-segment
40575   *
40576   * Features:
40577   *   decrypts the media-segment if it has a key uri and an iv
40578   *   aborts *all* requests if *any* one request fails
40579   *
40580   * The segment object, at minimum, has the following format:
40581   * {
40582   *   resolvedUri: String,
40583   *   [byterange]: {
40584   *     offset: Number,
40585   *     length: Number
40586   *   },
40587   *   [key]: {
40588   *     resolvedUri: String
40589   *     [byterange]: {
40590   *       offset: Number,
40591   *       length: Number
40592   *     },
40593   *     iv: {
40594   *       bytes: Uint32Array
40595   *     }
40596   *   },
40597   *   [map]: {
40598   *     resolvedUri: String,
40599   *     [byterange]: {
40600   *       offset: Number,
40601   *       length: Number
40602   *     },
40603   *     [bytes]: Uint8Array
40604   *   }
40605   * }
40606   * ...where [name] denotes optional properties
40607   *
40608   * @param {Function} xhr - an instance of the xhr wrapper in xhr.js
40609   * @param {Object} xhrOptions - the base options to provide to all xhr requests
40610   * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128
40611   *                                       decryption routines
40612   * @param {Object} segment - a simplified copy of the segmentInfo object
40613   *                           from SegmentLoader
40614   * @param {Function} progressFn - a callback that receives progress events from the main
40615   *                                segment's xhr request
40616   * @param {Function} doneFn - a callback that is executed only once all requests have
40617   *                            succeeded or failed
40618   * @returns {Function} a function that, when invoked, immediately aborts all
40619   *                     outstanding requests
40620   */
40621  var mediaSegmentRequest = function mediaSegmentRequest(xhr, xhrOptions, decryptionWorker, segment, progressFn, doneFn) {
40622    var activeXhrs = [];
40623    var finishProcessingFn = waitForCompletion(activeXhrs, decryptionWorker, doneFn);
40624
40625    // optionally, request the decryption key
40626    if (segment.key) {
40627      var keyRequestOptions = videojs$1.mergeOptions(xhrOptions, {
40628        uri: segment.key.resolvedUri,
40629        responseType: 'arraybuffer'
40630      });
40631      var keyRequestCallback = handleKeyResponse(segment, finishProcessingFn);
40632      var keyXhr = xhr(keyRequestOptions, keyRequestCallback);
40633
40634      activeXhrs.push(keyXhr);
40635    }
40636
40637    // optionally, request the associated media init segment
40638    if (segment.map && !segment.map.bytes) {
40639      var initSegmentOptions = videojs$1.mergeOptions(xhrOptions, {
40640        uri: segment.map.resolvedUri,
40641        responseType: 'arraybuffer',
40642        headers: segmentXhrHeaders(segment.map)
40643      });
40644      var initSegmentRequestCallback = handleInitSegmentResponse(segment, finishProcessingFn);
40645      var initSegmentXhr = xhr(initSegmentOptions, initSegmentRequestCallback);
40646
40647      activeXhrs.push(initSegmentXhr);
40648    }
40649
40650    var segmentRequestOptions = videojs$1.mergeOptions(xhrOptions, {
40651      uri: segment.resolvedUri,
40652      responseType: 'arraybuffer',
40653      headers: segmentXhrHeaders(segment)
40654    });
40655    var segmentRequestCallback = handleSegmentResponse(segment, finishProcessingFn);
40656    var segmentXhr = xhr(segmentRequestOptions, segmentRequestCallback);
40657
40658    segmentXhr.addEventListener('progress', handleProgress(segment, progressFn));
40659    activeXhrs.push(segmentXhr);
40660
40661    return function () {
40662      return abortAll(activeXhrs);
40663    };
40664  };
40665
40666  /**
40667   * @file - codecs.js - Handles tasks regarding codec strings such as translating them to
40668   * codec strings, or translating codec strings into objects that can be examined.
40669   */
40670
40671  // Default codec parameters if none were provided for video and/or audio
40672  var defaultCodecs = {
40673    videoCodec: 'avc1',
40674    videoObjectTypeIndicator: '.4d400d',
40675    // AAC-LC
40676    audioProfile: '2'
40677  };
40678
40679  /**
40680   * Parses a codec string to retrieve the number of codecs specified,
40681   * the video codec and object type indicator, and the audio profile.
40682   */
40683
40684  var parseCodecs = function parseCodecs() {
40685    var codecs = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
40686
40687    var result = {
40688      codecCount: 0
40689    };
40690    var parsed = void 0;
40691
40692    result.codecCount = codecs.split(',').length;
40693    result.codecCount = result.codecCount || 2;
40694
40695    // parse the video codec
40696    parsed = /(^|\s|,)+(avc[13])([^ ,]*)/i.exec(codecs);
40697    if (parsed) {
40698      result.videoCodec = parsed[2];
40699      result.videoObjectTypeIndicator = parsed[3];
40700    }
40701
40702    // parse the last field of the audio codec
40703    result.audioProfile = /(^|\s|,)+mp4a.[0-9A-Fa-f]+\.([0-9A-Fa-f]+)/i.exec(codecs);
40704    result.audioProfile = result.audioProfile && result.audioProfile[2];
40705
40706    return result;
40707  };
40708
40709  /**
40710   * Replace codecs in the codec string with the old apple-style `avc1.<dd>.<dd>` to the
40711   * standard `avc1.<hhhhhh>`.
40712   *
40713   * @param codecString {String} the codec string
40714   * @return {String} the codec string with old apple-style codecs replaced
40715   *
40716   * @private
40717   */
40718  var mapLegacyAvcCodecs = function mapLegacyAvcCodecs(codecString) {
40719    return codecString.replace(/avc1\.(\d+)\.(\d+)/i, function (match) {
40720      return translateLegacyCodecs([match])[0];
40721    });
40722  };
40723
40724  /**
40725   * Build a media mime-type string from a set of parameters
40726   * @param {String} type either 'audio' or 'video'
40727   * @param {String} container either 'mp2t' or 'mp4'
40728   * @param {Array} codecs an array of codec strings to add
40729   * @return {String} a valid media mime-type
40730   */
40731  var makeMimeTypeString = function makeMimeTypeString(type, container, codecs) {
40732    // The codecs array is filtered so that falsey values are
40733    // dropped and don't cause Array#join to create spurious
40734    // commas
40735    return type + '/' + container + '; codecs="' + codecs.filter(function (c) {
40736      return !!c;
40737    }).join(', ') + '"';
40738  };
40739
40740  /**
40741   * Returns the type container based on information in the playlist
40742   * @param {Playlist} media the current media playlist
40743   * @return {String} a valid media container type
40744   */
40745  var getContainerType = function getContainerType(media) {
40746    // An initialization segment means the media playlist is an iframe
40747    // playlist or is using the mp4 container. We don't currently
40748    // support iframe playlists, so assume this is signalling mp4
40749    // fragments.
40750    if (media.segments && media.segments.length && media.segments[0].map) {
40751      return 'mp4';
40752    }
40753    return 'mp2t';
40754  };
40755
40756  /**
40757   * Returns a set of codec strings parsed from the playlist or the default
40758   * codec strings if no codecs were specified in the playlist
40759   * @param {Playlist} media the current media playlist
40760   * @return {Object} an object with the video and audio codecs
40761   */
40762  var getCodecs = function getCodecs(media) {
40763    // if the codecs were explicitly specified, use them instead of the
40764    // defaults
40765    var mediaAttributes = media.attributes || {};
40766
40767    if (mediaAttributes.CODECS) {
40768      return parseCodecs(mediaAttributes.CODECS);
40769    }
40770    return defaultCodecs;
40771  };
40772
40773  var audioProfileFromDefault = function audioProfileFromDefault(master, audioGroupId) {
40774    if (!master.mediaGroups.AUDIO || !audioGroupId) {
40775      return null;
40776    }
40777
40778    var audioGroup = master.mediaGroups.AUDIO[audioGroupId];
40779
40780    if (!audioGroup) {
40781      return null;
40782    }
40783
40784    for (var name in audioGroup) {
40785      var audioType = audioGroup[name];
40786
40787      if (audioType.default && audioType.playlists) {
40788        // codec should be the same for all playlists within the audio type
40789        return parseCodecs(audioType.playlists[0].attributes.CODECS).audioProfile;
40790      }
40791    }
40792
40793    return null;
40794  };
40795
40796  /**
40797   * Calculates the MIME type strings for a working configuration of
40798   * SourceBuffers to play variant streams in a master playlist. If
40799   * there is no possible working configuration, an empty array will be
40800   * returned.
40801   *
40802   * @param master {Object} the m3u8 object for the master playlist
40803   * @param media {Object} the m3u8 object for the variant playlist
40804   * @return {Array} the MIME type strings. If the array has more than
40805   * one entry, the first element should be applied to the video
40806   * SourceBuffer and the second to the audio SourceBuffer.
40807   *
40808   * @private
40809   */
40810  var mimeTypesForPlaylist = function mimeTypesForPlaylist(master, media) {
40811    var containerType = getContainerType(media);
40812    var codecInfo = getCodecs(media);
40813    var mediaAttributes = media.attributes || {};
40814    // Default condition for a traditional HLS (no demuxed audio/video)
40815    var isMuxed = true;
40816    var isMaat = false;
40817
40818    if (!media) {
40819      // Not enough information
40820      return [];
40821    }
40822
40823    if (master.mediaGroups.AUDIO && mediaAttributes.AUDIO) {
40824      var audioGroup = master.mediaGroups.AUDIO[mediaAttributes.AUDIO];
40825
40826      // Handle the case where we are in a multiple-audio track scenario
40827      if (audioGroup) {
40828        isMaat = true;
40829        // Start with the everything demuxed then...
40830        isMuxed = false;
40831        // ...check to see if any audio group tracks are muxed (ie. lacking a uri)
40832        for (var groupId in audioGroup) {
40833          // either a uri is present (if the case of HLS and an external playlist), or
40834          // playlists is present (in the case of DASH where we don't have external audio
40835          // playlists)
40836          if (!audioGroup[groupId].uri && !audioGroup[groupId].playlists) {
40837            isMuxed = true;
40838            break;
40839          }
40840        }
40841      }
40842    }
40843
40844    // HLS with multiple-audio tracks must always get an audio codec.
40845    // Put another way, there is no way to have a video-only multiple-audio HLS!
40846    if (isMaat && !codecInfo.audioProfile) {
40847      if (!isMuxed) {
40848        // It is possible for codecs to be specified on the audio media group playlist but
40849        // not on the rendition playlist. This is mostly the case for DASH, where audio and
40850        // video are always separate (and separately specified).
40851        codecInfo.audioProfile = audioProfileFromDefault(master, mediaAttributes.AUDIO);
40852      }
40853
40854      if (!codecInfo.audioProfile) {
40855        videojs$1.log.warn('Multiple audio tracks present but no audio codec string is specified. ' + 'Attempting to use the default audio codec (mp4a.40.2)');
40856        codecInfo.audioProfile = defaultCodecs.audioProfile;
40857      }
40858    }
40859
40860    // Generate the final codec strings from the codec object generated above
40861    var codecStrings = {};
40862
40863    if (codecInfo.videoCodec) {
40864      codecStrings.video = '' + codecInfo.videoCodec + codecInfo.videoObjectTypeIndicator;
40865    }
40866
40867    if (codecInfo.audioProfile) {
40868      codecStrings.audio = 'mp4a.40.' + codecInfo.audioProfile;
40869    }
40870
40871    // Finally, make and return an array with proper mime-types depending on
40872    // the configuration
40873    var justAudio = makeMimeTypeString('audio', containerType, [codecStrings.audio]);
40874    var justVideo = makeMimeTypeString('video', containerType, [codecStrings.video]);
40875    var bothVideoAudio = makeMimeTypeString('video', containerType, [codecStrings.video, codecStrings.audio]);
40876
40877    if (isMaat) {
40878      if (!isMuxed && codecStrings.video) {
40879        return [justVideo, justAudio];
40880      }
40881
40882      if (!isMuxed && !codecStrings.video) {
40883        // There is no muxed content and no video codec string, so this is an audio only
40884        // stream with alternate audio.
40885        return [justAudio, justAudio];
40886      }
40887
40888      // There exists the possiblity that this will return a `video/container`
40889      // mime-type for the first entry in the array even when there is only audio.
40890      // This doesn't appear to be a problem and simplifies the code.
40891      return [bothVideoAudio, justAudio];
40892    }
40893
40894    // If there is no video codec at all, always just return a single
40895    // audio/<container> mime-type
40896    if (!codecStrings.video) {
40897      return [justAudio];
40898    }
40899
40900    // When not using separate audio media groups, audio and video is
40901    // *always* muxed
40902    return [bothVideoAudio];
40903  };
40904
40905  // Utilities
40906
40907  /**
40908   * Returns the CSS value for the specified property on an element
40909   * using `getComputedStyle`. Firefox has a long-standing issue where
40910   * getComputedStyle() may return null when running in an iframe with
40911   * `display: none`.
40912   *
40913   * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397
40914   * @param {HTMLElement} el the htmlelement to work on
40915   * @param {string} the proprety to get the style for
40916   */
40917  var safeGetComputedStyle = function safeGetComputedStyle(el, property) {
40918    var result = void 0;
40919
40920    if (!el) {
40921      return '';
40922    }
40923
40924    result = window_1.getComputedStyle(el);
40925    if (!result) {
40926      return '';
40927    }
40928
40929    return result[property];
40930  };
40931
40932  /**
40933   * Resuable stable sort function
40934   *
40935   * @param {Playlists} array
40936   * @param {Function} sortFn Different comparators
40937   * @function stableSort
40938   */
40939  var stableSort = function stableSort(array, sortFn) {
40940    var newArray = array.slice();
40941
40942    array.sort(function (left, right) {
40943      var cmp = sortFn(left, right);
40944
40945      if (cmp === 0) {
40946        return newArray.indexOf(left) - newArray.indexOf(right);
40947      }
40948      return cmp;
40949    });
40950  };
40951
40952  /**
40953   * A comparator function to sort two playlist object by bandwidth.
40954   *
40955   * @param {Object} left a media playlist object
40956   * @param {Object} right a media playlist object
40957   * @return {Number} Greater than zero if the bandwidth attribute of
40958   * left is greater than the corresponding attribute of right. Less
40959   * than zero if the bandwidth of right is greater than left and
40960   * exactly zero if the two are equal.
40961   */
40962  var comparePlaylistBandwidth = function comparePlaylistBandwidth(left, right) {
40963    var leftBandwidth = void 0;
40964    var rightBandwidth = void 0;
40965
40966    if (left.attributes.BANDWIDTH) {
40967      leftBandwidth = left.attributes.BANDWIDTH;
40968    }
40969    leftBandwidth = leftBandwidth || window_1.Number.MAX_VALUE;
40970    if (right.attributes.BANDWIDTH) {
40971      rightBandwidth = right.attributes.BANDWIDTH;
40972    }
40973    rightBandwidth = rightBandwidth || window_1.Number.MAX_VALUE;
40974
40975    return leftBandwidth - rightBandwidth;
40976  };
40977
40978  /**
40979   * A comparator function to sort two playlist object by resolution (width).
40980   * @param {Object} left a media playlist object
40981   * @param {Object} right a media playlist object
40982   * @return {Number} Greater than zero if the resolution.width attribute of
40983   * left is greater than the corresponding attribute of right. Less
40984   * than zero if the resolution.width of right is greater than left and
40985   * exactly zero if the two are equal.
40986   */
40987  var comparePlaylistResolution = function comparePlaylistResolution(left, right) {
40988    var leftWidth = void 0;
40989    var rightWidth = void 0;
40990
40991    if (left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) {
40992      leftWidth = left.attributes.RESOLUTION.width;
40993    }
40994
40995    leftWidth = leftWidth || window_1.Number.MAX_VALUE;
40996
40997    if (right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) {
40998      rightWidth = right.attributes.RESOLUTION.width;
40999    }
41000
41001    rightWidth = rightWidth || window_1.Number.MAX_VALUE;
41002
41003    // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions
41004    // have the same media dimensions/ resolution
41005    if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) {
41006      return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH;
41007    }
41008    return leftWidth - rightWidth;
41009  };
41010
41011  /**
41012   * Chooses the appropriate media playlist based on bandwidth and player size
41013   *
41014   * @param {Object} master
41015   *        Object representation of the master manifest
41016   * @param {Number} playerBandwidth
41017   *        Current calculated bandwidth of the player
41018   * @param {Number} playerWidth
41019   *        Current width of the player element
41020   * @param {Number} playerHeight
41021   *        Current height of the player element
41022   * @return {Playlist} the highest bitrate playlist less than the
41023   * currently detected bandwidth, accounting for some amount of
41024   * bandwidth variance
41025   */
41026  var simpleSelector = function simpleSelector(master, playerBandwidth, playerWidth, playerHeight) {
41027    // convert the playlists to an intermediary representation to make comparisons easier
41028    var sortedPlaylistReps = master.playlists.map(function (playlist) {
41029      var width = void 0;
41030      var height = void 0;
41031      var bandwidth = void 0;
41032
41033      width = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.width;
41034      height = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height;
41035      bandwidth = playlist.attributes.BANDWIDTH;
41036
41037      bandwidth = bandwidth || window_1.Number.MAX_VALUE;
41038
41039      return {
41040        bandwidth: bandwidth,
41041        width: width,
41042        height: height,
41043        playlist: playlist
41044      };
41045    });
41046
41047    stableSort(sortedPlaylistReps, function (left, right) {
41048      return left.bandwidth - right.bandwidth;
41049    });
41050
41051    // filter out any playlists that have been excluded due to
41052    // incompatible configurations
41053    sortedPlaylistReps = sortedPlaylistReps.filter(function (rep) {
41054      return !Playlist.isIncompatible(rep.playlist);
41055    });
41056
41057    // filter out any playlists that have been disabled manually through the representations
41058    // api or blacklisted temporarily due to playback errors.
41059    var enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) {
41060      return Playlist.isEnabled(rep.playlist);
41061    });
41062
41063    if (!enabledPlaylistReps.length) {
41064      // if there are no enabled playlists, then they have all been blacklisted or disabled
41065      // by the user through the representations api. In this case, ignore blacklisting and
41066      // fallback to what the user wants by using playlists the user has not disabled.
41067      enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) {
41068        return !Playlist.isDisabled(rep.playlist);
41069      });
41070    }
41071
41072    // filter out any variant that has greater effective bitrate
41073    // than the current estimated bandwidth
41074    var bandwidthPlaylistReps = enabledPlaylistReps.filter(function (rep) {
41075      return rep.bandwidth * Config.BANDWIDTH_VARIANCE < playerBandwidth;
41076    });
41077
41078    var highestRemainingBandwidthRep = bandwidthPlaylistReps[bandwidthPlaylistReps.length - 1];
41079
41080    // get all of the renditions with the same (highest) bandwidth
41081    // and then taking the very first element
41082    var bandwidthBestRep = bandwidthPlaylistReps.filter(function (rep) {
41083      return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
41084    })[0];
41085
41086    // filter out playlists without resolution information
41087    var haveResolution = bandwidthPlaylistReps.filter(function (rep) {
41088      return rep.width && rep.height;
41089    });
41090
41091    // sort variants by resolution
41092    stableSort(haveResolution, function (left, right) {
41093      return left.width - right.width;
41094    });
41095
41096    // if we have the exact resolution as the player use it
41097    var resolutionBestRepList = haveResolution.filter(function (rep) {
41098      return rep.width === playerWidth && rep.height === playerHeight;
41099    });
41100
41101    highestRemainingBandwidthRep = resolutionBestRepList[resolutionBestRepList.length - 1];
41102    // ensure that we pick the highest bandwidth variant that have exact resolution
41103    var resolutionBestRep = resolutionBestRepList.filter(function (rep) {
41104      return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
41105    })[0];
41106
41107    var resolutionPlusOneList = void 0;
41108    var resolutionPlusOneSmallest = void 0;
41109    var resolutionPlusOneRep = void 0;
41110
41111    // find the smallest variant that is larger than the player
41112    // if there is no match of exact resolution
41113    if (!resolutionBestRep) {
41114      resolutionPlusOneList = haveResolution.filter(function (rep) {
41115        return rep.width > playerWidth || rep.height > playerHeight;
41116      });
41117
41118      // find all the variants have the same smallest resolution
41119      resolutionPlusOneSmallest = resolutionPlusOneList.filter(function (rep) {
41120        return rep.width === resolutionPlusOneList[0].width && rep.height === resolutionPlusOneList[0].height;
41121      });
41122
41123      // ensure that we also pick the highest bandwidth variant that
41124      // is just-larger-than the video player
41125      highestRemainingBandwidthRep = resolutionPlusOneSmallest[resolutionPlusOneSmallest.length - 1];
41126      resolutionPlusOneRep = resolutionPlusOneSmallest.filter(function (rep) {
41127        return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
41128      })[0];
41129    }
41130
41131    // fallback chain of variants
41132    var chosenRep = resolutionPlusOneRep || resolutionBestRep || bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0];
41133
41134    return chosenRep ? chosenRep.playlist : null;
41135  };
41136
41137  // Playlist Selectors
41138
41139  /**
41140   * Chooses the appropriate media playlist based on the most recent
41141   * bandwidth estimate and the player size.
41142   *
41143   * Expects to be called within the context of an instance of HlsHandler
41144   *
41145   * @return {Playlist} the highest bitrate playlist less than the
41146   * currently detected bandwidth, accounting for some amount of
41147   * bandwidth variance
41148   */
41149  var lastBandwidthSelector = function lastBandwidthSelector() {
41150    return simpleSelector(this.playlists.master, this.systemBandwidth, parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10), parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10));
41151  };
41152
41153  /**
41154   * Chooses the appropriate media playlist based on the potential to rebuffer
41155   *
41156   * @param {Object} settings
41157   *        Object of information required to use this selector
41158   * @param {Object} settings.master
41159   *        Object representation of the master manifest
41160   * @param {Number} settings.currentTime
41161   *        The current time of the player
41162   * @param {Number} settings.bandwidth
41163   *        Current measured bandwidth
41164   * @param {Number} settings.duration
41165   *        Duration of the media
41166   * @param {Number} settings.segmentDuration
41167   *        Segment duration to be used in round trip time calculations
41168   * @param {Number} settings.timeUntilRebuffer
41169   *        Time left in seconds until the player has to rebuffer
41170   * @param {Number} settings.currentTimeline
41171   *        The current timeline segments are being loaded from
41172   * @param {SyncController} settings.syncController
41173   *        SyncController for determining if we have a sync point for a given playlist
41174   * @return {Object|null}
41175   *         {Object} return.playlist
41176   *         The highest bandwidth playlist with the least amount of rebuffering
41177   *         {Number} return.rebufferingImpact
41178   *         The amount of time in seconds switching to this playlist will rebuffer. A
41179   *         negative value means that switching will cause zero rebuffering.
41180   */
41181  var minRebufferMaxBandwidthSelector = function minRebufferMaxBandwidthSelector(settings) {
41182    var master = settings.master,
41183        currentTime = settings.currentTime,
41184        bandwidth = settings.bandwidth,
41185        duration$$1 = settings.duration,
41186        segmentDuration = settings.segmentDuration,
41187        timeUntilRebuffer = settings.timeUntilRebuffer,
41188        currentTimeline = settings.currentTimeline,
41189        syncController = settings.syncController;
41190
41191    // filter out any playlists that have been excluded due to
41192    // incompatible configurations
41193
41194    var compatiblePlaylists = master.playlists.filter(function (playlist) {
41195      return !Playlist.isIncompatible(playlist);
41196    });
41197
41198    // filter out any playlists that have been disabled manually through the representations
41199    // api or blacklisted temporarily due to playback errors.
41200    var enabledPlaylists = compatiblePlaylists.filter(Playlist.isEnabled);
41201
41202    if (!enabledPlaylists.length) {
41203      // if there are no enabled playlists, then they have all been blacklisted or disabled
41204      // by the user through the representations api. In this case, ignore blacklisting and
41205      // fallback to what the user wants by using playlists the user has not disabled.
41206      enabledPlaylists = compatiblePlaylists.filter(function (playlist) {
41207        return !Playlist.isDisabled(playlist);
41208      });
41209    }
41210
41211    var bandwidthPlaylists = enabledPlaylists.filter(Playlist.hasAttribute.bind(null, 'BANDWIDTH'));
41212
41213    var rebufferingEstimates = bandwidthPlaylists.map(function (playlist) {
41214      var syncPoint = syncController.getSyncPoint(playlist, duration$$1, currentTimeline, currentTime);
41215      // If there is no sync point for this playlist, switching to it will require a
41216      // sync request first. This will double the request time
41217      var numRequests = syncPoint ? 1 : 2;
41218      var requestTimeEstimate = Playlist.estimateSegmentRequestTime(segmentDuration, bandwidth, playlist);
41219      var rebufferingImpact = requestTimeEstimate * numRequests - timeUntilRebuffer;
41220
41221      return {
41222        playlist: playlist,
41223        rebufferingImpact: rebufferingImpact
41224      };
41225    });
41226
41227    var noRebufferingPlaylists = rebufferingEstimates.filter(function (estimate) {
41228      return estimate.rebufferingImpact <= 0;
41229    });
41230
41231    // Sort by bandwidth DESC
41232    stableSort(noRebufferingPlaylists, function (a, b) {
41233      return comparePlaylistBandwidth(b.playlist, a.playlist);
41234    });
41235
41236    if (noRebufferingPlaylists.length) {
41237      return noRebufferingPlaylists[0];
41238    }
41239
41240    stableSort(rebufferingEstimates, function (a, b) {
41241      return a.rebufferingImpact - b.rebufferingImpact;
41242    });
41243
41244    return rebufferingEstimates[0] || null;
41245  };
41246
41247  /**
41248   * Chooses the appropriate media playlist, which in this case is the lowest bitrate
41249   * one with video.  If no renditions with video exist, return the lowest audio rendition.
41250   *
41251   * Expects to be called within the context of an instance of HlsHandler
41252   *
41253   * @return {Object|null}
41254   *         {Object} return.playlist
41255   *         The lowest bitrate playlist that contains a video codec.  If no such rendition
41256   *         exists pick the lowest audio rendition.
41257   */
41258  var lowestBitrateCompatibleVariantSelector = function lowestBitrateCompatibleVariantSelector() {
41259    // filter out any playlists that have been excluded due to
41260    // incompatible configurations or playback errors
41261    var playlists = this.playlists.master.playlists.filter(Playlist.isEnabled);
41262
41263    // Sort ascending by bitrate
41264    stableSort(playlists, function (a, b) {
41265      return comparePlaylistBandwidth(a, b);
41266    });
41267
41268    // Parse and assume that playlists with no video codec have no video
41269    // (this is not necessarily true, although it is generally true).
41270    //
41271    // If an entire manifest has no valid videos everything will get filtered
41272    // out.
41273    var playlistsWithVideo = playlists.filter(function (playlist) {
41274      return parseCodecs(playlist.attributes.CODECS).videoCodec;
41275    });
41276
41277    return playlistsWithVideo[0] || null;
41278  };
41279
41280  /**
41281   * @file segment-loader.js
41282   */
41283
41284  // in ms
41285  var CHECK_BUFFER_DELAY = 500;
41286
41287  /**
41288   * Determines if we should call endOfStream on the media source based
41289   * on the state of the buffer or if appened segment was the final
41290   * segment in the playlist.
41291   *
41292   * @param {Object} playlist a media playlist object
41293   * @param {Object} mediaSource the MediaSource object
41294   * @param {Number} segmentIndex the index of segment we last appended
41295   * @returns {Boolean} do we need to call endOfStream on the MediaSource
41296   */
41297  var detectEndOfStream = function detectEndOfStream(playlist, mediaSource, segmentIndex) {
41298    if (!playlist || !mediaSource) {
41299      return false;
41300    }
41301
41302    var segments = playlist.segments;
41303
41304    // determine a few boolean values to help make the branch below easier
41305    // to read
41306    var appendedLastSegment = segmentIndex === segments.length;
41307
41308    // if we've buffered to the end of the video, we need to call endOfStream
41309    // so that MediaSources can trigger the `ended` event when it runs out of
41310    // buffered data instead of waiting for me
41311    return playlist.endList && mediaSource.readyState === 'open' && appendedLastSegment;
41312  };
41313
41314  var finite = function finite(num) {
41315    return typeof num === 'number' && isFinite(num);
41316  };
41317
41318  var illegalMediaSwitch = function illegalMediaSwitch(loaderType, startingMedia, newSegmentMedia) {
41319    // Although these checks should most likely cover non 'main' types, for now it narrows
41320    // the scope of our checks.
41321    if (loaderType !== 'main' || !startingMedia || !newSegmentMedia) {
41322      return null;
41323    }
41324
41325    if (!newSegmentMedia.containsAudio && !newSegmentMedia.containsVideo) {
41326      return 'Neither audio nor video found in segment.';
41327    }
41328
41329    if (startingMedia.containsVideo && !newSegmentMedia.containsVideo) {
41330      return 'Only audio found in segment when we expected video.' + ' We can\'t switch to audio only from a stream that had video.' + ' To get rid of this message, please add codec information to the manifest.';
41331    }
41332
41333    if (!startingMedia.containsVideo && newSegmentMedia.containsVideo) {
41334      return 'Video found in segment when we expected only audio.' + ' We can\'t switch to a stream with video from an audio only stream.' + ' To get rid of this message, please add codec information to the manifest.';
41335    }
41336
41337    return null;
41338  };
41339
41340  /**
41341   * Calculates a time value that is safe to remove from the back buffer without interupting
41342   * playback.
41343   *
41344   * @param {TimeRange} seekable
41345   *        The current seekable range
41346   * @param {Number} currentTime
41347   *        The current time of the player
41348   * @param {Number} targetDuration
41349   *        The target duration of the current playlist
41350   * @return {Number}
41351   *         Time that is safe to remove from the back buffer without interupting playback
41352   */
41353  var safeBackBufferTrimTime = function safeBackBufferTrimTime(seekable$$1, currentTime, targetDuration) {
41354    var removeToTime = void 0;
41355
41356    if (seekable$$1.length && seekable$$1.start(0) > 0 && seekable$$1.start(0) < currentTime) {
41357      // If we have a seekable range use that as the limit for what can be removed safely
41358      removeToTime = seekable$$1.start(0);
41359    } else {
41360      // otherwise remove anything older than 30 seconds before the current play head
41361      removeToTime = currentTime - 30;
41362    }
41363
41364    // Don't allow removing from the buffer within target duration of current time
41365    // to avoid the possibility of removing the GOP currently being played which could
41366    // cause playback stalls.
41367    return Math.min(removeToTime, currentTime - targetDuration);
41368  };
41369
41370  var segmentInfoString = function segmentInfoString(segmentInfo) {
41371    var _segmentInfo$segment = segmentInfo.segment,
41372        start = _segmentInfo$segment.start,
41373        end = _segmentInfo$segment.end,
41374        _segmentInfo$playlist = segmentInfo.playlist,
41375        seq = _segmentInfo$playlist.mediaSequence,
41376        id = _segmentInfo$playlist.id,
41377        _segmentInfo$playlist2 = _segmentInfo$playlist.segments,
41378        segments = _segmentInfo$playlist2 === undefined ? [] : _segmentInfo$playlist2,
41379        index = segmentInfo.mediaIndex,
41380        timeline = segmentInfo.timeline;
41381
41382    return ['appending [' + index + '] of [' + seq + ', ' + (seq + segments.length) + '] from playlist [' + id + ']', '[' + start + ' => ' + end + '] in timeline [' + timeline + ']'].join(' ');
41383  };
41384
41385  /**
41386   * An object that manages segment loading and appending.
41387   *
41388   * @class SegmentLoader
41389   * @param {Object} options required and optional options
41390   * @extends videojs.EventTarget
41391   */
41392
41393  var SegmentLoader = function (_videojs$EventTarget) {
41394    inherits$3(SegmentLoader, _videojs$EventTarget);
41395
41396    function SegmentLoader(settings) {
41397      classCallCheck$3(this, SegmentLoader);
41398
41399      // check pre-conditions
41400      var _this = possibleConstructorReturn$3(this, (SegmentLoader.__proto__ || Object.getPrototypeOf(SegmentLoader)).call(this));
41401
41402      if (!settings) {
41403        throw new TypeError('Initialization settings are required');
41404      }
41405      if (typeof settings.currentTime !== 'function') {
41406        throw new TypeError('No currentTime getter specified');
41407      }
41408      if (!settings.mediaSource) {
41409        throw new TypeError('No MediaSource specified');
41410      }
41411      // public properties
41412      _this.bandwidth = settings.bandwidth;
41413      _this.throughput = { rate: 0, count: 0 };
41414      _this.roundTrip = NaN;
41415      _this.resetStats_();
41416      _this.mediaIndex = null;
41417
41418      // private settings
41419      _this.hasPlayed_ = settings.hasPlayed;
41420      _this.currentTime_ = settings.currentTime;
41421      _this.seekable_ = settings.seekable;
41422      _this.seeking_ = settings.seeking;
41423      _this.duration_ = settings.duration;
41424      _this.mediaSource_ = settings.mediaSource;
41425      _this.hls_ = settings.hls;
41426      _this.loaderType_ = settings.loaderType;
41427      _this.startingMedia_ = void 0;
41428      _this.segmentMetadataTrack_ = settings.segmentMetadataTrack;
41429      _this.goalBufferLength_ = settings.goalBufferLength;
41430      _this.sourceType_ = settings.sourceType;
41431      _this.state_ = 'INIT';
41432
41433      // private instance variables
41434      _this.checkBufferTimeout_ = null;
41435      _this.error_ = void 0;
41436      _this.currentTimeline_ = -1;
41437      _this.pendingSegment_ = null;
41438      _this.mimeType_ = null;
41439      _this.sourceUpdater_ = null;
41440      _this.xhrOptions_ = null;
41441
41442      // Fragmented mp4 playback
41443      _this.activeInitSegmentId_ = null;
41444      _this.initSegments_ = {};
41445
41446      _this.decrypter_ = settings.decrypter;
41447
41448      // Manages the tracking and generation of sync-points, mappings
41449      // between a time in the display time and a segment index within
41450      // a playlist
41451      _this.syncController_ = settings.syncController;
41452      _this.syncPoint_ = {
41453        segmentIndex: 0,
41454        time: 0
41455      };
41456
41457      _this.syncController_.on('syncinfoupdate', function () {
41458        return _this.trigger('syncinfoupdate');
41459      });
41460
41461      _this.mediaSource_.addEventListener('sourceopen', function () {
41462        return _this.ended_ = false;
41463      });
41464
41465      // ...for determining the fetch location
41466      _this.fetchAtBuffer_ = false;
41467
41468      _this.logger_ = logger('SegmentLoader[' + _this.loaderType_ + ']');
41469
41470      Object.defineProperty(_this, 'state', {
41471        get: function get$$1() {
41472          return this.state_;
41473        },
41474        set: function set$$1(newState) {
41475          if (newState !== this.state_) {
41476            this.logger_(this.state_ + ' -> ' + newState);
41477            this.state_ = newState;
41478          }
41479        }
41480      });
41481      return _this;
41482    }
41483
41484    /**
41485     * reset all of our media stats
41486     *
41487     * @private
41488     */
41489
41490    createClass$2(SegmentLoader, [{
41491      key: 'resetStats_',
41492      value: function resetStats_() {
41493        this.mediaBytesTransferred = 0;
41494        this.mediaRequests = 0;
41495        this.mediaRequestsAborted = 0;
41496        this.mediaRequestsTimedout = 0;
41497        this.mediaRequestsErrored = 0;
41498        this.mediaTransferDuration = 0;
41499        this.mediaSecondsLoaded = 0;
41500      }
41501
41502      /**
41503       * dispose of the SegmentLoader and reset to the default state
41504       */
41505
41506    }, {
41507      key: 'dispose',
41508      value: function dispose() {
41509        this.state = 'DISPOSED';
41510        this.pause();
41511        this.abort_();
41512        if (this.sourceUpdater_) {
41513          this.sourceUpdater_.dispose();
41514        }
41515        this.resetStats_();
41516      }
41517
41518      /**
41519       * abort anything that is currently doing on with the SegmentLoader
41520       * and reset to a default state
41521       */
41522
41523    }, {
41524      key: 'abort',
41525      value: function abort() {
41526        if (this.state !== 'WAITING') {
41527          if (this.pendingSegment_) {
41528            this.pendingSegment_ = null;
41529          }
41530          return;
41531        }
41532
41533        this.abort_();
41534
41535        // We aborted the requests we were waiting on, so reset the loader's state to READY
41536        // since we are no longer "waiting" on any requests. XHR callback is not always run
41537        // when the request is aborted. This will prevent the loader from being stuck in the
41538        // WAITING state indefinitely.
41539        this.state = 'READY';
41540
41541        // don't wait for buffer check timeouts to begin fetching the
41542        // next segment
41543        if (!this.paused()) {
41544          this.monitorBuffer_();
41545        }
41546      }
41547
41548      /**
41549       * abort all pending xhr requests and null any pending segements
41550       *
41551       * @private
41552       */
41553
41554    }, {
41555      key: 'abort_',
41556      value: function abort_() {
41557        if (this.pendingSegment_) {
41558          this.pendingSegment_.abortRequests();
41559        }
41560
41561        // clear out the segment being processed
41562        this.pendingSegment_ = null;
41563      }
41564
41565      /**
41566       * set an error on the segment loader and null out any pending segements
41567       *
41568       * @param {Error} error the error to set on the SegmentLoader
41569       * @return {Error} the error that was set or that is currently set
41570       */
41571
41572    }, {
41573      key: 'error',
41574      value: function error(_error) {
41575        if (typeof _error !== 'undefined') {
41576          this.error_ = _error;
41577        }
41578
41579        this.pendingSegment_ = null;
41580        return this.error_;
41581      }
41582    }, {
41583      key: 'endOfStream',
41584      value: function endOfStream() {
41585        this.ended_ = true;
41586        this.pause();
41587        this.trigger('ended');
41588      }
41589
41590      /**
41591       * Indicates which time ranges are buffered
41592       *
41593       * @return {TimeRange}
41594       *         TimeRange object representing the current buffered ranges
41595       */
41596
41597    }, {
41598      key: 'buffered_',
41599      value: function buffered_() {
41600        if (!this.sourceUpdater_) {
41601          return videojs$1.createTimeRanges();
41602        }
41603
41604        return this.sourceUpdater_.buffered();
41605      }
41606
41607      /**
41608       * Gets and sets init segment for the provided map
41609       *
41610       * @param {Object} map
41611       *        The map object representing the init segment to get or set
41612       * @param {Boolean=} set
41613       *        If true, the init segment for the provided map should be saved
41614       * @return {Object}
41615       *         map object for desired init segment
41616       */
41617
41618    }, {
41619      key: 'initSegment',
41620      value: function initSegment(map) {
41621        var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
41622
41623        if (!map) {
41624          return null;
41625        }
41626
41627        var id = initSegmentId(map);
41628        var storedMap = this.initSegments_[id];
41629
41630        if (set$$1 && !storedMap && map.bytes) {
41631          this.initSegments_[id] = storedMap = {
41632            resolvedUri: map.resolvedUri,
41633            byterange: map.byterange,
41634            bytes: map.bytes
41635          };
41636        }
41637
41638        return storedMap || map;
41639      }
41640
41641      /**
41642       * Returns true if all configuration required for loading is present, otherwise false.
41643       *
41644       * @return {Boolean} True if the all configuration is ready for loading
41645       * @private
41646       */
41647
41648    }, {
41649      key: 'couldBeginLoading_',
41650      value: function couldBeginLoading_() {
41651        return this.playlist_ && (
41652        // the source updater is created when init_ is called, so either having a
41653        // source updater or being in the INIT state with a mimeType is enough
41654        // to say we have all the needed configuration to start loading.
41655        this.sourceUpdater_ || this.mimeType_ && this.state === 'INIT') && !this.paused();
41656      }
41657
41658      /**
41659       * load a playlist and start to fill the buffer
41660       */
41661
41662    }, {
41663      key: 'load',
41664      value: function load() {
41665        // un-pause
41666        this.monitorBuffer_();
41667
41668        // if we don't have a playlist yet, keep waiting for one to be
41669        // specified
41670        if (!this.playlist_) {
41671          return;
41672        }
41673
41674        // not sure if this is the best place for this
41675        this.syncController_.setDateTimeMapping(this.playlist_);
41676
41677        // if all the configuration is ready, initialize and begin loading
41678        if (this.state === 'INIT' && this.couldBeginLoading_()) {
41679          return this.init_();
41680        }
41681
41682        // if we're in the middle of processing a segment already, don't
41683        // kick off an additional segment request
41684        if (!this.couldBeginLoading_() || this.state !== 'READY' && this.state !== 'INIT') {
41685          return;
41686        }
41687
41688        this.state = 'READY';
41689      }
41690
41691      /**
41692       * Once all the starting parameters have been specified, begin
41693       * operation. This method should only be invoked from the INIT
41694       * state.
41695       *
41696       * @private
41697       */
41698
41699    }, {
41700      key: 'init_',
41701      value: function init_() {
41702        this.state = 'READY';
41703        this.sourceUpdater_ = new SourceUpdater(this.mediaSource_, this.mimeType_, this.loaderType_, this.sourceBufferEmitter_);
41704        this.resetEverything();
41705        return this.monitorBuffer_();
41706      }
41707
41708      /**
41709       * set a playlist on the segment loader
41710       *
41711       * @param {PlaylistLoader} media the playlist to set on the segment loader
41712       */
41713
41714    }, {
41715      key: 'playlist',
41716      value: function playlist(newPlaylist) {
41717        var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
41718
41719        if (!newPlaylist) {
41720          return;
41721        }
41722
41723        var oldPlaylist = this.playlist_;
41724        var segmentInfo = this.pendingSegment_;
41725
41726        this.playlist_ = newPlaylist;
41727        this.xhrOptions_ = options;
41728
41729        // when we haven't started playing yet, the start of a live playlist
41730        // is always our zero-time so force a sync update each time the playlist
41731        // is refreshed from the server
41732        if (!this.hasPlayed_()) {
41733          newPlaylist.syncInfo = {
41734            mediaSequence: newPlaylist.mediaSequence,
41735            time: 0
41736          };
41737        }
41738
41739        var oldId = oldPlaylist ? oldPlaylist.id : null;
41740
41741        this.logger_('playlist update [' + oldId + ' => ' + newPlaylist.id + ']');
41742
41743        // in VOD, this is always a rendition switch (or we updated our syncInfo above)
41744        // in LIVE, we always want to update with new playlists (including refreshes)
41745        this.trigger('syncinfoupdate');
41746
41747        // if we were unpaused but waiting for a playlist, start
41748        // buffering now
41749        if (this.state === 'INIT' && this.couldBeginLoading_()) {
41750          return this.init_();
41751        }
41752
41753        if (!oldPlaylist || oldPlaylist.uri !== newPlaylist.uri) {
41754          if (this.mediaIndex !== null) {
41755            // we must "resync" the segment loader when we switch renditions and
41756            // the segment loader is already synced to the previous rendition
41757            this.resyncLoader();
41758          }
41759
41760          // the rest of this function depends on `oldPlaylist` being defined
41761          return;
41762        }
41763
41764        // we reloaded the same playlist so we are in a live scenario
41765        // and we will likely need to adjust the mediaIndex
41766        var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence;
41767
41768        this.logger_('live window shift [' + mediaSequenceDiff + ']');
41769
41770        // update the mediaIndex on the SegmentLoader
41771        // this is important because we can abort a request and this value must be
41772        // equal to the last appended mediaIndex
41773        if (this.mediaIndex !== null) {
41774          this.mediaIndex -= mediaSequenceDiff;
41775        }
41776
41777        // update the mediaIndex on the SegmentInfo object
41778        // this is important because we will update this.mediaIndex with this value
41779        // in `handleUpdateEnd_` after the segment has been successfully appended
41780        if (segmentInfo) {
41781          segmentInfo.mediaIndex -= mediaSequenceDiff;
41782
41783          // we need to update the referenced segment so that timing information is
41784          // saved for the new playlist's segment, however, if the segment fell off the
41785          // playlist, we can leave the old reference and just lose the timing info
41786          if (segmentInfo.mediaIndex >= 0) {
41787            segmentInfo.segment = newPlaylist.segments[segmentInfo.mediaIndex];
41788          }
41789        }
41790
41791        this.syncController_.saveExpiredSegmentInfo(oldPlaylist, newPlaylist);
41792      }
41793
41794      /**
41795       * Prevent the loader from fetching additional segments. If there
41796       * is a segment request outstanding, it will finish processing
41797       * before the loader halts. A segment loader can be unpaused by
41798       * calling load().
41799       */
41800
41801    }, {
41802      key: 'pause',
41803      value: function pause() {
41804        if (this.checkBufferTimeout_) {
41805          window_1.clearTimeout(this.checkBufferTimeout_);
41806
41807          this.checkBufferTimeout_ = null;
41808        }
41809      }
41810
41811      /**
41812       * Returns whether the segment loader is fetching additional
41813       * segments when given the opportunity. This property can be
41814       * modified through calls to pause() and load().
41815       */
41816
41817    }, {
41818      key: 'paused',
41819      value: function paused() {
41820        return this.checkBufferTimeout_ === null;
41821      }
41822
41823      /**
41824       * create/set the following mimetype on the SourceBuffer through a
41825       * SourceUpdater
41826       *
41827       * @param {String} mimeType the mime type string to use
41828       * @param {Object} sourceBufferEmitter an event emitter that fires when a source buffer
41829       * is added to the media source
41830       */
41831
41832    }, {
41833      key: 'mimeType',
41834      value: function mimeType(_mimeType, sourceBufferEmitter) {
41835        if (this.mimeType_) {
41836          return;
41837        }
41838
41839        this.mimeType_ = _mimeType;
41840        this.sourceBufferEmitter_ = sourceBufferEmitter;
41841        // if we were unpaused but waiting for a sourceUpdater, start
41842        // buffering now
41843        if (this.state === 'INIT' && this.couldBeginLoading_()) {
41844          this.init_();
41845        }
41846      }
41847
41848      /**
41849       * Delete all the buffered data and reset the SegmentLoader
41850       */
41851
41852    }, {
41853      key: 'resetEverything',
41854      value: function resetEverything() {
41855        this.ended_ = false;
41856        this.resetLoader();
41857        this.remove(0, this.duration_());
41858        this.trigger('reseteverything');
41859      }
41860
41861      /**
41862       * Force the SegmentLoader to resync and start loading around the currentTime instead
41863       * of starting at the end of the buffer
41864       *
41865       * Useful for fast quality changes
41866       */
41867
41868    }, {
41869      key: 'resetLoader',
41870      value: function resetLoader() {
41871        this.fetchAtBuffer_ = false;
41872        this.resyncLoader();
41873      }
41874
41875      /**
41876       * Force the SegmentLoader to restart synchronization and make a conservative guess
41877       * before returning to the simple walk-forward method
41878       */
41879
41880    }, {
41881      key: 'resyncLoader',
41882      value: function resyncLoader() {
41883        this.mediaIndex = null;
41884        this.syncPoint_ = null;
41885        this.abort();
41886      }
41887
41888      /**
41889       * Remove any data in the source buffer between start and end times
41890       * @param {Number} start - the start time of the region to remove from the buffer
41891       * @param {Number} end - the end time of the region to remove from the buffer
41892       */
41893
41894    }, {
41895      key: 'remove',
41896      value: function remove(start, end) {
41897        if (this.sourceUpdater_) {
41898          this.sourceUpdater_.remove(start, end);
41899        }
41900        removeCuesFromTrack(start, end, this.segmentMetadataTrack_);
41901      }
41902
41903      /**
41904       * (re-)schedule monitorBufferTick_ to run as soon as possible
41905       *
41906       * @private
41907       */
41908
41909    }, {
41910      key: 'monitorBuffer_',
41911      value: function monitorBuffer_() {
41912        if (this.checkBufferTimeout_) {
41913          window_1.clearTimeout(this.checkBufferTimeout_);
41914        }
41915
41916        this.checkBufferTimeout_ = window_1.setTimeout(this.monitorBufferTick_.bind(this), 1);
41917      }
41918
41919      /**
41920       * As long as the SegmentLoader is in the READY state, periodically
41921       * invoke fillBuffer_().
41922       *
41923       * @private
41924       */
41925
41926    }, {
41927      key: 'monitorBufferTick_',
41928      value: function monitorBufferTick_() {
41929        if (this.state === 'READY') {
41930          this.fillBuffer_();
41931        }
41932
41933        if (this.checkBufferTimeout_) {
41934          window_1.clearTimeout(this.checkBufferTimeout_);
41935        }
41936
41937        this.checkBufferTimeout_ = window_1.setTimeout(this.monitorBufferTick_.bind(this), CHECK_BUFFER_DELAY);
41938      }
41939
41940      /**
41941       * fill the buffer with segements unless the sourceBuffers are
41942       * currently updating
41943       *
41944       * Note: this function should only ever be called by monitorBuffer_
41945       * and never directly
41946       *
41947       * @private
41948       */
41949
41950    }, {
41951      key: 'fillBuffer_',
41952      value: function fillBuffer_() {
41953        if (this.sourceUpdater_.updating()) {
41954          return;
41955        }
41956
41957        if (!this.syncPoint_) {
41958          this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_());
41959        }
41960
41961        // see if we need to begin loading immediately
41962        var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_);
41963
41964        if (!segmentInfo) {
41965          return;
41966        }
41967
41968        var isEndOfStream = detectEndOfStream(this.playlist_, this.mediaSource_, segmentInfo.mediaIndex);
41969
41970        if (isEndOfStream) {
41971          this.endOfStream();
41972          return;
41973        }
41974
41975        if (segmentInfo.mediaIndex === this.playlist_.segments.length - 1 && this.mediaSource_.readyState === 'ended' && !this.seeking_()) {
41976          return;
41977        }
41978
41979        // We will need to change timestampOffset of the sourceBuffer if either of
41980        // the following conditions are true:
41981        // - The segment.timeline !== this.currentTimeline
41982        //   (we are crossing a discontinuity somehow)
41983        // - The "timestampOffset" for the start of this segment is less than
41984        //   the currently set timestampOffset
41985        if (segmentInfo.timeline !== this.currentTimeline_ || segmentInfo.startOfSegment !== null && segmentInfo.startOfSegment < this.sourceUpdater_.timestampOffset()) {
41986          this.syncController_.reset();
41987          segmentInfo.timestampOffset = segmentInfo.startOfSegment;
41988        }
41989
41990        this.loadSegment_(segmentInfo);
41991      }
41992
41993      /**
41994       * Determines what segment request should be made, given current playback
41995       * state.
41996       *
41997       * @param {TimeRanges} buffered - the state of the buffer
41998       * @param {Object} playlist - the playlist object to fetch segments from
41999       * @param {Number} mediaIndex - the previous mediaIndex fetched or null
42000       * @param {Boolean} hasPlayed - a flag indicating whether we have played or not
42001       * @param {Number} currentTime - the playback position in seconds
42002       * @param {Object} syncPoint - a segment info object that describes the
42003       * @returns {Object} a segment request object that describes the segment to load
42004       */
42005
42006    }, {
42007      key: 'checkBuffer_',
42008      value: function checkBuffer_(buffered, playlist, mediaIndex, hasPlayed, currentTime, syncPoint) {
42009        var lastBufferedEnd = 0;
42010        var startOfSegment = void 0;
42011
42012        if (buffered.length) {
42013          lastBufferedEnd = buffered.end(buffered.length - 1);
42014        }
42015
42016        var bufferedTime = Math.max(0, lastBufferedEnd - currentTime);
42017
42018        if (!playlist.segments.length) {
42019          return null;
42020        }
42021
42022        // if there is plenty of content buffered, and the video has
42023        // been played before relax for awhile
42024        if (bufferedTime >= this.goalBufferLength_()) {
42025          return null;
42026        }
42027
42028        // if the video has not yet played once, and we already have
42029        // one segment downloaded do nothing
42030        if (!hasPlayed && bufferedTime >= 1) {
42031          return null;
42032        }
42033
42034        // When the syncPoint is null, there is no way of determining a good
42035        // conservative segment index to fetch from
42036        // The best thing to do here is to get the kind of sync-point data by
42037        // making a request
42038        if (syncPoint === null) {
42039          mediaIndex = this.getSyncSegmentCandidate_(playlist);
42040          return this.generateSegmentInfo_(playlist, mediaIndex, null, true);
42041        }
42042
42043        // Under normal playback conditions fetching is a simple walk forward
42044        if (mediaIndex !== null) {
42045          var segment = playlist.segments[mediaIndex];
42046
42047          if (segment && segment.end) {
42048            startOfSegment = segment.end;
42049          } else {
42050            startOfSegment = lastBufferedEnd;
42051          }
42052          return this.generateSegmentInfo_(playlist, mediaIndex + 1, startOfSegment, false);
42053        }
42054
42055        // There is a sync-point but the lack of a mediaIndex indicates that
42056        // we need to make a good conservative guess about which segment to
42057        // fetch
42058        if (this.fetchAtBuffer_) {
42059          // Find the segment containing the end of the buffer
42060          var mediaSourceInfo = Playlist.getMediaInfoForTime(playlist, lastBufferedEnd, syncPoint.segmentIndex, syncPoint.time);
42061
42062          mediaIndex = mediaSourceInfo.mediaIndex;
42063          startOfSegment = mediaSourceInfo.startTime;
42064        } else {
42065          // Find the segment containing currentTime
42066          var _mediaSourceInfo = Playlist.getMediaInfoForTime(playlist, currentTime, syncPoint.segmentIndex, syncPoint.time);
42067
42068          mediaIndex = _mediaSourceInfo.mediaIndex;
42069          startOfSegment = _mediaSourceInfo.startTime;
42070        }
42071
42072        return this.generateSegmentInfo_(playlist, mediaIndex, startOfSegment, false);
42073      }
42074
42075      /**
42076       * The segment loader has no recourse except to fetch a segment in the
42077       * current playlist and use the internal timestamps in that segment to
42078       * generate a syncPoint. This function returns a good candidate index
42079       * for that process.
42080       *
42081       * @param {Object} playlist - the playlist object to look for a
42082       * @returns {Number} An index of a segment from the playlist to load
42083       */
42084
42085    }, {
42086      key: 'getSyncSegmentCandidate_',
42087      value: function getSyncSegmentCandidate_(playlist) {
42088        var _this2 = this;
42089
42090        if (this.currentTimeline_ === -1) {
42091          return 0;
42092        }
42093
42094        var segmentIndexArray = playlist.segments.map(function (s, i) {
42095          return {
42096            timeline: s.timeline,
42097            segmentIndex: i
42098          };
42099        }).filter(function (s) {
42100          return s.timeline === _this2.currentTimeline_;
42101        });
42102
42103        if (segmentIndexArray.length) {
42104          return segmentIndexArray[Math.min(segmentIndexArray.length - 1, 1)].segmentIndex;
42105        }
42106
42107        return Math.max(playlist.segments.length - 1, 0);
42108      }
42109    }, {
42110      key: 'generateSegmentInfo_',
42111      value: function generateSegmentInfo_(playlist, mediaIndex, startOfSegment, isSyncRequest) {
42112        if (mediaIndex < 0 || mediaIndex >= playlist.segments.length) {
42113          return null;
42114        }
42115
42116        var segment = playlist.segments[mediaIndex];
42117
42118        return {
42119          requestId: 'segment-loader-' + Math.random(),
42120          // resolve the segment URL relative to the playlist
42121          uri: segment.resolvedUri,
42122          // the segment's mediaIndex at the time it was requested
42123          mediaIndex: mediaIndex,
42124          // whether or not to update the SegmentLoader's state with this
42125          // segment's mediaIndex
42126          isSyncRequest: isSyncRequest,
42127          startOfSegment: startOfSegment,
42128          // the segment's playlist
42129          playlist: playlist,
42130          // unencrypted bytes of the segment
42131          bytes: null,
42132          // when a key is defined for this segment, the encrypted bytes
42133          encryptedBytes: null,
42134          // The target timestampOffset for this segment when we append it
42135          // to the source buffer
42136          timestampOffset: null,
42137          // The timeline that the segment is in
42138          timeline: segment.timeline,
42139          // The expected duration of the segment in seconds
42140          duration: segment.duration,
42141          // retain the segment in case the playlist updates while doing an async process
42142          segment: segment
42143        };
42144      }
42145
42146      /**
42147       * Determines if the network has enough bandwidth to complete the current segment
42148       * request in a timely manner. If not, the request will be aborted early and bandwidth
42149       * updated to trigger a playlist switch.
42150       *
42151       * @param {Object} stats
42152       *        Object containing stats about the request timing and size
42153       * @return {Boolean} True if the request was aborted, false otherwise
42154       * @private
42155       */
42156
42157    }, {
42158      key: 'abortRequestEarly_',
42159      value: function abortRequestEarly_(stats) {
42160        if (this.hls_.tech_.paused() ||
42161        // Don't abort if the current playlist is on the lowestEnabledRendition
42162        // TODO: Replace using timeout with a boolean indicating whether this playlist is
42163        //       the lowestEnabledRendition.
42164        !this.xhrOptions_.timeout ||
42165        // Don't abort if we have no bandwidth information to estimate segment sizes
42166        !this.playlist_.attributes.BANDWIDTH) {
42167          return false;
42168        }
42169
42170        // Wait at least 1 second since the first byte of data has been received before
42171        // using the calculated bandwidth from the progress event to allow the bitrate
42172        // to stabilize
42173        if (Date.now() - (stats.firstBytesReceivedAt || Date.now()) < 1000) {
42174          return false;
42175        }
42176
42177        var currentTime = this.currentTime_();
42178        var measuredBandwidth = stats.bandwidth;
42179        var segmentDuration = this.pendingSegment_.duration;
42180
42181        var requestTimeRemaining = Playlist.estimateSegmentRequestTime(segmentDuration, measuredBandwidth, this.playlist_, stats.bytesReceived);
42182
42183        // Subtract 1 from the timeUntilRebuffer so we still consider an early abort
42184        // if we are only left with less than 1 second when the request completes.
42185        // A negative timeUntilRebuffering indicates we are already rebuffering
42186        var timeUntilRebuffer$$1 = timeUntilRebuffer(this.buffered_(), currentTime, this.hls_.tech_.playbackRate()) - 1;
42187
42188        // Only consider aborting early if the estimated time to finish the download
42189        // is larger than the estimated time until the player runs out of forward buffer
42190        if (requestTimeRemaining <= timeUntilRebuffer$$1) {
42191          return false;
42192        }
42193
42194        var switchCandidate = minRebufferMaxBandwidthSelector({
42195          master: this.hls_.playlists.master,
42196          currentTime: currentTime,
42197          bandwidth: measuredBandwidth,
42198          duration: this.duration_(),
42199          segmentDuration: segmentDuration,
42200          timeUntilRebuffer: timeUntilRebuffer$$1,
42201          currentTimeline: this.currentTimeline_,
42202          syncController: this.syncController_
42203        });
42204
42205        if (!switchCandidate) {
42206          return;
42207        }
42208
42209        var rebufferingImpact = requestTimeRemaining - timeUntilRebuffer$$1;
42210
42211        var timeSavedBySwitching = rebufferingImpact - switchCandidate.rebufferingImpact;
42212
42213        var minimumTimeSaving = 0.5;
42214
42215        // If we are already rebuffering, increase the amount of variance we add to the
42216        // potential round trip time of the new request so that we are not too aggressive
42217        // with switching to a playlist that might save us a fraction of a second.
42218        if (timeUntilRebuffer$$1 <= TIME_FUDGE_FACTOR) {
42219          minimumTimeSaving = 1;
42220        }
42221
42222        if (!switchCandidate.playlist || switchCandidate.playlist.uri === this.playlist_.uri || timeSavedBySwitching < minimumTimeSaving) {
42223          return false;
42224        }
42225
42226        // set the bandwidth to that of the desired playlist being sure to scale by
42227        // BANDWIDTH_VARIANCE and add one so the playlist selector does not exclude it
42228        // don't trigger a bandwidthupdate as the bandwidth is artifial
42229        this.bandwidth = switchCandidate.playlist.attributes.BANDWIDTH * Config.BANDWIDTH_VARIANCE + 1;
42230        this.abort();
42231        this.trigger('earlyabort');
42232        return true;
42233      }
42234
42235      /**
42236       * XHR `progress` event handler
42237       *
42238       * @param {Event}
42239       *        The XHR `progress` event
42240       * @param {Object} simpleSegment
42241       *        A simplified segment object copy
42242       * @private
42243       */
42244
42245    }, {
42246      key: 'handleProgress_',
42247      value: function handleProgress_(event, simpleSegment) {
42248        if (!this.pendingSegment_ || simpleSegment.requestId !== this.pendingSegment_.requestId || this.abortRequestEarly_(simpleSegment.stats)) {
42249          return;
42250        }
42251
42252        this.trigger('progress');
42253      }
42254
42255      /**
42256       * load a specific segment from a request into the buffer
42257       *
42258       * @private
42259       */
42260
42261    }, {
42262      key: 'loadSegment_',
42263      value: function loadSegment_(segmentInfo) {
42264        this.state = 'WAITING';
42265        this.pendingSegment_ = segmentInfo;
42266        this.trimBackBuffer_(segmentInfo);
42267
42268        segmentInfo.abortRequests = mediaSegmentRequest(this.hls_.xhr, this.xhrOptions_, this.decrypter_, this.createSimplifiedSegmentObj_(segmentInfo),
42269        // progress callback
42270        this.handleProgress_.bind(this), this.segmentRequestFinished_.bind(this));
42271      }
42272
42273      /**
42274       * trim the back buffer so that we don't have too much data
42275       * in the source buffer
42276       *
42277       * @private
42278       *
42279       * @param {Object} segmentInfo - the current segment
42280       */
42281
42282    }, {
42283      key: 'trimBackBuffer_',
42284      value: function trimBackBuffer_(segmentInfo) {
42285        var removeToTime = safeBackBufferTrimTime(this.seekable_(), this.currentTime_(), this.playlist_.targetDuration || 10);
42286
42287        // Chrome has a hard limit of 150MB of
42288        // buffer and a very conservative "garbage collector"
42289        // We manually clear out the old buffer to ensure
42290        // we don't trigger the QuotaExceeded error
42291        // on the source buffer during subsequent appends
42292
42293        if (removeToTime > 0) {
42294          this.remove(0, removeToTime);
42295        }
42296      }
42297
42298      /**
42299       * created a simplified copy of the segment object with just the
42300       * information necessary to perform the XHR and decryption
42301       *
42302       * @private
42303       *
42304       * @param {Object} segmentInfo - the current segment
42305       * @returns {Object} a simplified segment object copy
42306       */
42307
42308    }, {
42309      key: 'createSimplifiedSegmentObj_',
42310      value: function createSimplifiedSegmentObj_(segmentInfo) {
42311        var segment = segmentInfo.segment;
42312        var simpleSegment = {
42313          resolvedUri: segment.resolvedUri,
42314          byterange: segment.byterange,
42315          requestId: segmentInfo.requestId
42316        };
42317
42318        if (segment.key) {
42319          // if the media sequence is greater than 2^32, the IV will be incorrect
42320          // assuming 10s segments, that would be about 1300 years
42321          var iv = segment.key.iv || new Uint32Array([0, 0, 0, segmentInfo.mediaIndex + segmentInfo.playlist.mediaSequence]);
42322
42323          simpleSegment.key = {
42324            resolvedUri: segment.key.resolvedUri,
42325            iv: iv
42326          };
42327        }
42328
42329        if (segment.map) {
42330          simpleSegment.map = this.initSegment(segment.map);
42331        }
42332
42333        return simpleSegment;
42334      }
42335
42336      /**
42337       * Handle the callback from the segmentRequest function and set the
42338       * associated SegmentLoader state and errors if necessary
42339       *
42340       * @private
42341       */
42342
42343    }, {
42344      key: 'segmentRequestFinished_',
42345      value: function segmentRequestFinished_(error, simpleSegment) {
42346        // every request counts as a media request even if it has been aborted
42347        // or canceled due to a timeout
42348        this.mediaRequests += 1;
42349
42350        if (simpleSegment.stats) {
42351          this.mediaBytesTransferred += simpleSegment.stats.bytesReceived;
42352          this.mediaTransferDuration += simpleSegment.stats.roundTripTime;
42353        }
42354
42355        // The request was aborted and the SegmentLoader has already been reset
42356        if (!this.pendingSegment_) {
42357          this.mediaRequestsAborted += 1;
42358          return;
42359        }
42360
42361        // the request was aborted and the SegmentLoader has already started
42362        // another request. this can happen when the timeout for an aborted
42363        // request triggers due to a limitation in the XHR library
42364        // do not count this as any sort of request or we risk double-counting
42365        if (simpleSegment.requestId !== this.pendingSegment_.requestId) {
42366          return;
42367        }
42368
42369        // an error occurred from the active pendingSegment_ so reset everything
42370        if (error) {
42371          this.pendingSegment_ = null;
42372          this.state = 'READY';
42373
42374          // the requests were aborted just record the aborted stat and exit
42375          // this is not a true error condition and nothing corrective needs
42376          // to be done
42377          if (error.code === REQUEST_ERRORS.ABORTED) {
42378            this.mediaRequestsAborted += 1;
42379            return;
42380          }
42381
42382          this.pause();
42383
42384          // the error is really just that at least one of the requests timed-out
42385          // set the bandwidth to a very low value and trigger an ABR switch to
42386          // take emergency action
42387          if (error.code === REQUEST_ERRORS.TIMEOUT) {
42388            this.mediaRequestsTimedout += 1;
42389            this.bandwidth = 1;
42390            this.roundTrip = NaN;
42391            this.trigger('bandwidthupdate');
42392            return;
42393          }
42394
42395          // if control-flow has arrived here, then the error is real
42396          // emit an error event to blacklist the current playlist
42397          this.mediaRequestsErrored += 1;
42398          this.error(error);
42399          this.trigger('error');
42400          return;
42401        }
42402
42403        // the response was a success so set any bandwidth stats the request
42404        // generated for ABR purposes
42405        this.bandwidth = simpleSegment.stats.bandwidth;
42406        this.roundTrip = simpleSegment.stats.roundTripTime;
42407
42408        // if this request included an initialization segment, save that data
42409        // to the initSegment cache
42410        if (simpleSegment.map) {
42411          simpleSegment.map = this.initSegment(simpleSegment.map, true);
42412        }
42413
42414        this.processSegmentResponse_(simpleSegment);
42415      }
42416
42417      /**
42418       * Move any important data from the simplified segment object
42419       * back to the real segment object for future phases
42420       *
42421       * @private
42422       */
42423
42424    }, {
42425      key: 'processSegmentResponse_',
42426      value: function processSegmentResponse_(simpleSegment) {
42427        var segmentInfo = this.pendingSegment_;
42428
42429        segmentInfo.bytes = simpleSegment.bytes;
42430        if (simpleSegment.map) {
42431          segmentInfo.segment.map.bytes = simpleSegment.map.bytes;
42432        }
42433
42434        segmentInfo.endOfAllRequests = simpleSegment.endOfAllRequests;
42435        this.handleSegment_();
42436      }
42437
42438      /**
42439       * append a decrypted segement to the SourceBuffer through a SourceUpdater
42440       *
42441       * @private
42442       */
42443
42444    }, {
42445      key: 'handleSegment_',
42446      value: function handleSegment_() {
42447        var _this3 = this;
42448
42449        if (!this.pendingSegment_) {
42450          this.state = 'READY';
42451          return;
42452        }
42453
42454        var segmentInfo = this.pendingSegment_;
42455        var segment = segmentInfo.segment;
42456        var timingInfo = this.syncController_.probeSegmentInfo(segmentInfo);
42457
42458        // When we have our first timing info, determine what media types this loader is
42459        // dealing with. Although we're maintaining extra state, it helps to preserve the
42460        // separation of segment loader from the actual source buffers.
42461        if (typeof this.startingMedia_ === 'undefined' && timingInfo && (
42462        // Guard against cases where we're not getting timing info at all until we are
42463        // certain that all streams will provide it.
42464        timingInfo.containsAudio || timingInfo.containsVideo)) {
42465          this.startingMedia_ = {
42466            containsAudio: timingInfo.containsAudio,
42467            containsVideo: timingInfo.containsVideo
42468          };
42469        }
42470
42471        var illegalMediaSwitchError = illegalMediaSwitch(this.loaderType_, this.startingMedia_, timingInfo);
42472
42473        if (illegalMediaSwitchError) {
42474          this.error({
42475            message: illegalMediaSwitchError,
42476            blacklistDuration: Infinity
42477          });
42478          this.trigger('error');
42479          return;
42480        }
42481
42482        if (segmentInfo.isSyncRequest) {
42483          this.trigger('syncinfoupdate');
42484          this.pendingSegment_ = null;
42485          this.state = 'READY';
42486          return;
42487        }
42488
42489        if (segmentInfo.timestampOffset !== null && segmentInfo.timestampOffset !== this.sourceUpdater_.timestampOffset()) {
42490          this.sourceUpdater_.timestampOffset(segmentInfo.timestampOffset);
42491          // fired when a timestamp offset is set in HLS (can also identify discontinuities)
42492          this.trigger('timestampoffset');
42493        }
42494
42495        var timelineMapping = this.syncController_.mappingForTimeline(segmentInfo.timeline);
42496
42497        if (timelineMapping !== null) {
42498          this.trigger({
42499            type: 'segmenttimemapping',
42500            mapping: timelineMapping
42501          });
42502        }
42503
42504        this.state = 'APPENDING';
42505
42506        // if the media initialization segment is changing, append it
42507        // before the content segment
42508        if (segment.map) {
42509          var initId = initSegmentId(segment.map);
42510
42511          if (!this.activeInitSegmentId_ || this.activeInitSegmentId_ !== initId) {
42512            var initSegment = this.initSegment(segment.map);
42513
42514            this.sourceUpdater_.appendBuffer(initSegment.bytes, function () {
42515              _this3.activeInitSegmentId_ = initId;
42516            });
42517          }
42518        }
42519
42520        segmentInfo.byteLength = segmentInfo.bytes.byteLength;
42521        if (typeof segment.start === 'number' && typeof segment.end === 'number') {
42522          this.mediaSecondsLoaded += segment.end - segment.start;
42523        } else {
42524          this.mediaSecondsLoaded += segment.duration;
42525        }
42526
42527        this.logger_(segmentInfoString(segmentInfo));
42528
42529        this.sourceUpdater_.appendBuffer(segmentInfo.bytes, this.handleUpdateEnd_.bind(this));
42530      }
42531
42532      /**
42533       * callback to run when appendBuffer is finished. detects if we are
42534       * in a good state to do things with the data we got, or if we need
42535       * to wait for more
42536       *
42537       * @private
42538       */
42539
42540    }, {
42541      key: 'handleUpdateEnd_',
42542      value: function handleUpdateEnd_() {
42543        if (!this.pendingSegment_) {
42544          this.state = 'READY';
42545          if (!this.paused()) {
42546            this.monitorBuffer_();
42547          }
42548          return;
42549        }
42550
42551        var segmentInfo = this.pendingSegment_;
42552        var segment = segmentInfo.segment;
42553        var isWalkingForward = this.mediaIndex !== null;
42554
42555        this.pendingSegment_ = null;
42556        this.recordThroughput_(segmentInfo);
42557        this.addSegmentMetadataCue_(segmentInfo);
42558
42559        this.state = 'READY';
42560
42561        this.mediaIndex = segmentInfo.mediaIndex;
42562        this.fetchAtBuffer_ = true;
42563        this.currentTimeline_ = segmentInfo.timeline;
42564
42565        // We must update the syncinfo to recalculate the seekable range before
42566        // the following conditional otherwise it may consider this a bad "guess"
42567        // and attempt to resync when the post-update seekable window and live
42568        // point would mean that this was the perfect segment to fetch
42569        this.trigger('syncinfoupdate');
42570
42571        // If we previously appended a segment that ends more than 3 targetDurations before
42572        // the currentTime_ that means that our conservative guess was too conservative.
42573        // In that case, reset the loader state so that we try to use any information gained
42574        // from the previous request to create a new, more accurate, sync-point.
42575        if (segment.end && this.currentTime_() - segment.end > segmentInfo.playlist.targetDuration * 3) {
42576          this.resetEverything();
42577          return;
42578        }
42579
42580        // Don't do a rendition switch unless we have enough time to get a sync segment
42581        // and conservatively guess
42582        if (isWalkingForward) {
42583          this.trigger('bandwidthupdate');
42584        }
42585        this.trigger('progress');
42586
42587        // any time an update finishes and the last segment is in the
42588        // buffer, end the stream. this ensures the "ended" event will
42589        // fire if playback reaches that point.
42590        var isEndOfStream = detectEndOfStream(segmentInfo.playlist, this.mediaSource_, segmentInfo.mediaIndex + 1);
42591
42592        if (isEndOfStream) {
42593          this.endOfStream();
42594        }
42595
42596        if (!this.paused()) {
42597          this.monitorBuffer_();
42598        }
42599      }
42600
42601      /**
42602       * Records the current throughput of the decrypt, transmux, and append
42603       * portion of the semgment pipeline. `throughput.rate` is a the cumulative
42604       * moving average of the throughput. `throughput.count` is the number of
42605       * data points in the average.
42606       *
42607       * @private
42608       * @param {Object} segmentInfo the object returned by loadSegment
42609       */
42610
42611    }, {
42612      key: 'recordThroughput_',
42613      value: function recordThroughput_(segmentInfo) {
42614        var rate = this.throughput.rate;
42615        // Add one to the time to ensure that we don't accidentally attempt to divide
42616        // by zero in the case where the throughput is ridiculously high
42617        var segmentProcessingTime = Date.now() - segmentInfo.endOfAllRequests + 1;
42618        // Multiply by 8000 to convert from bytes/millisecond to bits/second
42619        var segmentProcessingThroughput = Math.floor(segmentInfo.byteLength / segmentProcessingTime * 8 * 1000);
42620
42621        // This is just a cumulative moving average calculation:
42622        //   newAvg = oldAvg + (sample - oldAvg) / (sampleCount + 1)
42623        this.throughput.rate += (segmentProcessingThroughput - rate) / ++this.throughput.count;
42624      }
42625
42626      /**
42627       * Adds a cue to the segment-metadata track with some metadata information about the
42628       * segment
42629       *
42630       * @private
42631       * @param {Object} segmentInfo
42632       *        the object returned by loadSegment
42633       * @method addSegmentMetadataCue_
42634       */
42635
42636    }, {
42637      key: 'addSegmentMetadataCue_',
42638      value: function addSegmentMetadataCue_(segmentInfo) {
42639        if (!this.segmentMetadataTrack_) {
42640          return;
42641        }
42642
42643        var segment = segmentInfo.segment;
42644        var start = segment.start;
42645        var end = segment.end;
42646
42647        // Do not try adding the cue if the start and end times are invalid.
42648        if (!finite(start) || !finite(end)) {
42649          return;
42650        }
42651
42652        removeCuesFromTrack(start, end, this.segmentMetadataTrack_);
42653
42654        var Cue = window_1.WebKitDataCue || window_1.VTTCue;
42655        var value = {
42656          bandwidth: segmentInfo.playlist.attributes.BANDWIDTH,
42657          resolution: segmentInfo.playlist.attributes.RESOLUTION,
42658          codecs: segmentInfo.playlist.attributes.CODECS,
42659          byteLength: segmentInfo.byteLength,
42660          uri: segmentInfo.uri,
42661          timeline: segmentInfo.timeline,
42662          playlist: segmentInfo.playlist.uri,
42663          start: start,
42664          end: end
42665        };
42666        var data = JSON.stringify(value);
42667        var cue = new Cue(start, end, data);
42668
42669        // Attach the metadata to the value property of the cue to keep consistency between
42670        // the differences of WebKitDataCue in safari and VTTCue in other browsers
42671        cue.value = value;
42672
42673        this.segmentMetadataTrack_.addCue(cue);
42674      }
42675    }]);
42676    return SegmentLoader;
42677  }(videojs$1.EventTarget);
42678
42679  /**
42680   * @file vtt-segment-loader.js
42681   */
42682
42683  var VTT_LINE_TERMINATORS = new Uint8Array('\n\n'.split('').map(function (char) {
42684    return char.charCodeAt(0);
42685  }));
42686
42687  var uintToString = function uintToString(uintArray) {
42688    return String.fromCharCode.apply(null, uintArray);
42689  };
42690
42691  /**
42692   * An object that manages segment loading and appending.
42693   *
42694   * @class VTTSegmentLoader
42695   * @param {Object} options required and optional options
42696   * @extends videojs.EventTarget
42697   */
42698
42699  var VTTSegmentLoader = function (_SegmentLoader) {
42700    inherits$3(VTTSegmentLoader, _SegmentLoader);
42701
42702    function VTTSegmentLoader(settings) {
42703      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
42704      classCallCheck$3(this, VTTSegmentLoader);
42705
42706      // SegmentLoader requires a MediaSource be specified or it will throw an error;
42707      // however, VTTSegmentLoader has no need of a media source, so delete the reference
42708      var _this = possibleConstructorReturn$3(this, (VTTSegmentLoader.__proto__ || Object.getPrototypeOf(VTTSegmentLoader)).call(this, settings, options));
42709
42710      _this.mediaSource_ = null;
42711
42712      _this.subtitlesTrack_ = null;
42713      return _this;
42714    }
42715
42716    /**
42717     * Indicates which time ranges are buffered
42718     *
42719     * @return {TimeRange}
42720     *         TimeRange object representing the current buffered ranges
42721     */
42722
42723    createClass$2(VTTSegmentLoader, [{
42724      key: 'buffered_',
42725      value: function buffered_() {
42726        if (!this.subtitlesTrack_ || !this.subtitlesTrack_.cues.length) {
42727          return videojs$1.createTimeRanges();
42728        }
42729
42730        var cues = this.subtitlesTrack_.cues;
42731        var start = cues[0].startTime;
42732        var end = cues[cues.length - 1].startTime;
42733
42734        return videojs$1.createTimeRanges([[start, end]]);
42735      }
42736
42737      /**
42738       * Gets and sets init segment for the provided map
42739       *
42740       * @param {Object} map
42741       *        The map object representing the init segment to get or set
42742       * @param {Boolean=} set
42743       *        If true, the init segment for the provided map should be saved
42744       * @return {Object}
42745       *         map object for desired init segment
42746       */
42747
42748    }, {
42749      key: 'initSegment',
42750      value: function initSegment(map) {
42751        var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
42752
42753        if (!map) {
42754          return null;
42755        }
42756
42757        var id = initSegmentId(map);
42758        var storedMap = this.initSegments_[id];
42759
42760        if (set$$1 && !storedMap && map.bytes) {
42761          // append WebVTT line terminators to the media initialization segment if it exists
42762          // to follow the WebVTT spec (https://w3c.github.io/webvtt/#file-structure) that
42763          // requires two or more WebVTT line terminators between the WebVTT header and the
42764          // rest of the file
42765          var combinedByteLength = VTT_LINE_TERMINATORS.byteLength + map.bytes.byteLength;
42766          var combinedSegment = new Uint8Array(combinedByteLength);
42767
42768          combinedSegment.set(map.bytes);
42769          combinedSegment.set(VTT_LINE_TERMINATORS, map.bytes.byteLength);
42770
42771          this.initSegments_[id] = storedMap = {
42772            resolvedUri: map.resolvedUri,
42773            byterange: map.byterange,
42774            bytes: combinedSegment
42775          };
42776        }
42777
42778        return storedMap || map;
42779      }
42780
42781      /**
42782       * Returns true if all configuration required for loading is present, otherwise false.
42783       *
42784       * @return {Boolean} True if the all configuration is ready for loading
42785       * @private
42786       */
42787
42788    }, {
42789      key: 'couldBeginLoading_',
42790      value: function couldBeginLoading_() {
42791        return this.playlist_ && this.subtitlesTrack_ && !this.paused();
42792      }
42793
42794      /**
42795       * Once all the starting parameters have been specified, begin
42796       * operation. This method should only be invoked from the INIT
42797       * state.
42798       *
42799       * @private
42800       */
42801
42802    }, {
42803      key: 'init_',
42804      value: function init_() {
42805        this.state = 'READY';
42806        this.resetEverything();
42807        return this.monitorBuffer_();
42808      }
42809
42810      /**
42811       * Set a subtitle track on the segment loader to add subtitles to
42812       *
42813       * @param {TextTrack=} track
42814       *        The text track to add loaded subtitles to
42815       * @return {TextTrack}
42816       *        Returns the subtitles track
42817       */
42818
42819    }, {
42820      key: 'track',
42821      value: function track(_track) {
42822        if (typeof _track === 'undefined') {
42823          return this.subtitlesTrack_;
42824        }
42825
42826        this.subtitlesTrack_ = _track;
42827
42828        // if we were unpaused but waiting for a sourceUpdater, start
42829        // buffering now
42830        if (this.state === 'INIT' && this.couldBeginLoading_()) {
42831          this.init_();
42832        }
42833
42834        return this.subtitlesTrack_;
42835      }
42836
42837      /**
42838       * Remove any data in the source buffer between start and end times
42839       * @param {Number} start - the start time of the region to remove from the buffer
42840       * @param {Number} end - the end time of the region to remove from the buffer
42841       */
42842
42843    }, {
42844      key: 'remove',
42845      value: function remove(start, end) {
42846        removeCuesFromTrack(start, end, this.subtitlesTrack_);
42847      }
42848
42849      /**
42850       * fill the buffer with segements unless the sourceBuffers are
42851       * currently updating
42852       *
42853       * Note: this function should only ever be called by monitorBuffer_
42854       * and never directly
42855       *
42856       * @private
42857       */
42858
42859    }, {
42860      key: 'fillBuffer_',
42861      value: function fillBuffer_() {
42862        var _this2 = this;
42863
42864        if (!this.syncPoint_) {
42865          this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_());
42866        }
42867
42868        // see if we need to begin loading immediately
42869        var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_);
42870
42871        segmentInfo = this.skipEmptySegments_(segmentInfo);
42872
42873        if (!segmentInfo) {
42874          return;
42875        }
42876
42877        if (this.syncController_.timestampOffsetForTimeline(segmentInfo.timeline) === null) {
42878          // We don't have the timestamp offset that we need to sync subtitles.
42879          // Rerun on a timestamp offset or user interaction.
42880          var checkTimestampOffset = function checkTimestampOffset() {
42881            _this2.state = 'READY';
42882            if (!_this2.paused()) {
42883              // if not paused, queue a buffer check as soon as possible
42884              _this2.monitorBuffer_();
42885            }
42886          };
42887
42888          this.syncController_.one('timestampoffset', checkTimestampOffset);
42889          this.state = 'WAITING_ON_TIMELINE';
42890          return;
42891        }
42892
42893        this.loadSegment_(segmentInfo);
42894      }
42895
42896      /**
42897       * Prevents the segment loader from requesting segments we know contain no subtitles
42898       * by walking forward until we find the next segment that we don't know whether it is
42899       * empty or not.
42900       *
42901       * @param {Object} segmentInfo
42902       *        a segment info object that describes the current segment
42903       * @return {Object}
42904       *         a segment info object that describes the current segment
42905       */
42906
42907    }, {
42908      key: 'skipEmptySegments_',
42909      value: function skipEmptySegments_(segmentInfo) {
42910        while (segmentInfo && segmentInfo.segment.empty) {
42911          segmentInfo = this.generateSegmentInfo_(segmentInfo.playlist, segmentInfo.mediaIndex + 1, segmentInfo.startOfSegment + segmentInfo.duration, segmentInfo.isSyncRequest);
42912        }
42913        return segmentInfo;
42914      }
42915
42916      /**
42917       * append a decrypted segement to the SourceBuffer through a SourceUpdater
42918       *
42919       * @private
42920       */
42921
42922    }, {
42923      key: 'handleSegment_',
42924      value: function handleSegment_() {
42925        var _this3 = this;
42926
42927        if (!this.pendingSegment_ || !this.subtitlesTrack_) {
42928          this.state = 'READY';
42929          return;
42930        }
42931
42932        this.state = 'APPENDING';
42933
42934        var segmentInfo = this.pendingSegment_;
42935        var segment = segmentInfo.segment;
42936
42937        // Make sure that vttjs has loaded, otherwise, wait till it finished loading
42938        if (typeof window_1.WebVTT !== 'function' && this.subtitlesTrack_ && this.subtitlesTrack_.tech_) {
42939
42940          var loadHandler = function loadHandler() {
42941            _this3.handleSegment_();
42942          };
42943
42944          this.state = 'WAITING_ON_VTTJS';
42945          this.subtitlesTrack_.tech_.one('vttjsloaded', loadHandler);
42946          this.subtitlesTrack_.tech_.one('vttjserror', function () {
42947            _this3.subtitlesTrack_.tech_.off('vttjsloaded', loadHandler);
42948            _this3.error({
42949              message: 'Error loading vtt.js'
42950            });
42951            _this3.state = 'READY';
42952            _this3.pause();
42953            _this3.trigger('error');
42954          });
42955
42956          return;
42957        }
42958
42959        segment.requested = true;
42960
42961        try {
42962          this.parseVTTCues_(segmentInfo);
42963        } catch (e) {
42964          this.error({
42965            message: e.message
42966          });
42967          this.state = 'READY';
42968          this.pause();
42969          return this.trigger('error');
42970        }
42971
42972        this.updateTimeMapping_(segmentInfo, this.syncController_.timelines[segmentInfo.timeline], this.playlist_);
42973
42974        if (segmentInfo.isSyncRequest) {
42975          this.trigger('syncinfoupdate');
42976          this.pendingSegment_ = null;
42977          this.state = 'READY';
42978          return;
42979        }
42980
42981        segmentInfo.byteLength = segmentInfo.bytes.byteLength;
42982
42983        this.mediaSecondsLoaded += segment.duration;
42984
42985        if (segmentInfo.cues.length) {
42986          // remove any overlapping cues to prevent doubling
42987          this.remove(segmentInfo.cues[0].endTime, segmentInfo.cues[segmentInfo.cues.length - 1].endTime);
42988        }
42989
42990        segmentInfo.cues.forEach(function (cue) {
42991          _this3.subtitlesTrack_.addCue(cue);
42992        });
42993
42994        this.handleUpdateEnd_();
42995      }
42996
42997      /**
42998       * Uses the WebVTT parser to parse the segment response
42999       *
43000       * @param {Object} segmentInfo
43001       *        a segment info object that describes the current segment
43002       * @private
43003       */
43004
43005    }, {
43006      key: 'parseVTTCues_',
43007      value: function parseVTTCues_(segmentInfo) {
43008        var decoder = void 0;
43009        var decodeBytesToString = false;
43010
43011        if (typeof window_1.TextDecoder === 'function') {
43012          decoder = new window_1.TextDecoder('utf8');
43013        } else {
43014          decoder = window_1.WebVTT.StringDecoder();
43015          decodeBytesToString = true;
43016        }
43017
43018        var parser = new window_1.WebVTT.Parser(window_1, window_1.vttjs, decoder);
43019
43020        segmentInfo.cues = [];
43021        segmentInfo.timestampmap = { MPEGTS: 0, LOCAL: 0 };
43022
43023        parser.oncue = segmentInfo.cues.push.bind(segmentInfo.cues);
43024        parser.ontimestampmap = function (map) {
43025          return segmentInfo.timestampmap = map;
43026        };
43027        parser.onparsingerror = function (error) {
43028          videojs$1.log.warn('Error encountered when parsing cues: ' + error.message);
43029        };
43030
43031        if (segmentInfo.segment.map) {
43032          var mapData = segmentInfo.segment.map.bytes;
43033
43034          if (decodeBytesToString) {
43035            mapData = uintToString(mapData);
43036          }
43037
43038          parser.parse(mapData);
43039        }
43040
43041        var segmentData = segmentInfo.bytes;
43042
43043        if (decodeBytesToString) {
43044          segmentData = uintToString(segmentData);
43045        }
43046
43047        parser.parse(segmentData);
43048        parser.flush();
43049      }
43050
43051      /**
43052       * Updates the start and end times of any cues parsed by the WebVTT parser using
43053       * the information parsed from the X-TIMESTAMP-MAP header and a TS to media time mapping
43054       * from the SyncController
43055       *
43056       * @param {Object} segmentInfo
43057       *        a segment info object that describes the current segment
43058       * @param {Object} mappingObj
43059       *        object containing a mapping from TS to media time
43060       * @param {Object} playlist
43061       *        the playlist object containing the segment
43062       * @private
43063       */
43064
43065    }, {
43066      key: 'updateTimeMapping_',
43067      value: function updateTimeMapping_(segmentInfo, mappingObj, playlist) {
43068        var segment = segmentInfo.segment;
43069
43070        if (!mappingObj) {
43071          // If the sync controller does not have a mapping of TS to Media Time for the
43072          // timeline, then we don't have enough information to update the cue
43073          // start/end times
43074          return;
43075        }
43076
43077        if (!segmentInfo.cues.length) {
43078          // If there are no cues, we also do not have enough information to figure out
43079          // segment timing. Mark that the segment contains no cues so we don't re-request
43080          // an empty segment.
43081          segment.empty = true;
43082          return;
43083        }
43084
43085        var timestampmap = segmentInfo.timestampmap;
43086        var diff = timestampmap.MPEGTS / 90000 - timestampmap.LOCAL + mappingObj.mapping;
43087
43088        segmentInfo.cues.forEach(function (cue) {
43089          // First convert cue time to TS time using the timestamp-map provided within the vtt
43090          cue.startTime += diff;
43091          cue.endTime += diff;
43092        });
43093
43094        if (!playlist.syncInfo) {
43095          var firstStart = segmentInfo.cues[0].startTime;
43096          var lastStart = segmentInfo.cues[segmentInfo.cues.length - 1].startTime;
43097
43098          playlist.syncInfo = {
43099            mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex,
43100            time: Math.min(firstStart, lastStart - segment.duration)
43101          };
43102        }
43103      }
43104    }]);
43105    return VTTSegmentLoader;
43106  }(SegmentLoader);
43107
43108  /**
43109   * @file ad-cue-tags.js
43110   */
43111
43112  /**
43113   * Searches for an ad cue that overlaps with the given mediaTime
43114   */
43115  var findAdCue = function findAdCue(track, mediaTime) {
43116    var cues = track.cues;
43117
43118    for (var i = 0; i < cues.length; i++) {
43119      var cue = cues[i];
43120
43121      if (mediaTime >= cue.adStartTime && mediaTime <= cue.adEndTime) {
43122        return cue;
43123      }
43124    }
43125    return null;
43126  };
43127
43128  var updateAdCues = function updateAdCues(media, track) {
43129    var offset = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
43130
43131    if (!media.segments) {
43132      return;
43133    }
43134
43135    var mediaTime = offset;
43136    var cue = void 0;
43137
43138    for (var i = 0; i < media.segments.length; i++) {
43139      var segment = media.segments[i];
43140
43141      if (!cue) {
43142        // Since the cues will span for at least the segment duration, adding a fudge
43143        // factor of half segment duration will prevent duplicate cues from being
43144        // created when timing info is not exact (e.g. cue start time initialized
43145        // at 10.006677, but next call mediaTime is 10.003332 )
43146        cue = findAdCue(track, mediaTime + segment.duration / 2);
43147      }
43148
43149      if (cue) {
43150        if ('cueIn' in segment) {
43151          // Found a CUE-IN so end the cue
43152          cue.endTime = mediaTime;
43153          cue.adEndTime = mediaTime;
43154          mediaTime += segment.duration;
43155          cue = null;
43156          continue;
43157        }
43158
43159        if (mediaTime < cue.endTime) {
43160          // Already processed this mediaTime for this cue
43161          mediaTime += segment.duration;
43162          continue;
43163        }
43164
43165        // otherwise extend cue until a CUE-IN is found
43166        cue.endTime += segment.duration;
43167      } else {
43168        if ('cueOut' in segment) {
43169          cue = new window_1.VTTCue(mediaTime, mediaTime + segment.duration, segment.cueOut);
43170          cue.adStartTime = mediaTime;
43171          // Assumes tag format to be
43172          // #EXT-X-CUE-OUT:30
43173          cue.adEndTime = mediaTime + parseFloat(segment.cueOut);
43174          track.addCue(cue);
43175        }
43176
43177        if ('cueOutCont' in segment) {
43178          // Entered into the middle of an ad cue
43179          var adOffset = void 0;
43180          var adTotal = void 0;
43181
43182          // Assumes tag formate to be
43183          // #EXT-X-CUE-OUT-CONT:10/30
43184
43185          var _segment$cueOutCont$s = segment.cueOutCont.split('/').map(parseFloat);
43186
43187          var _segment$cueOutCont$s2 = slicedToArray$1(_segment$cueOutCont$s, 2);
43188
43189          adOffset = _segment$cueOutCont$s2[0];
43190          adTotal = _segment$cueOutCont$s2[1];
43191
43192          cue = new window_1.VTTCue(mediaTime, mediaTime + segment.duration, '');
43193          cue.adStartTime = mediaTime - adOffset;
43194          cue.adEndTime = cue.adStartTime + adTotal;
43195          track.addCue(cue);
43196        }
43197      }
43198      mediaTime += segment.duration;
43199    }
43200  };
43201
43202  /**
43203   * @file sync-controller.js
43204   */
43205
43206  var tsprobe = tsInspector.inspect;
43207
43208  var syncPointStrategies = [
43209  // Stategy "VOD": Handle the VOD-case where the sync-point is *always*
43210  //                the equivalence display-time 0 === segment-index 0
43211  {
43212    name: 'VOD',
43213    run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
43214      if (duration$$1 !== Infinity) {
43215        var syncPoint = {
43216          time: 0,
43217          segmentIndex: 0
43218        };
43219
43220        return syncPoint;
43221      }
43222      return null;
43223    }
43224  },
43225  // Stategy "ProgramDateTime": We have a program-date-time tag in this playlist
43226  {
43227    name: 'ProgramDateTime',
43228    run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
43229      if (!syncController.datetimeToDisplayTime) {
43230        return null;
43231      }
43232
43233      var segments = playlist.segments || [];
43234      var syncPoint = null;
43235      var lastDistance = null;
43236
43237      currentTime = currentTime || 0;
43238
43239      for (var i = 0; i < segments.length; i++) {
43240        var segment = segments[i];
43241
43242        if (segment.dateTimeObject) {
43243          var segmentTime = segment.dateTimeObject.getTime() / 1000;
43244          var segmentStart = segmentTime + syncController.datetimeToDisplayTime;
43245          var distance = Math.abs(currentTime - segmentStart);
43246
43247          // Once the distance begins to increase, we have passed
43248          // currentTime and can stop looking for better candidates
43249          if (lastDistance !== null && lastDistance < distance) {
43250            break;
43251          }
43252
43253          lastDistance = distance;
43254          syncPoint = {
43255            time: segmentStart,
43256            segmentIndex: i
43257          };
43258        }
43259      }
43260      return syncPoint;
43261    }
43262  },
43263  // Stategy "Segment": We have a known time mapping for a timeline and a
43264  //                    segment in the current timeline with timing data
43265  {
43266    name: 'Segment',
43267    run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
43268      var segments = playlist.segments || [];
43269      var syncPoint = null;
43270      var lastDistance = null;
43271
43272      currentTime = currentTime || 0;
43273
43274      for (var i = 0; i < segments.length; i++) {
43275        var segment = segments[i];
43276
43277        if (segment.timeline === currentTimeline && typeof segment.start !== 'undefined') {
43278          var distance = Math.abs(currentTime - segment.start);
43279
43280          // Once the distance begins to increase, we have passed
43281          // currentTime and can stop looking for better candidates
43282          if (lastDistance !== null && lastDistance < distance) {
43283            break;
43284          }
43285
43286          if (!syncPoint || lastDistance === null || lastDistance >= distance) {
43287            lastDistance = distance;
43288            syncPoint = {
43289              time: segment.start,
43290              segmentIndex: i
43291            };
43292          }
43293        }
43294      }
43295      return syncPoint;
43296    }
43297  },
43298  // Stategy "Discontinuity": We have a discontinuity with a known
43299  //                          display-time
43300  {
43301    name: 'Discontinuity',
43302    run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
43303      var syncPoint = null;
43304
43305      currentTime = currentTime || 0;
43306
43307      if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
43308        var lastDistance = null;
43309
43310        for (var i = 0; i < playlist.discontinuityStarts.length; i++) {
43311          var segmentIndex = playlist.discontinuityStarts[i];
43312          var discontinuity = playlist.discontinuitySequence + i + 1;
43313          var discontinuitySync = syncController.discontinuities[discontinuity];
43314
43315          if (discontinuitySync) {
43316            var distance = Math.abs(currentTime - discontinuitySync.time);
43317
43318            // Once the distance begins to increase, we have passed
43319            // currentTime and can stop looking for better candidates
43320            if (lastDistance !== null && lastDistance < distance) {
43321              break;
43322            }
43323
43324            if (!syncPoint || lastDistance === null || lastDistance >= distance) {
43325              lastDistance = distance;
43326              syncPoint = {
43327                time: discontinuitySync.time,
43328                segmentIndex: segmentIndex
43329              };
43330            }
43331          }
43332        }
43333      }
43334      return syncPoint;
43335    }
43336  },
43337  // Stategy "Playlist": We have a playlist with a known mapping of
43338  //                     segment index to display time
43339  {
43340    name: 'Playlist',
43341    run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
43342      if (playlist.syncInfo) {
43343        var syncPoint = {
43344          time: playlist.syncInfo.time,
43345          segmentIndex: playlist.syncInfo.mediaSequence - playlist.mediaSequence
43346        };
43347
43348        return syncPoint;
43349      }
43350      return null;
43351    }
43352  }];
43353
43354  var SyncController = function (_videojs$EventTarget) {
43355    inherits$3(SyncController, _videojs$EventTarget);
43356
43357    function SyncController() {
43358      classCallCheck$3(this, SyncController);
43359
43360      // Segment Loader state variables...
43361      // ...for synching across variants
43362      var _this = possibleConstructorReturn$3(this, (SyncController.__proto__ || Object.getPrototypeOf(SyncController)).call(this));
43363
43364      _this.inspectCache_ = undefined;
43365
43366      // ...for synching across variants
43367      _this.timelines = [];
43368      _this.discontinuities = [];
43369      _this.datetimeToDisplayTime = null;
43370
43371      _this.logger_ = logger('SyncController');
43372      return _this;
43373    }
43374
43375    /**
43376     * Find a sync-point for the playlist specified
43377     *
43378     * A sync-point is defined as a known mapping from display-time to
43379     * a segment-index in the current playlist.
43380     *
43381     * @param {Playlist} playlist
43382     *        The playlist that needs a sync-point
43383     * @param {Number} duration
43384     *        Duration of the MediaSource (Infinite if playing a live source)
43385     * @param {Number} currentTimeline
43386     *        The last timeline from which a segment was loaded
43387     * @returns {Object}
43388     *          A sync-point object
43389     */
43390
43391    createClass$2(SyncController, [{
43392      key: 'getSyncPoint',
43393      value: function getSyncPoint(playlist, duration$$1, currentTimeline, currentTime) {
43394        var syncPoints = this.runStrategies_(playlist, duration$$1, currentTimeline, currentTime);
43395
43396        if (!syncPoints.length) {
43397          // Signal that we need to attempt to get a sync-point manually
43398          // by fetching a segment in the playlist and constructing
43399          // a sync-point from that information
43400          return null;
43401        }
43402
43403        // Now find the sync-point that is closest to the currentTime because
43404        // that should result in the most accurate guess about which segment
43405        // to fetch
43406        return this.selectSyncPoint_(syncPoints, { key: 'time', value: currentTime });
43407      }
43408
43409      /**
43410       * Calculate the amount of time that has expired off the playlist during playback
43411       *
43412       * @param {Playlist} playlist
43413       *        Playlist object to calculate expired from
43414       * @param {Number} duration
43415       *        Duration of the MediaSource (Infinity if playling a live source)
43416       * @returns {Number|null}
43417       *          The amount of time that has expired off the playlist during playback. Null
43418       *          if no sync-points for the playlist can be found.
43419       */
43420
43421    }, {
43422      key: 'getExpiredTime',
43423      value: function getExpiredTime(playlist, duration$$1) {
43424        if (!playlist || !playlist.segments) {
43425          return null;
43426        }
43427
43428        var syncPoints = this.runStrategies_(playlist, duration$$1, playlist.discontinuitySequence, 0);
43429
43430        // Without sync-points, there is not enough information to determine the expired time
43431        if (!syncPoints.length) {
43432          return null;
43433        }
43434
43435        var syncPoint = this.selectSyncPoint_(syncPoints, {
43436          key: 'segmentIndex',
43437          value: 0
43438        });
43439
43440        // If the sync-point is beyond the start of the playlist, we want to subtract the
43441        // duration from index 0 to syncPoint.segmentIndex instead of adding.
43442        if (syncPoint.segmentIndex > 0) {
43443          syncPoint.time *= -1;
43444        }
43445
43446        return Math.abs(syncPoint.time + sumDurations(playlist, syncPoint.segmentIndex, 0));
43447      }
43448
43449      /**
43450       * Runs each sync-point strategy and returns a list of sync-points returned by the
43451       * strategies
43452       *
43453       * @private
43454       * @param {Playlist} playlist
43455       *        The playlist that needs a sync-point
43456       * @param {Number} duration
43457       *        Duration of the MediaSource (Infinity if playing a live source)
43458       * @param {Number} currentTimeline
43459       *        The last timeline from which a segment was loaded
43460       * @returns {Array}
43461       *          A list of sync-point objects
43462       */
43463
43464    }, {
43465      key: 'runStrategies_',
43466      value: function runStrategies_(playlist, duration$$1, currentTimeline, currentTime) {
43467        var syncPoints = [];
43468
43469        // Try to find a sync-point in by utilizing various strategies...
43470        for (var i = 0; i < syncPointStrategies.length; i++) {
43471          var strategy = syncPointStrategies[i];
43472          var syncPoint = strategy.run(this, playlist, duration$$1, currentTimeline, currentTime);
43473
43474          if (syncPoint) {
43475            syncPoint.strategy = strategy.name;
43476            syncPoints.push({
43477              strategy: strategy.name,
43478              syncPoint: syncPoint
43479            });
43480          }
43481        }
43482
43483        return syncPoints;
43484      }
43485
43486      /**
43487       * Selects the sync-point nearest the specified target
43488       *
43489       * @private
43490       * @param {Array} syncPoints
43491       *        List of sync-points to select from
43492       * @param {Object} target
43493       *        Object specifying the property and value we are targeting
43494       * @param {String} target.key
43495       *        Specifies the property to target. Must be either 'time' or 'segmentIndex'
43496       * @param {Number} target.value
43497       *        The value to target for the specified key.
43498       * @returns {Object}
43499       *          The sync-point nearest the target
43500       */
43501
43502    }, {
43503      key: 'selectSyncPoint_',
43504      value: function selectSyncPoint_(syncPoints, target) {
43505        var bestSyncPoint = syncPoints[0].syncPoint;
43506        var bestDistance = Math.abs(syncPoints[0].syncPoint[target.key] - target.value);
43507        var bestStrategy = syncPoints[0].strategy;
43508
43509        for (var i = 1; i < syncPoints.length; i++) {
43510          var newDistance = Math.abs(syncPoints[i].syncPoint[target.key] - target.value);
43511
43512          if (newDistance < bestDistance) {
43513            bestDistance = newDistance;
43514            bestSyncPoint = syncPoints[i].syncPoint;
43515            bestStrategy = syncPoints[i].strategy;
43516          }
43517        }
43518
43519        this.logger_('syncPoint for [' + target.key + ': ' + target.value + '] chosen with strategy' + (' [' + bestStrategy + ']: [time:' + bestSyncPoint.time + ',') + (' segmentIndex:' + bestSyncPoint.segmentIndex + ']'));
43520
43521        return bestSyncPoint;
43522      }
43523
43524      /**
43525       * Save any meta-data present on the segments when segments leave
43526       * the live window to the playlist to allow for synchronization at the
43527       * playlist level later.
43528       *
43529       * @param {Playlist} oldPlaylist - The previous active playlist
43530       * @param {Playlist} newPlaylist - The updated and most current playlist
43531       */
43532
43533    }, {
43534      key: 'saveExpiredSegmentInfo',
43535      value: function saveExpiredSegmentInfo(oldPlaylist, newPlaylist) {
43536        var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence;
43537
43538        // When a segment expires from the playlist and it has a start time
43539        // save that information as a possible sync-point reference in future
43540        for (var i = mediaSequenceDiff - 1; i >= 0; i--) {
43541          var lastRemovedSegment = oldPlaylist.segments[i];
43542
43543          if (lastRemovedSegment && typeof lastRemovedSegment.start !== 'undefined') {
43544            newPlaylist.syncInfo = {
43545              mediaSequence: oldPlaylist.mediaSequence + i,
43546              time: lastRemovedSegment.start
43547            };
43548            this.logger_('playlist refresh sync: [time:' + newPlaylist.syncInfo.time + ',' + (' mediaSequence: ' + newPlaylist.syncInfo.mediaSequence + ']'));
43549            this.trigger('syncinfoupdate');
43550            break;
43551          }
43552        }
43553      }
43554
43555      /**
43556       * Save the mapping from playlist's ProgramDateTime to display. This should
43557       * only ever happen once at the start of playback.
43558       *
43559       * @param {Playlist} playlist - The currently active playlist
43560       */
43561
43562    }, {
43563      key: 'setDateTimeMapping',
43564      value: function setDateTimeMapping(playlist) {
43565        if (!this.datetimeToDisplayTime && playlist.segments && playlist.segments.length && playlist.segments[0].dateTimeObject) {
43566          var playlistTimestamp = playlist.segments[0].dateTimeObject.getTime() / 1000;
43567
43568          this.datetimeToDisplayTime = -playlistTimestamp;
43569        }
43570      }
43571
43572      /**
43573       * Reset the state of the inspection cache when we do a rendition
43574       * switch
43575       */
43576
43577    }, {
43578      key: 'reset',
43579      value: function reset() {
43580        this.inspectCache_ = undefined;
43581      }
43582
43583      /**
43584       * Probe or inspect a fmp4 or an mpeg2-ts segment to determine the start
43585       * and end of the segment in it's internal "media time". Used to generate
43586       * mappings from that internal "media time" to the display time that is
43587       * shown on the player.
43588       *
43589       * @param {SegmentInfo} segmentInfo - The current active request information
43590       */
43591
43592    }, {
43593      key: 'probeSegmentInfo',
43594      value: function probeSegmentInfo(segmentInfo) {
43595        var segment = segmentInfo.segment;
43596        var playlist = segmentInfo.playlist;
43597        var timingInfo = void 0;
43598
43599        if (segment.map) {
43600          timingInfo = this.probeMp4Segment_(segmentInfo);
43601        } else {
43602          timingInfo = this.probeTsSegment_(segmentInfo);
43603        }
43604
43605        if (timingInfo) {
43606          if (this.calculateSegmentTimeMapping_(segmentInfo, timingInfo)) {
43607            this.saveDiscontinuitySyncInfo_(segmentInfo);
43608
43609            // If the playlist does not have sync information yet, record that information
43610            // now with segment timing information
43611            if (!playlist.syncInfo) {
43612              playlist.syncInfo = {
43613                mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex,
43614                time: segment.start
43615              };
43616            }
43617          }
43618        }
43619
43620        return timingInfo;
43621      }
43622
43623      /**
43624       * Probe an fmp4 or an mpeg2-ts segment to determine the start of the segment
43625       * in it's internal "media time".
43626       *
43627       * @private
43628       * @param {SegmentInfo} segmentInfo - The current active request information
43629       * @return {object} The start and end time of the current segment in "media time"
43630       */
43631
43632    }, {
43633      key: 'probeMp4Segment_',
43634      value: function probeMp4Segment_(segmentInfo) {
43635        var segment = segmentInfo.segment;
43636        var timescales = probe.timescale(segment.map.bytes);
43637        var startTime = probe.startTime(timescales, segmentInfo.bytes);
43638
43639        if (segmentInfo.timestampOffset !== null) {
43640          segmentInfo.timestampOffset -= startTime;
43641        }
43642
43643        return {
43644          start: startTime,
43645          end: startTime + segment.duration
43646        };
43647      }
43648
43649      /**
43650       * Probe an mpeg2-ts segment to determine the start and end of the segment
43651       * in it's internal "media time".
43652       *
43653       * @private
43654       * @param {SegmentInfo} segmentInfo - The current active request information
43655       * @return {object} The start and end time of the current segment in "media time"
43656       */
43657
43658    }, {
43659      key: 'probeTsSegment_',
43660      value: function probeTsSegment_(segmentInfo) {
43661        var timeInfo = tsprobe(segmentInfo.bytes, this.inspectCache_);
43662        var segmentStartTime = void 0;
43663        var segmentEndTime = void 0;
43664
43665        if (!timeInfo) {
43666          return null;
43667        }
43668
43669        if (timeInfo.video && timeInfo.video.length === 2) {
43670          this.inspectCache_ = timeInfo.video[1].dts;
43671          segmentStartTime = timeInfo.video[0].dtsTime;
43672          segmentEndTime = timeInfo.video[1].dtsTime;
43673        } else if (timeInfo.audio && timeInfo.audio.length === 2) {
43674          this.inspectCache_ = timeInfo.audio[1].dts;
43675          segmentStartTime = timeInfo.audio[0].dtsTime;
43676          segmentEndTime = timeInfo.audio[1].dtsTime;
43677        }
43678
43679        return {
43680          start: segmentStartTime,
43681          end: segmentEndTime,
43682          containsVideo: timeInfo.video && timeInfo.video.length === 2,
43683          containsAudio: timeInfo.audio && timeInfo.audio.length === 2
43684        };
43685      }
43686    }, {
43687      key: 'timestampOffsetForTimeline',
43688      value: function timestampOffsetForTimeline(timeline) {
43689        if (typeof this.timelines[timeline] === 'undefined') {
43690          return null;
43691        }
43692        return this.timelines[timeline].time;
43693      }
43694    }, {
43695      key: 'mappingForTimeline',
43696      value: function mappingForTimeline(timeline) {
43697        if (typeof this.timelines[timeline] === 'undefined') {
43698          return null;
43699        }
43700        return this.timelines[timeline].mapping;
43701      }
43702
43703      /**
43704       * Use the "media time" for a segment to generate a mapping to "display time" and
43705       * save that display time to the segment.
43706       *
43707       * @private
43708       * @param {SegmentInfo} segmentInfo
43709       *        The current active request information
43710       * @param {object} timingInfo
43711       *        The start and end time of the current segment in "media time"
43712       * @returns {Boolean}
43713       *          Returns false if segment time mapping could not be calculated
43714       */
43715
43716    }, {
43717      key: 'calculateSegmentTimeMapping_',
43718      value: function calculateSegmentTimeMapping_(segmentInfo, timingInfo) {
43719        var segment = segmentInfo.segment;
43720        var mappingObj = this.timelines[segmentInfo.timeline];
43721
43722        if (segmentInfo.timestampOffset !== null) {
43723          mappingObj = {
43724            time: segmentInfo.startOfSegment,
43725            mapping: segmentInfo.startOfSegment - timingInfo.start
43726          };
43727          this.timelines[segmentInfo.timeline] = mappingObj;
43728          this.trigger('timestampoffset');
43729
43730          this.logger_('time mapping for timeline ' + segmentInfo.timeline + ': ' + ('[time: ' + mappingObj.time + '] [mapping: ' + mappingObj.mapping + ']'));
43731
43732          segment.start = segmentInfo.startOfSegment;
43733          segment.end = timingInfo.end + mappingObj.mapping;
43734        } else if (mappingObj) {
43735          segment.start = timingInfo.start + mappingObj.mapping;
43736          segment.end = timingInfo.end + mappingObj.mapping;
43737        } else {
43738          return false;
43739        }
43740
43741        return true;
43742      }
43743
43744      /**
43745       * Each time we have discontinuity in the playlist, attempt to calculate the location
43746       * in display of the start of the discontinuity and save that. We also save an accuracy
43747       * value so that we save values with the most accuracy (closest to 0.)
43748       *
43749       * @private
43750       * @param {SegmentInfo} segmentInfo - The current active request information
43751       */
43752
43753    }, {
43754      key: 'saveDiscontinuitySyncInfo_',
43755      value: function saveDiscontinuitySyncInfo_(segmentInfo) {
43756        var playlist = segmentInfo.playlist;
43757        var segment = segmentInfo.segment;
43758
43759        // If the current segment is a discontinuity then we know exactly where
43760        // the start of the range and it's accuracy is 0 (greater accuracy values
43761        // mean more approximation)
43762        if (segment.discontinuity) {
43763          this.discontinuities[segment.timeline] = {
43764            time: segment.start,
43765            accuracy: 0
43766          };
43767        } else if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
43768          // Search for future discontinuities that we can provide better timing
43769          // information for and save that information for sync purposes
43770          for (var i = 0; i < playlist.discontinuityStarts.length; i++) {
43771            var segmentIndex = playlist.discontinuityStarts[i];
43772            var discontinuity = playlist.discontinuitySequence + i + 1;
43773            var mediaIndexDiff = segmentIndex - segmentInfo.mediaIndex;
43774            var accuracy = Math.abs(mediaIndexDiff);
43775
43776            if (!this.discontinuities[discontinuity] || this.discontinuities[discontinuity].accuracy > accuracy) {
43777              var time = void 0;
43778
43779              if (mediaIndexDiff < 0) {
43780                time = segment.start - sumDurations(playlist, segmentInfo.mediaIndex, segmentIndex);
43781              } else {
43782                time = segment.end + sumDurations(playlist, segmentInfo.mediaIndex + 1, segmentIndex);
43783              }
43784
43785              this.discontinuities[discontinuity] = {
43786                time: time,
43787                accuracy: accuracy
43788              };
43789            }
43790          }
43791        }
43792      }
43793    }]);
43794    return SyncController;
43795  }(videojs$1.EventTarget);
43796
43797  var Decrypter$1 = new shimWorker("./decrypter-worker.worker.js", function (window, document$$1) {
43798    var self = this;
43799    var decrypterWorker = function () {
43800
43801      var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
43802
43803      var win;
43804
43805      if (typeof window !== "undefined") {
43806        win = window;
43807      } else if (typeof commonjsGlobal !== "undefined") {
43808        win = commonjsGlobal;
43809      } else if (typeof self !== "undefined") {
43810        win = self;
43811      } else {
43812        win = {};
43813      }
43814
43815      var window_1$$1 = win;
43816
43817      /*
43818       * pkcs7.pad
43819       * https://github.com/brightcove/pkcs7
43820       *
43821       * Copyright (c) 2014 Brightcove
43822       * Licensed under the apache2 license.
43823       */
43824
43825      /**
43826       * Returns the subarray of a Uint8Array without PKCS#7 padding.
43827       * @param padded {Uint8Array} unencrypted bytes that have been padded
43828       * @return {Uint8Array} the unpadded bytes
43829       * @see http://tools.ietf.org/html/rfc5652
43830       */
43831      function unpad(padded) {
43832        return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]);
43833      }
43834
43835      var classCallCheck$$1 = function classCallCheck$$1(instance, Constructor) {
43836        if (!(instance instanceof Constructor)) {
43837          throw new TypeError("Cannot call a class as a function");
43838        }
43839      };
43840
43841      var createClass$$1 = function () {
43842        function defineProperties(target, props) {
43843          for (var i = 0; i < props.length; i++) {
43844            var descriptor = props[i];
43845            descriptor.enumerable = descriptor.enumerable || false;
43846            descriptor.configurable = true;
43847            if ("value" in descriptor) descriptor.writable = true;
43848            Object.defineProperty(target, descriptor.key, descriptor);
43849          }
43850        }
43851
43852        return function (Constructor, protoProps, staticProps) {
43853          if (protoProps) defineProperties(Constructor.prototype, protoProps);
43854          if (staticProps) defineProperties(Constructor, staticProps);
43855          return Constructor;
43856        };
43857      }();
43858
43859      var inherits$$1 = function inherits$$1(subClass, superClass) {
43860        if (typeof superClass !== "function" && superClass !== null) {
43861          throw new TypeError("Super expression must either be null or a function, not " + (typeof superClass === 'undefined' ? 'undefined' : _typeof(superClass)));
43862        }
43863
43864        subClass.prototype = Object.create(superClass && superClass.prototype, {
43865          constructor: {
43866            value: subClass,
43867            enumerable: false,
43868            writable: true,
43869            configurable: true
43870          }
43871        });
43872        if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
43873      };
43874
43875      var possibleConstructorReturn$$1 = function possibleConstructorReturn$$1(self, call) {
43876        if (!self) {
43877          throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
43878        }
43879
43880        return call && ((typeof call === 'undefined' ? 'undefined' : _typeof(call)) === "object" || typeof call === "function") ? call : self;
43881      };
43882
43883      /**
43884       * @file aes.js
43885       *
43886       * This file contains an adaptation of the AES decryption algorithm
43887       * from the Standford Javascript Cryptography Library. That work is
43888       * covered by the following copyright and permissions notice:
43889       *
43890       * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
43891       * All rights reserved.
43892       *
43893       * Redistribution and use in source and binary forms, with or without
43894       * modification, are permitted provided that the following conditions are
43895       * met:
43896       *
43897       * 1. Redistributions of source code must retain the above copyright
43898       *    notice, this list of conditions and the following disclaimer.
43899       *
43900       * 2. Redistributions in binary form must reproduce the above
43901       *    copyright notice, this list of conditions and the following
43902       *    disclaimer in the documentation and/or other materials provided
43903       *    with the distribution.
43904       *
43905       * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
43906       * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
43907       * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
43908       * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
43909       * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
43910       * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
43911       * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
43912       * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
43913       * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
43914       * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
43915       * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43916       *
43917       * The views and conclusions contained in the software and documentation
43918       * are those of the authors and should not be interpreted as representing
43919       * official policies, either expressed or implied, of the authors.
43920       */
43921
43922      /**
43923       * Expand the S-box tables.
43924       *
43925       * @private
43926       */
43927      var precompute = function precompute() {
43928        var tables = [[[], [], [], [], []], [[], [], [], [], []]];
43929        var encTable = tables[0];
43930        var decTable = tables[1];
43931        var sbox = encTable[4];
43932        var sboxInv = decTable[4];
43933        var i = void 0;
43934        var x = void 0;
43935        var xInv = void 0;
43936        var d = [];
43937        var th = [];
43938        var x2 = void 0;
43939        var x4 = void 0;
43940        var x8 = void 0;
43941        var s = void 0;
43942        var tEnc = void 0;
43943        var tDec = void 0;
43944
43945        // Compute double and third tables
43946        for (i = 0; i < 256; i++) {
43947          th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i;
43948        }
43949
43950        for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
43951          // Compute sbox
43952          s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4;
43953          s = s >> 8 ^ s & 255 ^ 99;
43954          sbox[x] = s;
43955          sboxInv[s] = x;
43956
43957          // Compute MixColumns
43958          x8 = d[x4 = d[x2 = d[x]]];
43959          tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
43960          tEnc = d[s] * 0x101 ^ s * 0x1010100;
43961
43962          for (i = 0; i < 4; i++) {
43963            encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8;
43964            decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8;
43965          }
43966        }
43967
43968        // Compactify. Considerable speedup on Firefox.
43969        for (i = 0; i < 5; i++) {
43970          encTable[i] = encTable[i].slice(0);
43971          decTable[i] = decTable[i].slice(0);
43972        }
43973        return tables;
43974      };
43975      var aesTables = null;
43976
43977      /**
43978       * Schedule out an AES key for both encryption and decryption. This
43979       * is a low-level class. Use a cipher mode to do bulk encryption.
43980       *
43981       * @class AES
43982       * @param key {Array} The key as an array of 4, 6 or 8 words.
43983       */
43984
43985      var AES = function () {
43986        function AES(key) {
43987          classCallCheck$$1(this, AES);
43988
43989          /**
43990           * The expanded S-box and inverse S-box tables. These will be computed
43991           * on the client so that we don't have to send them down the wire.
43992           *
43993           * There are two tables, _tables[0] is for encryption and
43994           * _tables[1] is for decryption.
43995           *
43996           * The first 4 sub-tables are the expanded S-box with MixColumns. The
43997           * last (_tables[01][4]) is the S-box itself.
43998           *
43999           * @private
44000           */
44001          // if we have yet to precompute the S-box tables
44002          // do so now
44003          if (!aesTables) {
44004            aesTables = precompute();
44005          }
44006          // then make a copy of that object for use
44007          this._tables = [[aesTables[0][0].slice(), aesTables[0][1].slice(), aesTables[0][2].slice(), aesTables[0][3].slice(), aesTables[0][4].slice()], [aesTables[1][0].slice(), aesTables[1][1].slice(), aesTables[1][2].slice(), aesTables[1][3].slice(), aesTables[1][4].slice()]];
44008          var i = void 0;
44009          var j = void 0;
44010          var tmp = void 0;
44011          var encKey = void 0;
44012          var decKey = void 0;
44013          var sbox = this._tables[0][4];
44014          var decTable = this._tables[1];
44015          var keyLen = key.length;
44016          var rcon = 1;
44017
44018          if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
44019            throw new Error('Invalid aes key size');
44020          }
44021
44022          encKey = key.slice(0);
44023          decKey = [];
44024          this._key = [encKey, decKey];
44025
44026          // schedule encryption keys
44027          for (i = keyLen; i < 4 * keyLen + 28; i++) {
44028            tmp = encKey[i - 1];
44029
44030            // apply sbox
44031            if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) {
44032              tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255];
44033
44034              // shift rows and add rcon
44035              if (i % keyLen === 0) {
44036                tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24;
44037                rcon = rcon << 1 ^ (rcon >> 7) * 283;
44038              }
44039            }
44040
44041            encKey[i] = encKey[i - keyLen] ^ tmp;
44042          }
44043
44044          // schedule decryption keys
44045          for (j = 0; i; j++, i--) {
44046            tmp = encKey[j & 3 ? i : i - 4];
44047            if (i <= 4 || j < 4) {
44048              decKey[j] = tmp;
44049            } else {
44050              decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]];
44051            }
44052          }
44053        }
44054
44055        /**
44056         * Decrypt 16 bytes, specified as four 32-bit words.
44057         *
44058         * @param {Number} encrypted0 the first word to decrypt
44059         * @param {Number} encrypted1 the second word to decrypt
44060         * @param {Number} encrypted2 the third word to decrypt
44061         * @param {Number} encrypted3 the fourth word to decrypt
44062         * @param {Int32Array} out the array to write the decrypted words
44063         * into
44064         * @param {Number} offset the offset into the output array to start
44065         * writing results
44066         * @return {Array} The plaintext.
44067         */
44068
44069        AES.prototype.decrypt = function decrypt$$1(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
44070          var key = this._key[1];
44071          // state variables a,b,c,d are loaded with pre-whitened data
44072          var a = encrypted0 ^ key[0];
44073          var b = encrypted3 ^ key[1];
44074          var c = encrypted2 ^ key[2];
44075          var d = encrypted1 ^ key[3];
44076          var a2 = void 0;
44077          var b2 = void 0;
44078          var c2 = void 0;
44079
44080          // key.length === 2 ?
44081          var nInnerRounds = key.length / 4 - 2;
44082          var i = void 0;
44083          var kIndex = 4;
44084          var table = this._tables[1];
44085
44086          // load up the tables
44087          var table0 = table[0];
44088          var table1 = table[1];
44089          var table2 = table[2];
44090          var table3 = table[3];
44091          var sbox = table[4];
44092
44093          // Inner rounds. Cribbed from OpenSSL.
44094          for (i = 0; i < nInnerRounds; i++) {
44095            a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex];
44096            b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1];
44097            c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2];
44098            d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3];
44099            kIndex += 4;
44100            a = a2;b = b2;c = c2;
44101          }
44102
44103          // Last round.
44104          for (i = 0; i < 4; i++) {
44105            out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++];
44106            a2 = a;a = b;b = c;c = d;d = a2;
44107          }
44108        };
44109
44110        return AES;
44111      }();
44112
44113      /**
44114       * @file stream.js
44115       */
44116      /**
44117       * A lightweight readable stream implemention that handles event dispatching.
44118       *
44119       * @class Stream
44120       */
44121      var Stream = function () {
44122        function Stream() {
44123          classCallCheck$$1(this, Stream);
44124
44125          this.listeners = {};
44126        }
44127
44128        /**
44129         * Add a listener for a specified event type.
44130         *
44131         * @param {String} type the event name
44132         * @param {Function} listener the callback to be invoked when an event of
44133         * the specified type occurs
44134         */
44135
44136        Stream.prototype.on = function on(type, listener) {
44137          if (!this.listeners[type]) {
44138            this.listeners[type] = [];
44139          }
44140          this.listeners[type].push(listener);
44141        };
44142
44143        /**
44144         * Remove a listener for a specified event type.
44145         *
44146         * @param {String} type the event name
44147         * @param {Function} listener  a function previously registered for this
44148         * type of event through `on`
44149         * @return {Boolean} if we could turn it off or not
44150         */
44151
44152        Stream.prototype.off = function off(type, listener) {
44153          if (!this.listeners[type]) {
44154            return false;
44155          }
44156
44157          var index = this.listeners[type].indexOf(listener);
44158
44159          this.listeners[type].splice(index, 1);
44160          return index > -1;
44161        };
44162
44163        /**
44164         * Trigger an event of the specified type on this stream. Any additional
44165         * arguments to this function are passed as parameters to event listeners.
44166         *
44167         * @param {String} type the event name
44168         */
44169
44170        Stream.prototype.trigger = function trigger(type) {
44171          var callbacks = this.listeners[type];
44172
44173          if (!callbacks) {
44174            return;
44175          }
44176
44177          // Slicing the arguments on every invocation of this method
44178          // can add a significant amount of overhead. Avoid the
44179          // intermediate object creation for the common case of a
44180          // single callback argument
44181          if (arguments.length === 2) {
44182            var length = callbacks.length;
44183
44184            for (var i = 0; i < length; ++i) {
44185              callbacks[i].call(this, arguments[1]);
44186            }
44187          } else {
44188            var args = Array.prototype.slice.call(arguments, 1);
44189            var _length = callbacks.length;
44190
44191            for (var _i = 0; _i < _length; ++_i) {
44192              callbacks[_i].apply(this, args);
44193            }
44194          }
44195        };
44196
44197        /**
44198         * Destroys the stream and cleans up.
44199         */
44200
44201        Stream.prototype.dispose = function dispose() {
44202          this.listeners = {};
44203        };
44204        /**
44205         * Forwards all `data` events on this stream to the destination stream. The
44206         * destination stream should provide a method `push` to receive the data
44207         * events as they arrive.
44208         *
44209         * @param {Stream} destination the stream that will receive all `data` events
44210         * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
44211         */
44212
44213        Stream.prototype.pipe = function pipe(destination) {
44214          this.on('data', function (data) {
44215            destination.push(data);
44216          });
44217        };
44218
44219        return Stream;
44220      }();
44221
44222      /**
44223       * @file async-stream.js
44224       */
44225      /**
44226       * A wrapper around the Stream class to use setTiemout
44227       * and run stream "jobs" Asynchronously
44228       *
44229       * @class AsyncStream
44230       * @extends Stream
44231       */
44232
44233      var AsyncStream$$1 = function (_Stream) {
44234        inherits$$1(AsyncStream$$1, _Stream);
44235
44236        function AsyncStream$$1() {
44237          classCallCheck$$1(this, AsyncStream$$1);
44238
44239          var _this = possibleConstructorReturn$$1(this, _Stream.call(this, Stream));
44240
44241          _this.jobs = [];
44242          _this.delay = 1;
44243          _this.timeout_ = null;
44244          return _this;
44245        }
44246
44247        /**
44248         * process an async job
44249         *
44250         * @private
44251         */
44252
44253        AsyncStream$$1.prototype.processJob_ = function processJob_() {
44254          this.jobs.shift()();
44255          if (this.jobs.length) {
44256            this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
44257          } else {
44258            this.timeout_ = null;
44259          }
44260        };
44261
44262        /**
44263         * push a job into the stream
44264         *
44265         * @param {Function} job the job to push into the stream
44266         */
44267
44268        AsyncStream$$1.prototype.push = function push(job) {
44269          this.jobs.push(job);
44270          if (!this.timeout_) {
44271            this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
44272          }
44273        };
44274
44275        return AsyncStream$$1;
44276      }(Stream);
44277
44278      /**
44279       * @file decrypter.js
44280       *
44281       * An asynchronous implementation of AES-128 CBC decryption with
44282       * PKCS#7 padding.
44283       */
44284
44285      /**
44286       * Convert network-order (big-endian) bytes into their little-endian
44287       * representation.
44288       */
44289      var ntoh = function ntoh(word) {
44290        return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24;
44291      };
44292
44293      /**
44294       * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
44295       *
44296       * @param {Uint8Array} encrypted the encrypted bytes
44297       * @param {Uint32Array} key the bytes of the decryption key
44298       * @param {Uint32Array} initVector the initialization vector (IV) to
44299       * use for the first round of CBC.
44300       * @return {Uint8Array} the decrypted bytes
44301       *
44302       * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
44303       * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
44304       * @see https://tools.ietf.org/html/rfc2315
44305       */
44306      var decrypt$$1 = function decrypt$$1(encrypted, key, initVector) {
44307        // word-level access to the encrypted bytes
44308        var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2);
44309
44310        var decipher = new AES(Array.prototype.slice.call(key));
44311
44312        // byte and word-level access for the decrypted output
44313        var decrypted = new Uint8Array(encrypted.byteLength);
44314        var decrypted32 = new Int32Array(decrypted.buffer);
44315
44316        // temporary variables for working with the IV, encrypted, and
44317        // decrypted data
44318        var init0 = void 0;
44319        var init1 = void 0;
44320        var init2 = void 0;
44321        var init3 = void 0;
44322        var encrypted0 = void 0;
44323        var encrypted1 = void 0;
44324        var encrypted2 = void 0;
44325        var encrypted3 = void 0;
44326
44327        // iteration variable
44328        var wordIx = void 0;
44329
44330        // pull out the words of the IV to ensure we don't modify the
44331        // passed-in reference and easier access
44332        init0 = initVector[0];
44333        init1 = initVector[1];
44334        init2 = initVector[2];
44335        init3 = initVector[3];
44336
44337        // decrypt four word sequences, applying cipher-block chaining (CBC)
44338        // to each decrypted block
44339        for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
44340          // convert big-endian (network order) words into little-endian
44341          // (javascript order)
44342          encrypted0 = ntoh(encrypted32[wordIx]);
44343          encrypted1 = ntoh(encrypted32[wordIx + 1]);
44344          encrypted2 = ntoh(encrypted32[wordIx + 2]);
44345          encrypted3 = ntoh(encrypted32[wordIx + 3]);
44346
44347          // decrypt the block
44348          decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx);
44349
44350          // XOR with the IV, and restore network byte-order to obtain the
44351          // plaintext
44352          decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
44353          decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
44354          decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
44355          decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3);
44356
44357          // setup the IV for the next round
44358          init0 = encrypted0;
44359          init1 = encrypted1;
44360          init2 = encrypted2;
44361          init3 = encrypted3;
44362        }
44363
44364        return decrypted;
44365      };
44366
44367      /**
44368       * The `Decrypter` class that manages decryption of AES
44369       * data through `AsyncStream` objects and the `decrypt`
44370       * function
44371       *
44372       * @param {Uint8Array} encrypted the encrypted bytes
44373       * @param {Uint32Array} key the bytes of the decryption key
44374       * @param {Uint32Array} initVector the initialization vector (IV) to
44375       * @param {Function} done the function to run when done
44376       * @class Decrypter
44377       */
44378
44379      var Decrypter$$1 = function () {
44380        function Decrypter$$1(encrypted, key, initVector, done) {
44381          classCallCheck$$1(this, Decrypter$$1);
44382
44383          var step = Decrypter$$1.STEP;
44384          var encrypted32 = new Int32Array(encrypted.buffer);
44385          var decrypted = new Uint8Array(encrypted.byteLength);
44386          var i = 0;
44387
44388          this.asyncStream_ = new AsyncStream$$1();
44389
44390          // split up the encryption job and do the individual chunks asynchronously
44391          this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
44392          for (i = step; i < encrypted32.length; i += step) {
44393            initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]);
44394            this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
44395          }
44396          // invoke the done() callback when everything is finished
44397          this.asyncStream_.push(function () {
44398            // remove pkcs#7 padding from the decrypted bytes
44399            done(null, unpad(decrypted));
44400          });
44401        }
44402
44403        /**
44404         * a getter for step the maximum number of bytes to process at one time
44405         *
44406         * @return {Number} the value of step 32000
44407         */
44408
44409        /**
44410         * @private
44411         */
44412        Decrypter$$1.prototype.decryptChunk_ = function decryptChunk_(encrypted, key, initVector, decrypted) {
44413          return function () {
44414            var bytes = decrypt$$1(encrypted, key, initVector);
44415
44416            decrypted.set(bytes, encrypted.byteOffset);
44417          };
44418        };
44419
44420        createClass$$1(Decrypter$$1, null, [{
44421          key: 'STEP',
44422          get: function get$$1() {
44423            // 4 * 8000;
44424            return 32000;
44425          }
44426        }]);
44427        return Decrypter$$1;
44428      }();
44429
44430      /**
44431       * @file bin-utils.js
44432       */
44433
44434      /**
44435       * Creates an object for sending to a web worker modifying properties that are TypedArrays
44436       * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
44437       *
44438       * @param {Object} message
44439       *        Object of properties and values to send to the web worker
44440       * @return {Object}
44441       *         Modified message with TypedArray values expanded
44442       * @function createTransferableMessage
44443       */
44444      var createTransferableMessage = function createTransferableMessage(message) {
44445        var transferable = {};
44446
44447        Object.keys(message).forEach(function (key) {
44448          var value = message[key];
44449
44450          if (ArrayBuffer.isView(value)) {
44451            transferable[key] = {
44452              bytes: value.buffer,
44453              byteOffset: value.byteOffset,
44454              byteLength: value.byteLength
44455            };
44456          } else {
44457            transferable[key] = value;
44458          }
44459        });
44460
44461        return transferable;
44462      };
44463
44464      /**
44465       * Our web worker interface so that things can talk to aes-decrypter
44466       * that will be running in a web worker. the scope is passed to this by
44467       * webworkify.
44468       *
44469       * @param {Object} self
44470       *        the scope for the web worker
44471       */
44472      var DecrypterWorker = function DecrypterWorker(self) {
44473        self.onmessage = function (event) {
44474          var data = event.data;
44475          var encrypted = new Uint8Array(data.encrypted.bytes, data.encrypted.byteOffset, data.encrypted.byteLength);
44476          var key = new Uint32Array(data.key.bytes, data.key.byteOffset, data.key.byteLength / 4);
44477          var iv = new Uint32Array(data.iv.bytes, data.iv.byteOffset, data.iv.byteLength / 4);
44478
44479          /* eslint-disable no-new, handle-callback-err */
44480          new Decrypter$$1(encrypted, key, iv, function (err, bytes) {
44481            window_1$$1.postMessage(createTransferableMessage({
44482              source: data.source,
44483              decrypted: bytes
44484            }), [bytes.buffer]);
44485          });
44486          /* eslint-enable */
44487        };
44488      };
44489
44490      var decrypterWorker = new DecrypterWorker(self);
44491
44492      return decrypterWorker;
44493    }();
44494  });
44495
44496  /**
44497   * Convert the properties of an HLS track into an audioTrackKind.
44498   *
44499   * @private
44500   */
44501  var audioTrackKind_ = function audioTrackKind_(properties) {
44502    var kind = properties.default ? 'main' : 'alternative';
44503
44504    if (properties.characteristics && properties.characteristics.indexOf('public.accessibility.describes-video') >= 0) {
44505      kind = 'main-desc';
44506    }
44507
44508    return kind;
44509  };
44510
44511  /**
44512   * Pause provided segment loader and playlist loader if active
44513   *
44514   * @param {SegmentLoader} segmentLoader
44515   *        SegmentLoader to pause
44516   * @param {Object} mediaType
44517   *        Active media type
44518   * @function stopLoaders
44519   */
44520  var stopLoaders = function stopLoaders(segmentLoader, mediaType) {
44521    segmentLoader.abort();
44522    segmentLoader.pause();
44523
44524    if (mediaType && mediaType.activePlaylistLoader) {
44525      mediaType.activePlaylistLoader.pause();
44526      mediaType.activePlaylistLoader = null;
44527    }
44528  };
44529
44530  /**
44531   * Start loading provided segment loader and playlist loader
44532   *
44533   * @param {PlaylistLoader} playlistLoader
44534   *        PlaylistLoader to start loading
44535   * @param {Object} mediaType
44536   *        Active media type
44537   * @function startLoaders
44538   */
44539  var startLoaders = function startLoaders(playlistLoader, mediaType) {
44540    // Segment loader will be started after `loadedmetadata` or `loadedplaylist` from the
44541    // playlist loader
44542    mediaType.activePlaylistLoader = playlistLoader;
44543    playlistLoader.load();
44544  };
44545
44546  /**
44547   * Returns a function to be called when the media group changes. It performs a
44548   * non-destructive (preserve the buffer) resync of the SegmentLoader. This is because a
44549   * change of group is merely a rendition switch of the same content at another encoding,
44550   * rather than a change of content, such as switching audio from English to Spanish.
44551   *
44552   * @param {String} type
44553   *        MediaGroup type
44554   * @param {Object} settings
44555   *        Object containing required information for media groups
44556   * @return {Function}
44557   *         Handler for a non-destructive resync of SegmentLoader when the active media
44558   *         group changes.
44559   * @function onGroupChanged
44560   */
44561  var onGroupChanged = function onGroupChanged(type, settings) {
44562    return function () {
44563      var _settings$segmentLoad = settings.segmentLoaders,
44564          segmentLoader = _settings$segmentLoad[type],
44565          mainSegmentLoader = _settings$segmentLoad.main,
44566          mediaType = settings.mediaTypes[type];
44567
44568      var activeTrack = mediaType.activeTrack();
44569      var activeGroup = mediaType.activeGroup(activeTrack);
44570      var previousActiveLoader = mediaType.activePlaylistLoader;
44571
44572      stopLoaders(segmentLoader, mediaType);
44573
44574      if (!activeGroup) {
44575        // there is no group active
44576        return;
44577      }
44578
44579      if (!activeGroup.playlistLoader) {
44580        if (previousActiveLoader) {
44581          // The previous group had a playlist loader but the new active group does not
44582          // this means we are switching from demuxed to muxed audio. In this case we want to
44583          // do a destructive reset of the main segment loader and not restart the audio
44584          // loaders.
44585          mainSegmentLoader.resetEverything();
44586        }
44587        return;
44588      }
44589
44590      // Non-destructive resync
44591      segmentLoader.resyncLoader();
44592
44593      startLoaders(activeGroup.playlistLoader, mediaType);
44594    };
44595  };
44596
44597  /**
44598   * Returns a function to be called when the media track changes. It performs a
44599   * destructive reset of the SegmentLoader to ensure we start loading as close to
44600   * currentTime as possible.
44601   *
44602   * @param {String} type
44603   *        MediaGroup type
44604   * @param {Object} settings
44605   *        Object containing required information for media groups
44606   * @return {Function}
44607   *         Handler for a destructive reset of SegmentLoader when the active media
44608   *         track changes.
44609   * @function onTrackChanged
44610   */
44611  var onTrackChanged = function onTrackChanged(type, settings) {
44612    return function () {
44613      var _settings$segmentLoad2 = settings.segmentLoaders,
44614          segmentLoader = _settings$segmentLoad2[type],
44615          mainSegmentLoader = _settings$segmentLoad2.main,
44616          mediaType = settings.mediaTypes[type];
44617
44618      var activeTrack = mediaType.activeTrack();
44619      var activeGroup = mediaType.activeGroup(activeTrack);
44620      var previousActiveLoader = mediaType.activePlaylistLoader;
44621
44622      stopLoaders(segmentLoader, mediaType);
44623
44624      if (!activeGroup) {
44625        // there is no group active so we do not want to restart loaders
44626        return;
44627      }
44628
44629      if (!activeGroup.playlistLoader) {
44630        // when switching from demuxed audio/video to muxed audio/video (noted by no playlist
44631        // loader for the audio group), we want to do a destructive reset of the main segment
44632        // loader and not restart the audio loaders
44633        mainSegmentLoader.resetEverything();
44634        return;
44635      }
44636
44637      if (previousActiveLoader === activeGroup.playlistLoader) {
44638        // Nothing has actually changed. This can happen because track change events can fire
44639        // multiple times for a "single" change. One for enabling the new active track, and
44640        // one for disabling the track that was active
44641        startLoaders(activeGroup.playlistLoader, mediaType);
44642        return;
44643      }
44644
44645      if (segmentLoader.track) {
44646        // For WebVTT, set the new text track in the segmentloader
44647        segmentLoader.track(activeTrack);
44648      }
44649
44650      // destructive reset
44651      segmentLoader.resetEverything();
44652
44653      startLoaders(activeGroup.playlistLoader, mediaType);
44654    };
44655  };
44656
44657  var onError = {
44658    /**
44659     * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
44660     * an error.
44661     *
44662     * @param {String} type
44663     *        MediaGroup type
44664     * @param {Object} settings
44665     *        Object containing required information for media groups
44666     * @return {Function}
44667     *         Error handler. Logs warning (or error if the playlist is blacklisted) to
44668     *         console and switches back to default audio track.
44669     * @function onError.AUDIO
44670     */
44671    AUDIO: function AUDIO(type, settings) {
44672      return function () {
44673        var segmentLoader = settings.segmentLoaders[type],
44674            mediaType = settings.mediaTypes[type],
44675            blacklistCurrentPlaylist = settings.blacklistCurrentPlaylist;
44676
44677        stopLoaders(segmentLoader, mediaType);
44678
44679        // switch back to default audio track
44680        var activeTrack = mediaType.activeTrack();
44681        var activeGroup = mediaType.activeGroup();
44682        var id = (activeGroup.filter(function (group) {
44683          return group.default;
44684        })[0] || activeGroup[0]).id;
44685        var defaultTrack = mediaType.tracks[id];
44686
44687        if (activeTrack === defaultTrack) {
44688          // Default track encountered an error. All we can do now is blacklist the current
44689          // rendition and hope another will switch audio groups
44690          blacklistCurrentPlaylist({
44691            message: 'Problem encountered loading the default audio track.'
44692          });
44693          return;
44694        }
44695
44696        videojs$1.log.warn('Problem encountered loading the alternate audio track.' + 'Switching back to default.');
44697
44698        for (var trackId in mediaType.tracks) {
44699          mediaType.tracks[trackId].enabled = mediaType.tracks[trackId] === defaultTrack;
44700        }
44701
44702        mediaType.onTrackChanged();
44703      };
44704    },
44705    /**
44706     * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
44707     * an error.
44708     *
44709     * @param {String} type
44710     *        MediaGroup type
44711     * @param {Object} settings
44712     *        Object containing required information for media groups
44713     * @return {Function}
44714     *         Error handler. Logs warning to console and disables the active subtitle track
44715     * @function onError.SUBTITLES
44716     */
44717    SUBTITLES: function SUBTITLES(type, settings) {
44718      return function () {
44719        var segmentLoader = settings.segmentLoaders[type],
44720            mediaType = settings.mediaTypes[type];
44721
44722        videojs$1.log.warn('Problem encountered loading the subtitle track.' + 'Disabling subtitle track.');
44723
44724        stopLoaders(segmentLoader, mediaType);
44725
44726        var track = mediaType.activeTrack();
44727
44728        if (track) {
44729          track.mode = 'disabled';
44730        }
44731
44732        mediaType.onTrackChanged();
44733      };
44734    }
44735  };
44736
44737  var setupListeners = {
44738    /**
44739     * Setup event listeners for audio playlist loader
44740     *
44741     * @param {String} type
44742     *        MediaGroup type
44743     * @param {PlaylistLoader|null} playlistLoader
44744     *        PlaylistLoader to register listeners on
44745     * @param {Object} settings
44746     *        Object containing required information for media groups
44747     * @function setupListeners.AUDIO
44748     */
44749    AUDIO: function AUDIO(type, playlistLoader, settings) {
44750      if (!playlistLoader) {
44751        // no playlist loader means audio will be muxed with the video
44752        return;
44753      }
44754
44755      var tech = settings.tech,
44756          requestOptions = settings.requestOptions,
44757          segmentLoader = settings.segmentLoaders[type];
44758
44759      playlistLoader.on('loadedmetadata', function () {
44760        var media = playlistLoader.media();
44761
44762        segmentLoader.playlist(media, requestOptions);
44763
44764        // if the video is already playing, or if this isn't a live video and preload
44765        // permits, start downloading segments
44766        if (!tech.paused() || media.endList && tech.preload() !== 'none') {
44767          segmentLoader.load();
44768        }
44769      });
44770
44771      playlistLoader.on('loadedplaylist', function () {
44772        segmentLoader.playlist(playlistLoader.media(), requestOptions);
44773
44774        // If the player isn't paused, ensure that the segment loader is running
44775        if (!tech.paused()) {
44776          segmentLoader.load();
44777        }
44778      });
44779
44780      playlistLoader.on('error', onError[type](type, settings));
44781    },
44782    /**
44783     * Setup event listeners for subtitle playlist loader
44784     *
44785     * @param {String} type
44786     *        MediaGroup type
44787     * @param {PlaylistLoader|null} playlistLoader
44788     *        PlaylistLoader to register listeners on
44789     * @param {Object} settings
44790     *        Object containing required information for media groups
44791     * @function setupListeners.SUBTITLES
44792     */
44793    SUBTITLES: function SUBTITLES(type, playlistLoader, settings) {
44794      var tech = settings.tech,
44795          requestOptions = settings.requestOptions,
44796          segmentLoader = settings.segmentLoaders[type],
44797          mediaType = settings.mediaTypes[type];
44798
44799      playlistLoader.on('loadedmetadata', function () {
44800        var media = playlistLoader.media();
44801
44802        segmentLoader.playlist(media, requestOptions);
44803        segmentLoader.track(mediaType.activeTrack());
44804
44805        // if the video is already playing, or if this isn't a live video and preload
44806        // permits, start downloading segments
44807        if (!tech.paused() || media.endList && tech.preload() !== 'none') {
44808          segmentLoader.load();
44809        }
44810      });
44811
44812      playlistLoader.on('loadedplaylist', function () {
44813        segmentLoader.playlist(playlistLoader.media(), requestOptions);
44814
44815        // If the player isn't paused, ensure that the segment loader is running
44816        if (!tech.paused()) {
44817          segmentLoader.load();
44818        }
44819      });
44820
44821      playlistLoader.on('error', onError[type](type, settings));
44822    }
44823  };
44824
44825  var byGroupId = function byGroupId(type, groupId) {
44826    return function (playlist) {
44827      return playlist.attributes[type] === groupId;
44828    };
44829  };
44830
44831  var byResolvedUri = function byResolvedUri(resolvedUri) {
44832    return function (playlist) {
44833      return playlist.resolvedUri === resolvedUri;
44834    };
44835  };
44836
44837  var initialize = {
44838    /**
44839     * Setup PlaylistLoaders and AudioTracks for the audio groups
44840     *
44841     * @param {String} type
44842     *        MediaGroup type
44843     * @param {Object} settings
44844     *        Object containing required information for media groups
44845     * @function initialize.AUDIO
44846     */
44847    'AUDIO': function AUDIO(type, settings) {
44848      var hls = settings.hls,
44849          sourceType = settings.sourceType,
44850          segmentLoader = settings.segmentLoaders[type],
44851          withCredentials = settings.requestOptions.withCredentials,
44852          _settings$master = settings.master,
44853          mediaGroups = _settings$master.mediaGroups,
44854          playlists = _settings$master.playlists,
44855          _settings$mediaTypes$ = settings.mediaTypes[type],
44856          groups = _settings$mediaTypes$.groups,
44857          tracks = _settings$mediaTypes$.tracks,
44858          masterPlaylistLoader = settings.masterPlaylistLoader;
44859
44860      // force a default if we have none
44861
44862      if (!mediaGroups[type] || Object.keys(mediaGroups[type]).length === 0) {
44863        mediaGroups[type] = { main: { default: { default: true } } };
44864      }
44865
44866      for (var groupId in mediaGroups[type]) {
44867        if (!groups[groupId]) {
44868          groups[groupId] = [];
44869        }
44870
44871        // List of playlists that have an AUDIO attribute value matching the current
44872        // group ID
44873        var groupPlaylists = playlists.filter(byGroupId(type, groupId));
44874
44875        for (var variantLabel in mediaGroups[type][groupId]) {
44876          var properties = mediaGroups[type][groupId][variantLabel];
44877
44878          // List of playlists for the current group ID that have a matching uri with
44879          // this alternate audio variant
44880          var matchingPlaylists = groupPlaylists.filter(byResolvedUri(properties.resolvedUri));
44881
44882          if (matchingPlaylists.length) {
44883            // If there is a playlist that has the same uri as this audio variant, assume
44884            // that the playlist is audio only. We delete the resolvedUri property here
44885            // to prevent a playlist loader from being created so that we don't have
44886            // both the main and audio segment loaders loading the same audio segments
44887            // from the same playlist.
44888            delete properties.resolvedUri;
44889          }
44890
44891          var playlistLoader = void 0;
44892
44893          if (properties.resolvedUri) {
44894            playlistLoader = new PlaylistLoader(properties.resolvedUri, hls, withCredentials);
44895          } else if (properties.playlists && sourceType === 'dash') {
44896            playlistLoader = new DashPlaylistLoader(properties.playlists[0], hls, withCredentials, masterPlaylistLoader);
44897          } else {
44898            // no resolvedUri means the audio is muxed with the video when using this
44899            // audio track
44900            playlistLoader = null;
44901          }
44902
44903          properties = videojs$1.mergeOptions({ id: variantLabel, playlistLoader: playlistLoader }, properties);
44904
44905          setupListeners[type](type, properties.playlistLoader, settings);
44906
44907          groups[groupId].push(properties);
44908
44909          if (typeof tracks[variantLabel] === 'undefined') {
44910            var track = new videojs$1.AudioTrack({
44911              id: variantLabel,
44912              kind: audioTrackKind_(properties),
44913              enabled: false,
44914              language: properties.language,
44915              default: properties.default,
44916              label: variantLabel
44917            });
44918
44919            tracks[variantLabel] = track;
44920          }
44921        }
44922      }
44923
44924      // setup single error event handler for the segment loader
44925      segmentLoader.on('error', onError[type](type, settings));
44926    },
44927    /**
44928     * Setup PlaylistLoaders and TextTracks for the subtitle groups
44929     *
44930     * @param {String} type
44931     *        MediaGroup type
44932     * @param {Object} settings
44933     *        Object containing required information for media groups
44934     * @function initialize.SUBTITLES
44935     */
44936    'SUBTITLES': function SUBTITLES(type, settings) {
44937      var tech = settings.tech,
44938          hls = settings.hls,
44939          sourceType = settings.sourceType,
44940          segmentLoader = settings.segmentLoaders[type],
44941          withCredentials = settings.requestOptions.withCredentials,
44942          mediaGroups = settings.master.mediaGroups,
44943          _settings$mediaTypes$2 = settings.mediaTypes[type],
44944          groups = _settings$mediaTypes$2.groups,
44945          tracks = _settings$mediaTypes$2.tracks,
44946          masterPlaylistLoader = settings.masterPlaylistLoader;
44947
44948      for (var groupId in mediaGroups[type]) {
44949        if (!groups[groupId]) {
44950          groups[groupId] = [];
44951        }
44952
44953        for (var variantLabel in mediaGroups[type][groupId]) {
44954          if (mediaGroups[type][groupId][variantLabel].forced) {
44955            // Subtitle playlists with the forced attribute are not selectable in Safari.
44956            // According to Apple's HLS Authoring Specification:
44957            //   If content has forced subtitles and regular subtitles in a given language,
44958            //   the regular subtitles track in that language MUST contain both the forced
44959            //   subtitles and the regular subtitles for that language.
44960            // Because of this requirement and that Safari does not add forced subtitles,
44961            // forced subtitles are skipped here to maintain consistent experience across
44962            // all platforms
44963            continue;
44964          }
44965
44966          var properties = mediaGroups[type][groupId][variantLabel];
44967
44968          var playlistLoader = void 0;
44969
44970          if (sourceType === 'hls') {
44971            playlistLoader = new PlaylistLoader(properties.resolvedUri, hls, withCredentials);
44972          } else if (sourceType === 'dash') {
44973            playlistLoader = new DashPlaylistLoader(properties.playlists[0], hls, withCredentials, masterPlaylistLoader);
44974          }
44975
44976          properties = videojs$1.mergeOptions({
44977            id: variantLabel,
44978            playlistLoader: playlistLoader
44979          }, properties);
44980
44981          setupListeners[type](type, properties.playlistLoader, settings);
44982
44983          groups[groupId].push(properties);
44984
44985          if (typeof tracks[variantLabel] === 'undefined') {
44986            var track = tech.addRemoteTextTrack({
44987              id: variantLabel,
44988              kind: 'subtitles',
44989              enabled: false,
44990              language: properties.language,
44991              label: variantLabel
44992            }, false).track;
44993
44994            tracks[variantLabel] = track;
44995          }
44996        }
44997      }
44998
44999      // setup single error event handler for the segment loader
45000      segmentLoader.on('error', onError[type](type, settings));
45001    },
45002    /**
45003     * Setup TextTracks for the closed-caption groups
45004     *
45005     * @param {String} type
45006     *        MediaGroup type
45007     * @param {Object} settings
45008     *        Object containing required information for media groups
45009     * @function initialize['CLOSED-CAPTIONS']
45010     */
45011    'CLOSED-CAPTIONS': function CLOSEDCAPTIONS(type, settings) {
45012      var tech = settings.tech,
45013          mediaGroups = settings.master.mediaGroups,
45014          _settings$mediaTypes$3 = settings.mediaTypes[type],
45015          groups = _settings$mediaTypes$3.groups,
45016          tracks = _settings$mediaTypes$3.tracks;
45017
45018      for (var groupId in mediaGroups[type]) {
45019        if (!groups[groupId]) {
45020          groups[groupId] = [];
45021        }
45022
45023        for (var variantLabel in mediaGroups[type][groupId]) {
45024          var properties = mediaGroups[type][groupId][variantLabel];
45025
45026          // We only support CEA608 captions for now, so ignore anything that
45027          // doesn't use a CCx INSTREAM-ID
45028          if (!properties.instreamId.match(/CC\d/)) {
45029            continue;
45030          }
45031
45032          // No PlaylistLoader is required for Closed-Captions because the captions are
45033          // embedded within the video stream
45034          groups[groupId].push(videojs$1.mergeOptions({ id: variantLabel }, properties));
45035
45036          if (typeof tracks[variantLabel] === 'undefined') {
45037            var track = tech.addRemoteTextTrack({
45038              id: properties.instreamId,
45039              kind: 'captions',
45040              enabled: false,
45041              language: properties.language,
45042              label: variantLabel
45043            }, false).track;
45044
45045            tracks[variantLabel] = track;
45046          }
45047        }
45048      }
45049    }
45050  };
45051
45052  /**
45053   * Returns a function used to get the active group of the provided type
45054   *
45055   * @param {String} type
45056   *        MediaGroup type
45057   * @param {Object} settings
45058   *        Object containing required information for media groups
45059   * @return {Function}
45060   *         Function that returns the active media group for the provided type. Takes an
45061   *         optional parameter {TextTrack} track. If no track is provided, a list of all
45062   *         variants in the group, otherwise the variant corresponding to the provided
45063   *         track is returned.
45064   * @function activeGroup
45065   */
45066  var activeGroup = function activeGroup(type, settings) {
45067    return function (track) {
45068      var masterPlaylistLoader = settings.masterPlaylistLoader,
45069          groups = settings.mediaTypes[type].groups;
45070
45071      var media = masterPlaylistLoader.media();
45072
45073      if (!media) {
45074        return null;
45075      }
45076
45077      var variants = null;
45078
45079      if (media.attributes[type]) {
45080        variants = groups[media.attributes[type]];
45081      }
45082
45083      variants = variants || groups.main;
45084
45085      if (typeof track === 'undefined') {
45086        return variants;
45087      }
45088
45089      if (track === null) {
45090        // An active track was specified so a corresponding group is expected. track === null
45091        // means no track is currently active so there is no corresponding group
45092        return null;
45093      }
45094
45095      return variants.filter(function (props) {
45096        return props.id === track.id;
45097      })[0] || null;
45098    };
45099  };
45100
45101  var activeTrack = {
45102    /**
45103     * Returns a function used to get the active track of type provided
45104     *
45105     * @param {String} type
45106     *        MediaGroup type
45107     * @param {Object} settings
45108     *        Object containing required information for media groups
45109     * @return {Function}
45110     *         Function that returns the active media track for the provided type. Returns
45111     *         null if no track is active
45112     * @function activeTrack.AUDIO
45113     */
45114    AUDIO: function AUDIO(type, settings) {
45115      return function () {
45116        var tracks = settings.mediaTypes[type].tracks;
45117
45118        for (var id in tracks) {
45119          if (tracks[id].enabled) {
45120            return tracks[id];
45121          }
45122        }
45123
45124        return null;
45125      };
45126    },
45127    /**
45128     * Returns a function used to get the active track of type provided
45129     *
45130     * @param {String} type
45131     *        MediaGroup type
45132     * @param {Object} settings
45133     *        Object containing required information for media groups
45134     * @return {Function}
45135     *         Function that returns the active media track for the provided type. Returns
45136     *         null if no track is active
45137     * @function activeTrack.SUBTITLES
45138     */
45139    SUBTITLES: function SUBTITLES(type, settings) {
45140      return function () {
45141        var tracks = settings.mediaTypes[type].tracks;
45142
45143        for (var id in tracks) {
45144          if (tracks[id].mode === 'showing') {
45145            return tracks[id];
45146          }
45147        }
45148
45149        return null;
45150      };
45151    }
45152  };
45153
45154  /**
45155   * Setup PlaylistLoaders and Tracks for media groups (Audio, Subtitles,
45156   * Closed-Captions) specified in the master manifest.
45157   *
45158   * @param {Object} settings
45159   *        Object containing required information for setting up the media groups
45160   * @param {SegmentLoader} settings.segmentLoaders.AUDIO
45161   *        Audio segment loader
45162   * @param {SegmentLoader} settings.segmentLoaders.SUBTITLES
45163   *        Subtitle segment loader
45164   * @param {SegmentLoader} settings.segmentLoaders.main
45165   *        Main segment loader
45166   * @param {Tech} settings.tech
45167   *        The tech of the player
45168   * @param {Object} settings.requestOptions
45169   *        XHR request options used by the segment loaders
45170   * @param {PlaylistLoader} settings.masterPlaylistLoader
45171   *        PlaylistLoader for the master source
45172   * @param {HlsHandler} settings.hls
45173   *        HLS SourceHandler
45174   * @param {Object} settings.master
45175   *        The parsed master manifest
45176   * @param {Object} settings.mediaTypes
45177   *        Object to store the loaders, tracks, and utility methods for each media type
45178   * @param {Function} settings.blacklistCurrentPlaylist
45179   *        Blacklists the current rendition and forces a rendition switch.
45180   * @function setupMediaGroups
45181   */
45182  var setupMediaGroups = function setupMediaGroups(settings) {
45183    ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) {
45184      initialize[type](type, settings);
45185    });
45186
45187    var mediaTypes = settings.mediaTypes,
45188        masterPlaylistLoader = settings.masterPlaylistLoader,
45189        tech = settings.tech,
45190        hls = settings.hls;
45191
45192    // setup active group and track getters and change event handlers
45193
45194    ['AUDIO', 'SUBTITLES'].forEach(function (type) {
45195      mediaTypes[type].activeGroup = activeGroup(type, settings);
45196      mediaTypes[type].activeTrack = activeTrack[type](type, settings);
45197      mediaTypes[type].onGroupChanged = onGroupChanged(type, settings);
45198      mediaTypes[type].onTrackChanged = onTrackChanged(type, settings);
45199    });
45200
45201    // DO NOT enable the default subtitle or caption track.
45202    // DO enable the default audio track
45203    var audioGroup = mediaTypes.AUDIO.activeGroup();
45204    var groupId = (audioGroup.filter(function (group) {
45205      return group.default;
45206    })[0] || audioGroup[0]).id;
45207
45208    mediaTypes.AUDIO.tracks[groupId].enabled = true;
45209    mediaTypes.AUDIO.onTrackChanged();
45210
45211    masterPlaylistLoader.on('mediachange', function () {
45212      ['AUDIO', 'SUBTITLES'].forEach(function (type) {
45213        return mediaTypes[type].onGroupChanged();
45214      });
45215    });
45216
45217    // custom audio track change event handler for usage event
45218    var onAudioTrackChanged = function onAudioTrackChanged() {
45219      mediaTypes.AUDIO.onTrackChanged();
45220      tech.trigger({ type: 'usage', name: 'hls-audio-change' });
45221    };
45222
45223    tech.audioTracks().addEventListener('change', onAudioTrackChanged);
45224    tech.remoteTextTracks().addEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
45225
45226    hls.on('dispose', function () {
45227      tech.audioTracks().removeEventListener('change', onAudioTrackChanged);
45228      tech.remoteTextTracks().removeEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
45229    });
45230
45231    // clear existing audio tracks and add the ones we just created
45232    tech.clearTracks('audio');
45233
45234    for (var id in mediaTypes.AUDIO.tracks) {
45235      tech.audioTracks().addTrack(mediaTypes.AUDIO.tracks[id]);
45236    }
45237  };
45238
45239  /**
45240   * Creates skeleton object used to store the loaders, tracks, and utility methods for each
45241   * media type
45242   *
45243   * @return {Object}
45244   *         Object to store the loaders, tracks, and utility methods for each media type
45245   * @function createMediaTypes
45246   */
45247  var createMediaTypes = function createMediaTypes() {
45248    var mediaTypes = {};
45249
45250    ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) {
45251      mediaTypes[type] = {
45252        groups: {},
45253        tracks: {},
45254        activePlaylistLoader: null,
45255        activeGroup: noop$1,
45256        activeTrack: noop$1,
45257        onGroupChanged: noop$1,
45258        onTrackChanged: noop$1
45259      };
45260    });
45261
45262    return mediaTypes;
45263  };
45264
45265  /**
45266   * @file master-playlist-controller.js
45267   */
45268
45269  var ABORT_EARLY_BLACKLIST_SECONDS = 60 * 2;
45270
45271  var Hls = void 0;
45272
45273  // SegmentLoader stats that need to have each loader's
45274  // values summed to calculate the final value
45275  var loaderStats = ['mediaRequests', 'mediaRequestsAborted', 'mediaRequestsTimedout', 'mediaRequestsErrored', 'mediaTransferDuration', 'mediaBytesTransferred'];
45276  var sumLoaderStat = function sumLoaderStat(stat) {
45277    return this.audioSegmentLoader_[stat] + this.mainSegmentLoader_[stat];
45278  };
45279
45280  /**
45281   * the master playlist controller controller all interactons
45282   * between playlists and segmentloaders. At this time this mainly
45283   * involves a master playlist and a series of audio playlists
45284   * if they are available
45285   *
45286   * @class MasterPlaylistController
45287   * @extends videojs.EventTarget
45288   */
45289  var MasterPlaylistController = function (_videojs$EventTarget) {
45290    inherits$3(MasterPlaylistController, _videojs$EventTarget);
45291
45292    function MasterPlaylistController(options) {
45293      classCallCheck$3(this, MasterPlaylistController);
45294
45295      var _this = possibleConstructorReturn$3(this, (MasterPlaylistController.__proto__ || Object.getPrototypeOf(MasterPlaylistController)).call(this));
45296
45297      var url = options.url,
45298          withCredentials = options.withCredentials,
45299          tech = options.tech,
45300          bandwidth = options.bandwidth,
45301          externHls = options.externHls,
45302          useCueTags = options.useCueTags,
45303          blacklistDuration = options.blacklistDuration,
45304          enableLowInitialPlaylist = options.enableLowInitialPlaylist,
45305          sourceType = options.sourceType;
45306
45307      if (!url) {
45308        throw new Error('A non-empty playlist URL is required');
45309      }
45310
45311      Hls = externHls;
45312
45313      _this.withCredentials = withCredentials;
45314      _this.tech_ = tech;
45315      _this.hls_ = tech.hls;
45316      _this.sourceType_ = sourceType;
45317      _this.useCueTags_ = useCueTags;
45318      _this.blacklistDuration = blacklistDuration;
45319      _this.enableLowInitialPlaylist = enableLowInitialPlaylist;
45320      if (_this.useCueTags_) {
45321        _this.cueTagsTrack_ = _this.tech_.addTextTrack('metadata', 'ad-cues');
45322        _this.cueTagsTrack_.inBandMetadataTrackDispatchType = '';
45323      }
45324
45325      _this.requestOptions_ = {
45326        withCredentials: _this.withCredentials,
45327        timeout: null
45328      };
45329
45330      _this.mediaTypes_ = createMediaTypes();
45331
45332      _this.mediaSource = new videojs$1.MediaSource();
45333
45334      // load the media source into the player
45335      _this.mediaSource.addEventListener('sourceopen', _this.handleSourceOpen_.bind(_this));
45336
45337      _this.seekable_ = videojs$1.createTimeRanges();
45338      _this.hasPlayed_ = function () {
45339        return false;
45340      };
45341
45342      _this.syncController_ = new SyncController(options);
45343      _this.segmentMetadataTrack_ = tech.addRemoteTextTrack({
45344        kind: 'metadata',
45345        label: 'segment-metadata'
45346      }, false).track;
45347
45348      _this.decrypter_ = new Decrypter$1();
45349
45350      var segmentLoaderSettings = {
45351        hls: _this.hls_,
45352        mediaSource: _this.mediaSource,
45353        currentTime: _this.tech_.currentTime.bind(_this.tech_),
45354        seekable: function seekable$$1() {
45355          return _this.seekable();
45356        },
45357        seeking: function seeking() {
45358          return _this.tech_.seeking();
45359        },
45360        duration: function duration$$1() {
45361          return _this.mediaSource.duration;
45362        },
45363        hasPlayed: function hasPlayed() {
45364          return _this.hasPlayed_();
45365        },
45366        goalBufferLength: function goalBufferLength() {
45367          return _this.goalBufferLength();
45368        },
45369        bandwidth: bandwidth,
45370        syncController: _this.syncController_,
45371        decrypter: _this.decrypter_,
45372        sourceType: _this.sourceType_
45373      };
45374
45375      _this.masterPlaylistLoader_ = _this.sourceType_ === 'dash' ? new DashPlaylistLoader(url, _this.hls_, _this.withCredentials) : new PlaylistLoader(url, _this.hls_, _this.withCredentials);
45376      _this.setupMasterPlaylistLoaderListeners_();
45377
45378      // setup segment loaders
45379      // combined audio/video or just video when alternate audio track is selected
45380      _this.mainSegmentLoader_ = new SegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, {
45381        segmentMetadataTrack: _this.segmentMetadataTrack_,
45382        loaderType: 'main'
45383      }), options);
45384
45385      // alternate audio track
45386      _this.audioSegmentLoader_ = new SegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, {
45387        loaderType: 'audio'
45388      }), options);
45389
45390      _this.subtitleSegmentLoader_ = new VTTSegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, {
45391        loaderType: 'vtt'
45392      }), options);
45393
45394      _this.setupSegmentLoaderListeners_();
45395
45396      // Create SegmentLoader stat-getters
45397      loaderStats.forEach(function (stat) {
45398        _this[stat + '_'] = sumLoaderStat.bind(_this, stat);
45399      });
45400
45401      _this.logger_ = logger('MPC');
45402
45403      _this.masterPlaylistLoader_.load();
45404      return _this;
45405    }
45406
45407    /**
45408     * Register event handlers on the master playlist loader. A helper
45409     * function for construction time.
45410     *
45411     * @private
45412     */
45413
45414    createClass$2(MasterPlaylistController, [{
45415      key: 'setupMasterPlaylistLoaderListeners_',
45416      value: function setupMasterPlaylistLoaderListeners_() {
45417        var _this2 = this;
45418
45419        this.masterPlaylistLoader_.on('loadedmetadata', function () {
45420          var media = _this2.masterPlaylistLoader_.media();
45421          var requestTimeout = _this2.masterPlaylistLoader_.targetDuration * 1.5 * 1000;
45422
45423          // If we don't have any more available playlists, we don't want to
45424          // timeout the request.
45425          if (isLowestEnabledRendition(_this2.masterPlaylistLoader_.master, _this2.masterPlaylistLoader_.media())) {
45426            _this2.requestOptions_.timeout = 0;
45427          } else {
45428            _this2.requestOptions_.timeout = requestTimeout;
45429          }
45430
45431          // if this isn't a live video and preload permits, start
45432          // downloading segments
45433          if (media.endList && _this2.tech_.preload() !== 'none') {
45434            _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_);
45435            _this2.mainSegmentLoader_.load();
45436          }
45437
45438          setupMediaGroups({
45439            sourceType: _this2.sourceType_,
45440            segmentLoaders: {
45441              AUDIO: _this2.audioSegmentLoader_,
45442              SUBTITLES: _this2.subtitleSegmentLoader_,
45443              main: _this2.mainSegmentLoader_
45444            },
45445            tech: _this2.tech_,
45446            requestOptions: _this2.requestOptions_,
45447            masterPlaylistLoader: _this2.masterPlaylistLoader_,
45448            hls: _this2.hls_,
45449            master: _this2.master(),
45450            mediaTypes: _this2.mediaTypes_,
45451            blacklistCurrentPlaylist: _this2.blacklistCurrentPlaylist.bind(_this2)
45452          });
45453
45454          _this2.triggerPresenceUsage_(_this2.master(), media);
45455
45456          try {
45457            _this2.setupSourceBuffers_();
45458          } catch (e) {
45459            videojs$1.log.warn('Failed to create SourceBuffers', e);
45460            return _this2.mediaSource.endOfStream('decode');
45461          }
45462          _this2.setupFirstPlay();
45463
45464          _this2.trigger('selectedinitialmedia');
45465        });
45466
45467        this.masterPlaylistLoader_.on('loadedplaylist', function () {
45468          var updatedPlaylist = _this2.masterPlaylistLoader_.media();
45469
45470          if (!updatedPlaylist) {
45471            // blacklist any variants that are not supported by the browser before selecting
45472            // an initial media as the playlist selectors do not consider browser support
45473            _this2.excludeUnsupportedVariants_();
45474
45475            var selectedMedia = void 0;
45476
45477            if (_this2.enableLowInitialPlaylist) {
45478              selectedMedia = _this2.selectInitialPlaylist();
45479            }
45480
45481            if (!selectedMedia) {
45482              selectedMedia = _this2.selectPlaylist();
45483            }
45484
45485            _this2.initialMedia_ = selectedMedia;
45486            _this2.masterPlaylistLoader_.media(_this2.initialMedia_);
45487            return;
45488          }
45489
45490          if (_this2.useCueTags_) {
45491            _this2.updateAdCues_(updatedPlaylist);
45492          }
45493
45494          // TODO: Create a new event on the PlaylistLoader that signals
45495          // that the segments have changed in some way and use that to
45496          // update the SegmentLoader instead of doing it twice here and
45497          // on `mediachange`
45498          _this2.mainSegmentLoader_.playlist(updatedPlaylist, _this2.requestOptions_);
45499          _this2.updateDuration();
45500
45501          // If the player isn't paused, ensure that the segment loader is running,
45502          // as it is possible that it was temporarily stopped while waiting for
45503          // a playlist (e.g., in case the playlist errored and we re-requested it).
45504          if (!_this2.tech_.paused()) {
45505            _this2.mainSegmentLoader_.load();
45506          }
45507
45508          if (!updatedPlaylist.endList) {
45509            var addSeekableRange = function addSeekableRange() {
45510              var seekable$$1 = _this2.seekable();
45511
45512              if (seekable$$1.length !== 0) {
45513                _this2.mediaSource.addSeekableRange_(seekable$$1.start(0), seekable$$1.end(0));
45514              }
45515            };
45516
45517            if (_this2.duration() !== Infinity) {
45518              var onDurationchange = function onDurationchange() {
45519                if (_this2.duration() === Infinity) {
45520                  addSeekableRange();
45521                } else {
45522                  _this2.tech_.one('durationchange', onDurationchange);
45523                }
45524              };
45525
45526              _this2.tech_.one('durationchange', onDurationchange);
45527            } else {
45528              addSeekableRange();
45529            }
45530          }
45531        });
45532
45533        this.masterPlaylistLoader_.on('error', function () {
45534          _this2.blacklistCurrentPlaylist(_this2.masterPlaylistLoader_.error);
45535        });
45536
45537        this.masterPlaylistLoader_.on('mediachanging', function () {
45538          _this2.mainSegmentLoader_.abort();
45539          _this2.mainSegmentLoader_.pause();
45540        });
45541
45542        this.masterPlaylistLoader_.on('mediachange', function () {
45543          var media = _this2.masterPlaylistLoader_.media();
45544          var requestTimeout = _this2.masterPlaylistLoader_.targetDuration * 1.5 * 1000;
45545
45546          // If we don't have any more available playlists, we don't want to
45547          // timeout the request.
45548          if (isLowestEnabledRendition(_this2.masterPlaylistLoader_.master, _this2.masterPlaylistLoader_.media())) {
45549            _this2.requestOptions_.timeout = 0;
45550          } else {
45551            _this2.requestOptions_.timeout = requestTimeout;
45552          }
45553
45554          // TODO: Create a new event on the PlaylistLoader that signals
45555          // that the segments have changed in some way and use that to
45556          // update the SegmentLoader instead of doing it twice here and
45557          // on `loadedplaylist`
45558          _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_);
45559          _this2.mainSegmentLoader_.load();
45560
45561          _this2.tech_.trigger({
45562            type: 'mediachange',
45563            bubbles: true
45564          });
45565        });
45566
45567        this.masterPlaylistLoader_.on('playlistunchanged', function () {
45568          var updatedPlaylist = _this2.masterPlaylistLoader_.media();
45569          var playlistOutdated = _this2.stuckAtPlaylistEnd_(updatedPlaylist);
45570
45571          if (playlistOutdated) {
45572            // Playlist has stopped updating and we're stuck at its end. Try to
45573            // blacklist it and switch to another playlist in the hope that that
45574            // one is updating (and give the player a chance to re-adjust to the
45575            // safe live point).
45576            _this2.blacklistCurrentPlaylist({
45577              message: 'Playlist no longer updating.'
45578            });
45579            // useful for monitoring QoS
45580            _this2.tech_.trigger('playliststuck');
45581          }
45582        });
45583
45584        this.masterPlaylistLoader_.on('renditiondisabled', function () {
45585          _this2.tech_.trigger({ type: 'usage', name: 'hls-rendition-disabled' });
45586        });
45587        this.masterPlaylistLoader_.on('renditionenabled', function () {
45588          _this2.tech_.trigger({ type: 'usage', name: 'hls-rendition-enabled' });
45589        });
45590      }
45591
45592      /**
45593       * A helper function for triggerring presence usage events once per source
45594       *
45595       * @private
45596       */
45597
45598    }, {
45599      key: 'triggerPresenceUsage_',
45600      value: function triggerPresenceUsage_(master, media) {
45601        var mediaGroups = master.mediaGroups || {};
45602        var defaultDemuxed = true;
45603        var audioGroupKeys = Object.keys(mediaGroups.AUDIO);
45604
45605        for (var mediaGroup in mediaGroups.AUDIO) {
45606          for (var label in mediaGroups.AUDIO[mediaGroup]) {
45607            var properties = mediaGroups.AUDIO[mediaGroup][label];
45608
45609            if (!properties.uri) {
45610              defaultDemuxed = false;
45611            }
45612          }
45613        }
45614
45615        if (defaultDemuxed) {
45616          this.tech_.trigger({ type: 'usage', name: 'hls-demuxed' });
45617        }
45618
45619        if (Object.keys(mediaGroups.SUBTITLES).length) {
45620          this.tech_.trigger({ type: 'usage', name: 'hls-webvtt' });
45621        }
45622
45623        if (Hls.Playlist.isAes(media)) {
45624          this.tech_.trigger({ type: 'usage', name: 'hls-aes' });
45625        }
45626
45627        if (Hls.Playlist.isFmp4(media)) {
45628          this.tech_.trigger({ type: 'usage', name: 'hls-fmp4' });
45629        }
45630
45631        if (audioGroupKeys.length && Object.keys(mediaGroups.AUDIO[audioGroupKeys[0]]).length > 1) {
45632          this.tech_.trigger({ type: 'usage', name: 'hls-alternate-audio' });
45633        }
45634
45635        if (this.useCueTags_) {
45636          this.tech_.trigger({ type: 'usage', name: 'hls-playlist-cue-tags' });
45637        }
45638      }
45639      /**
45640       * Register event handlers on the segment loaders. A helper function
45641       * for construction time.
45642       *
45643       * @private
45644       */
45645
45646    }, {
45647      key: 'setupSegmentLoaderListeners_',
45648      value: function setupSegmentLoaderListeners_() {
45649        var _this3 = this;
45650
45651        this.mainSegmentLoader_.on('bandwidthupdate', function () {
45652          var nextPlaylist = _this3.selectPlaylist();
45653          var currentPlaylist = _this3.masterPlaylistLoader_.media();
45654          var buffered = _this3.tech_.buffered();
45655          var forwardBuffer = buffered.length ? buffered.end(buffered.length - 1) - _this3.tech_.currentTime() : 0;
45656
45657          var bufferLowWaterLine = _this3.bufferLowWaterLine();
45658
45659          // If the playlist is live, then we want to not take low water line into account.
45660          // This is because in LIVE, the player plays 3 segments from the end of the
45661          // playlist, and if `BUFFER_LOW_WATER_LINE` is greater than the duration availble
45662          // in those segments, a viewer will never experience a rendition upswitch.
45663          if (!currentPlaylist.endList ||
45664          // For the same reason as LIVE, we ignore the low water line when the VOD
45665          // duration is below the max potential low water line
45666          _this3.duration() < Config.MAX_BUFFER_LOW_WATER_LINE ||
45667          // we want to switch down to lower resolutions quickly to continue playback, but
45668          nextPlaylist.attributes.BANDWIDTH < currentPlaylist.attributes.BANDWIDTH ||
45669          // ensure we have some buffer before we switch up to prevent us running out of
45670          // buffer while loading a higher rendition.
45671          forwardBuffer >= bufferLowWaterLine) {
45672            _this3.masterPlaylistLoader_.media(nextPlaylist);
45673          }
45674
45675          _this3.tech_.trigger('bandwidthupdate');
45676        });
45677        this.mainSegmentLoader_.on('progress', function () {
45678          _this3.trigger('progress');
45679        });
45680
45681        this.mainSegmentLoader_.on('error', function () {
45682          _this3.blacklistCurrentPlaylist(_this3.mainSegmentLoader_.error());
45683        });
45684
45685        this.mainSegmentLoader_.on('syncinfoupdate', function () {
45686          _this3.onSyncInfoUpdate_();
45687        });
45688
45689        this.mainSegmentLoader_.on('timestampoffset', function () {
45690          _this3.tech_.trigger({ type: 'usage', name: 'hls-timestamp-offset' });
45691        });
45692        this.audioSegmentLoader_.on('syncinfoupdate', function () {
45693          _this3.onSyncInfoUpdate_();
45694        });
45695
45696        this.mainSegmentLoader_.on('ended', function () {
45697          _this3.onEndOfStream();
45698        });
45699
45700        this.mainSegmentLoader_.on('earlyabort', function () {
45701          _this3.blacklistCurrentPlaylist({
45702            message: 'Aborted early because there isn\'t enough bandwidth to complete the ' + 'request without rebuffering.'
45703          }, ABORT_EARLY_BLACKLIST_SECONDS);
45704        });
45705
45706        this.mainSegmentLoader_.on('reseteverything', function () {
45707          // If playing an MTS stream, a videojs.MediaSource is listening for
45708          // hls-reset to reset caption parsing state in the transmuxer
45709          _this3.tech_.trigger('hls-reset');
45710        });
45711
45712        this.mainSegmentLoader_.on('segmenttimemapping', function (event) {
45713          // If playing an MTS stream in html, a videojs.MediaSource is listening for
45714          // hls-segment-time-mapping update its internal mapping of stream to display time
45715          _this3.tech_.trigger({
45716            type: 'hls-segment-time-mapping',
45717            mapping: event.mapping
45718          });
45719        });
45720
45721        this.audioSegmentLoader_.on('ended', function () {
45722          _this3.onEndOfStream();
45723        });
45724      }
45725    }, {
45726      key: 'mediaSecondsLoaded_',
45727      value: function mediaSecondsLoaded_() {
45728        return Math.max(this.audioSegmentLoader_.mediaSecondsLoaded + this.mainSegmentLoader_.mediaSecondsLoaded);
45729      }
45730
45731      /**
45732       * Call load on our SegmentLoaders
45733       */
45734
45735    }, {
45736      key: 'load',
45737      value: function load() {
45738        this.mainSegmentLoader_.load();
45739        if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
45740          this.audioSegmentLoader_.load();
45741        }
45742        if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
45743          this.subtitleSegmentLoader_.load();
45744        }
45745      }
45746
45747      /**
45748       * Re-tune playback quality level for the current player
45749       * conditions. This method may perform destructive actions, like
45750       * removing already buffered content, to readjust the currently
45751       * active playlist quickly.
45752       *
45753       * @private
45754       */
45755
45756    }, {
45757      key: 'fastQualityChange_',
45758      value: function fastQualityChange_() {
45759        var media = this.selectPlaylist();
45760
45761        if (media !== this.masterPlaylistLoader_.media()) {
45762          this.masterPlaylistLoader_.media(media);
45763
45764          this.mainSegmentLoader_.resetLoader();
45765          // don't need to reset audio as it is reset when media changes
45766        }
45767      }
45768
45769      /**
45770       * Begin playback.
45771       */
45772
45773    }, {
45774      key: 'play',
45775      value: function play() {
45776        if (this.setupFirstPlay()) {
45777          return;
45778        }
45779
45780        if (this.tech_.ended()) {
45781          this.tech_.setCurrentTime(0);
45782        }
45783
45784        if (this.hasPlayed_()) {
45785          this.load();
45786        }
45787
45788        var seekable$$1 = this.tech_.seekable();
45789
45790        // if the viewer has paused and we fell out of the live window,
45791        // seek forward to the live point
45792        if (this.tech_.duration() === Infinity) {
45793          if (this.tech_.currentTime() < seekable$$1.start(0)) {
45794            return this.tech_.setCurrentTime(seekable$$1.end(seekable$$1.length - 1));
45795          }
45796        }
45797      }
45798
45799      /**
45800       * Seek to the latest media position if this is a live video and the
45801       * player and video are loaded and initialized.
45802       */
45803
45804    }, {
45805      key: 'setupFirstPlay',
45806      value: function setupFirstPlay() {
45807        var _this4 = this;
45808
45809        var media = this.masterPlaylistLoader_.media();
45810
45811        // Check that everything is ready to begin buffering for the first call to play
45812        //  If 1) there is no active media
45813        //     2) the player is paused
45814        //     3) the first play has already been setup
45815        // then exit early
45816        if (!media || this.tech_.paused() || this.hasPlayed_()) {
45817          return false;
45818        }
45819
45820        // when the video is a live stream
45821        if (!media.endList) {
45822          var seekable$$1 = this.seekable();
45823
45824          if (!seekable$$1.length) {
45825            // without a seekable range, the player cannot seek to begin buffering at the live
45826            // point
45827            return false;
45828          }
45829
45830          if (videojs$1.browser.IE_VERSION && this.tech_.readyState() === 0) {
45831            // IE11 throws an InvalidStateError if you try to set currentTime while the
45832            // readyState is 0, so it must be delayed until the tech fires loadedmetadata.
45833            this.tech_.one('loadedmetadata', function () {
45834              _this4.trigger('firstplay');
45835              _this4.tech_.setCurrentTime(seekable$$1.end(0));
45836              _this4.hasPlayed_ = function () {
45837                return true;
45838              };
45839            });
45840
45841            return false;
45842          }
45843
45844          // trigger firstplay to inform the source handler to ignore the next seek event
45845          this.trigger('firstplay');
45846          // seek to the live point
45847          this.tech_.setCurrentTime(seekable$$1.end(0));
45848        }
45849
45850        this.hasPlayed_ = function () {
45851          return true;
45852        };
45853        // we can begin loading now that everything is ready
45854        this.load();
45855        return true;
45856      }
45857
45858      /**
45859       * handle the sourceopen event on the MediaSource
45860       *
45861       * @private
45862       */
45863
45864    }, {
45865      key: 'handleSourceOpen_',
45866      value: function handleSourceOpen_() {
45867        // Only attempt to create the source buffer if none already exist.
45868        // handleSourceOpen is also called when we are "re-opening" a source buffer
45869        // after `endOfStream` has been called (in response to a seek for instance)
45870        try {
45871          this.setupSourceBuffers_();
45872        } catch (e) {
45873          videojs$1.log.warn('Failed to create Source Buffers', e);
45874          return this.mediaSource.endOfStream('decode');
45875        }
45876
45877        // if autoplay is enabled, begin playback. This is duplicative of
45878        // code in video.js but is required because play() must be invoked
45879        // *after* the media source has opened.
45880        if (this.tech_.autoplay()) {
45881          var playPromise = this.tech_.play();
45882
45883          // Catch/silence error when a pause interrupts a play request
45884          // on browsers which return a promise
45885          if (typeof playPromise !== 'undefined' && typeof playPromise.then === 'function') {
45886            playPromise.then(null, function (e) {});
45887          }
45888        }
45889
45890        this.trigger('sourceopen');
45891      }
45892
45893      /**
45894       * Calls endOfStream on the media source when all active stream types have called
45895       * endOfStream
45896       *
45897       * @param {string} streamType
45898       *        Stream type of the segment loader that called endOfStream
45899       * @private
45900       */
45901
45902    }, {
45903      key: 'onEndOfStream',
45904      value: function onEndOfStream() {
45905        var isEndOfStream = this.mainSegmentLoader_.ended_;
45906
45907        if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
45908          // if the audio playlist loader exists, then alternate audio is active
45909          if (!this.mainSegmentLoader_.startingMedia_ || this.mainSegmentLoader_.startingMedia_.containsVideo) {
45910            // if we do not know if the main segment loader contains video yet or if we
45911            // definitively know the main segment loader contains video, then we need to wait
45912            // for both main and audio segment loaders to call endOfStream
45913            isEndOfStream = isEndOfStream && this.audioSegmentLoader_.ended_;
45914          } else {
45915            // otherwise just rely on the audio loader
45916            isEndOfStream = this.audioSegmentLoader_.ended_;
45917          }
45918        }
45919
45920        if (isEndOfStream) {
45921          this.mediaSource.endOfStream();
45922        }
45923      }
45924
45925      /**
45926       * Check if a playlist has stopped being updated
45927       * @param {Object} playlist the media playlist object
45928       * @return {boolean} whether the playlist has stopped being updated or not
45929       */
45930
45931    }, {
45932      key: 'stuckAtPlaylistEnd_',
45933      value: function stuckAtPlaylistEnd_(playlist) {
45934        var seekable$$1 = this.seekable();
45935
45936        if (!seekable$$1.length) {
45937          // playlist doesn't have enough information to determine whether we are stuck
45938          return false;
45939        }
45940
45941        var expired = this.syncController_.getExpiredTime(playlist, this.mediaSource.duration);
45942
45943        if (expired === null) {
45944          return false;
45945        }
45946
45947        // does not use the safe live end to calculate playlist end, since we
45948        // don't want to say we are stuck while there is still content
45949        var absolutePlaylistEnd = Hls.Playlist.playlistEnd(playlist, expired);
45950        var currentTime = this.tech_.currentTime();
45951        var buffered = this.tech_.buffered();
45952
45953        if (!buffered.length) {
45954          // return true if the playhead reached the absolute end of the playlist
45955          return absolutePlaylistEnd - currentTime <= SAFE_TIME_DELTA;
45956        }
45957        var bufferedEnd = buffered.end(buffered.length - 1);
45958
45959        // return true if there is too little buffer left and buffer has reached absolute
45960        // end of playlist
45961        return bufferedEnd - currentTime <= SAFE_TIME_DELTA && absolutePlaylistEnd - bufferedEnd <= SAFE_TIME_DELTA;
45962      }
45963
45964      /**
45965       * Blacklists a playlist when an error occurs for a set amount of time
45966       * making it unavailable for selection by the rendition selection algorithm
45967       * and then forces a new playlist (rendition) selection.
45968       *
45969       * @param {Object=} error an optional error that may include the playlist
45970       * to blacklist
45971       * @param {Number=} blacklistDuration an optional number of seconds to blacklist the
45972       * playlist
45973       */
45974
45975    }, {
45976      key: 'blacklistCurrentPlaylist',
45977      value: function blacklistCurrentPlaylist() {
45978        var error = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
45979        var blacklistDuration = arguments[1];
45980
45981        var currentPlaylist = void 0;
45982        var nextPlaylist = void 0;
45983
45984        // If the `error` was generated by the playlist loader, it will contain
45985        // the playlist we were trying to load (but failed) and that should be
45986        // blacklisted instead of the currently selected playlist which is likely
45987        // out-of-date in this scenario
45988        currentPlaylist = error.playlist || this.masterPlaylistLoader_.media();
45989
45990        blacklistDuration = blacklistDuration || error.blacklistDuration || this.blacklistDuration;
45991
45992        // If there is no current playlist, then an error occurred while we were
45993        // trying to load the master OR while we were disposing of the tech
45994        if (!currentPlaylist) {
45995          this.error = error;
45996
45997          try {
45998            return this.mediaSource.endOfStream('network');
45999          } catch (e) {
46000            return this.trigger('error');
46001          }
46002        }
46003
46004        var isFinalRendition = this.masterPlaylistLoader_.master.playlists.filter(isEnabled).length === 1;
46005
46006        if (isFinalRendition) {
46007          // Never blacklisting this playlist because it's final rendition
46008          videojs$1.log.warn('Problem encountered with the current ' + 'HLS playlist. Trying again since it is the final playlist.');
46009
46010          this.tech_.trigger('retryplaylist');
46011          return this.masterPlaylistLoader_.load(isFinalRendition);
46012        }
46013        // Blacklist this playlist
46014        currentPlaylist.excludeUntil = Date.now() + blacklistDuration * 1000;
46015        this.tech_.trigger('blacklistplaylist');
46016        this.tech_.trigger({ type: 'usage', name: 'hls-rendition-blacklisted' });
46017
46018        // Select a new playlist
46019        nextPlaylist = this.selectPlaylist();
46020        videojs$1.log.warn('Problem encountered with the current HLS playlist.' + (error.message ? ' ' + error.message : '') + ' Switching to another playlist.');
46021
46022        return this.masterPlaylistLoader_.media(nextPlaylist);
46023      }
46024
46025      /**
46026       * Pause all segment loaders
46027       */
46028
46029    }, {
46030      key: 'pauseLoading',
46031      value: function pauseLoading() {
46032        this.mainSegmentLoader_.pause();
46033        if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
46034          this.audioSegmentLoader_.pause();
46035        }
46036        if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
46037          this.subtitleSegmentLoader_.pause();
46038        }
46039      }
46040
46041      /**
46042       * set the current time on all segment loaders
46043       *
46044       * @param {TimeRange} currentTime the current time to set
46045       * @return {TimeRange} the current time
46046       */
46047
46048    }, {
46049      key: 'setCurrentTime',
46050      value: function setCurrentTime(currentTime) {
46051        var buffered = findRange(this.tech_.buffered(), currentTime);
46052
46053        if (!(this.masterPlaylistLoader_ && this.masterPlaylistLoader_.media())) {
46054          // return immediately if the metadata is not ready yet
46055          return 0;
46056        }
46057
46058        // it's clearly an edge-case but don't thrown an error if asked to
46059        // seek within an empty playlist
46060        if (!this.masterPlaylistLoader_.media().segments) {
46061          return 0;
46062        }
46063
46064        // In flash playback, the segment loaders should be reset on every seek, even
46065        // in buffer seeks. If the seek location is already buffered, continue buffering as
46066        // usual
46067        // TODO: redo this comment
46068        if (buffered && buffered.length) {
46069          return currentTime;
46070        }
46071
46072        // cancel outstanding requests so we begin buffering at the new
46073        // location
46074        this.mainSegmentLoader_.resetEverything();
46075        this.mainSegmentLoader_.abort();
46076        if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
46077          this.audioSegmentLoader_.resetEverything();
46078          this.audioSegmentLoader_.abort();
46079        }
46080        if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
46081          this.subtitleSegmentLoader_.resetEverything();
46082          this.subtitleSegmentLoader_.abort();
46083        }
46084
46085        // start segment loader loading in case they are paused
46086        this.load();
46087      }
46088
46089      /**
46090       * get the current duration
46091       *
46092       * @return {TimeRange} the duration
46093       */
46094
46095    }, {
46096      key: 'duration',
46097      value: function duration$$1() {
46098        if (!this.masterPlaylistLoader_) {
46099          return 0;
46100        }
46101
46102        if (this.mediaSource) {
46103          return this.mediaSource.duration;
46104        }
46105
46106        return Hls.Playlist.duration(this.masterPlaylistLoader_.media());
46107      }
46108
46109      /**
46110       * check the seekable range
46111       *
46112       * @return {TimeRange} the seekable range
46113       */
46114
46115    }, {
46116      key: 'seekable',
46117      value: function seekable$$1() {
46118        return this.seekable_;
46119      }
46120    }, {
46121      key: 'onSyncInfoUpdate_',
46122      value: function onSyncInfoUpdate_() {
46123        var mainSeekable = void 0;
46124        var audioSeekable = void 0;
46125
46126        if (!this.masterPlaylistLoader_) {
46127          return;
46128        }
46129
46130        var media = this.masterPlaylistLoader_.media();
46131
46132        if (!media) {
46133          return;
46134        }
46135
46136        var expired = this.syncController_.getExpiredTime(media, this.mediaSource.duration);
46137
46138        if (expired === null) {
46139          // not enough information to update seekable
46140          return;
46141        }
46142
46143        mainSeekable = Hls.Playlist.seekable(media, expired);
46144
46145        if (mainSeekable.length === 0) {
46146          return;
46147        }
46148
46149        if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
46150          media = this.mediaTypes_.AUDIO.activePlaylistLoader.media();
46151          expired = this.syncController_.getExpiredTime(media, this.mediaSource.duration);
46152
46153          if (expired === null) {
46154            return;
46155          }
46156
46157          audioSeekable = Hls.Playlist.seekable(media, expired);
46158
46159          if (audioSeekable.length === 0) {
46160            return;
46161          }
46162        }
46163
46164        if (!audioSeekable) {
46165          // seekable has been calculated based on buffering video data so it
46166          // can be returned directly
46167          this.seekable_ = mainSeekable;
46168        } else if (audioSeekable.start(0) > mainSeekable.end(0) || mainSeekable.start(0) > audioSeekable.end(0)) {
46169          // seekables are pretty far off, rely on main
46170          this.seekable_ = mainSeekable;
46171        } else {
46172          this.seekable_ = videojs$1.createTimeRanges([[audioSeekable.start(0) > mainSeekable.start(0) ? audioSeekable.start(0) : mainSeekable.start(0), audioSeekable.end(0) < mainSeekable.end(0) ? audioSeekable.end(0) : mainSeekable.end(0)]]);
46173        }
46174
46175        this.logger_('seekable updated [' + printableRange(this.seekable_) + ']');
46176
46177        this.tech_.trigger('seekablechanged');
46178      }
46179
46180      /**
46181       * Update the player duration
46182       */
46183
46184    }, {
46185      key: 'updateDuration',
46186      value: function updateDuration() {
46187        var _this5 = this;
46188
46189        var oldDuration = this.mediaSource.duration;
46190        var newDuration = Hls.Playlist.duration(this.masterPlaylistLoader_.media());
46191        var buffered = this.tech_.buffered();
46192        var setDuration = function setDuration() {
46193          _this5.mediaSource.duration = newDuration;
46194          _this5.tech_.trigger('durationchange');
46195
46196          _this5.mediaSource.removeEventListener('sourceopen', setDuration);
46197        };
46198
46199        if (buffered.length > 0) {
46200          newDuration = Math.max(newDuration, buffered.end(buffered.length - 1));
46201        }
46202
46203        // if the duration has changed, invalidate the cached value
46204        if (oldDuration !== newDuration) {
46205          // update the duration
46206          if (this.mediaSource.readyState !== 'open') {
46207            this.mediaSource.addEventListener('sourceopen', setDuration);
46208          } else {
46209            setDuration();
46210          }
46211        }
46212      }
46213
46214      /**
46215       * dispose of the MasterPlaylistController and everything
46216       * that it controls
46217       */
46218
46219    }, {
46220      key: 'dispose',
46221      value: function dispose() {
46222        var _this6 = this;
46223
46224        this.decrypter_.terminate();
46225        this.masterPlaylistLoader_.dispose();
46226        this.mainSegmentLoader_.dispose();
46227
46228        ['AUDIO', 'SUBTITLES'].forEach(function (type) {
46229          var groups = _this6.mediaTypes_[type].groups;
46230
46231          for (var id in groups) {
46232            groups[id].forEach(function (group) {
46233              if (group.playlistLoader) {
46234                group.playlistLoader.dispose();
46235              }
46236            });
46237          }
46238        });
46239
46240        this.audioSegmentLoader_.dispose();
46241        this.subtitleSegmentLoader_.dispose();
46242      }
46243
46244      /**
46245       * return the master playlist object if we have one
46246       *
46247       * @return {Object} the master playlist object that we parsed
46248       */
46249
46250    }, {
46251      key: 'master',
46252      value: function master() {
46253        return this.masterPlaylistLoader_.master;
46254      }
46255
46256      /**
46257       * return the currently selected playlist
46258       *
46259       * @return {Object} the currently selected playlist object that we parsed
46260       */
46261
46262    }, {
46263      key: 'media',
46264      value: function media() {
46265        // playlist loader will not return media if it has not been fully loaded
46266        return this.masterPlaylistLoader_.media() || this.initialMedia_;
46267      }
46268
46269      /**
46270       * setup our internal source buffers on our segment Loaders
46271       *
46272       * @private
46273       */
46274
46275    }, {
46276      key: 'setupSourceBuffers_',
46277      value: function setupSourceBuffers_() {
46278        var media = this.masterPlaylistLoader_.media();
46279        var mimeTypes = void 0;
46280
46281        // wait until a media playlist is available and the Media Source is
46282        // attached
46283        if (!media || this.mediaSource.readyState !== 'open') {
46284          return;
46285        }
46286
46287        mimeTypes = mimeTypesForPlaylist(this.masterPlaylistLoader_.master, media);
46288        if (mimeTypes.length < 1) {
46289          this.error = 'No compatible SourceBuffer configuration for the variant stream:' + media.resolvedUri;
46290          return this.mediaSource.endOfStream('decode');
46291        }
46292
46293        this.configureLoaderMimeTypes_(mimeTypes);
46294        // exclude any incompatible variant streams from future playlist
46295        // selection
46296        this.excludeIncompatibleVariants_(media);
46297      }
46298    }, {
46299      key: 'configureLoaderMimeTypes_',
46300      value: function configureLoaderMimeTypes_(mimeTypes) {
46301        // If the content is demuxed, we can't start appending segments to a source buffer
46302        // until both source buffers are set up, or else the browser may not let us add the
46303        // second source buffer (it will assume we are playing either audio only or video
46304        // only).
46305        var sourceBufferEmitter =
46306        // If there is more than one mime type
46307        mimeTypes.length > 1 &&
46308        // and the first mime type does not have muxed video and audio
46309        mimeTypes[0].indexOf(',') === -1 &&
46310        // and the two mime types are different (they can be the same in the case of audio
46311        // only with alternate audio)
46312        mimeTypes[0] !== mimeTypes[1] ?
46313        // then we want to wait on the second source buffer
46314        new videojs$1.EventTarget() :
46315        // otherwise there is no need to wait as the content is either audio only,
46316        // video only, or muxed content.
46317        null;
46318
46319        this.mainSegmentLoader_.mimeType(mimeTypes[0], sourceBufferEmitter);
46320        if (mimeTypes[1]) {
46321          this.audioSegmentLoader_.mimeType(mimeTypes[1], sourceBufferEmitter);
46322        }
46323      }
46324
46325      /**
46326       * Blacklists playlists with codecs that are unsupported by the browser.
46327       */
46328
46329    }, {
46330      key: 'excludeUnsupportedVariants_',
46331      value: function excludeUnsupportedVariants_() {
46332        this.master().playlists.forEach(function (variant) {
46333          if (variant.attributes.CODECS && window_1.MediaSource && window_1.MediaSource.isTypeSupported && !window_1.MediaSource.isTypeSupported('video/mp4; codecs="' + mapLegacyAvcCodecs(variant.attributes.CODECS) + '"')) {
46334            variant.excludeUntil = Infinity;
46335          }
46336        });
46337      }
46338
46339      /**
46340       * Blacklist playlists that are known to be codec or
46341       * stream-incompatible with the SourceBuffer configuration. For
46342       * instance, Media Source Extensions would cause the video element to
46343       * stall waiting for video data if you switched from a variant with
46344       * video and audio to an audio-only one.
46345       *
46346       * @param {Object} media a media playlist compatible with the current
46347       * set of SourceBuffers. Variants in the current master playlist that
46348       * do not appear to have compatible codec or stream configurations
46349       * will be excluded from the default playlist selection algorithm
46350       * indefinitely.
46351       * @private
46352       */
46353
46354    }, {
46355      key: 'excludeIncompatibleVariants_',
46356      value: function excludeIncompatibleVariants_(media) {
46357        var codecCount = 2;
46358        var videoCodec = null;
46359        var codecs = void 0;
46360
46361        if (media.attributes.CODECS) {
46362          codecs = parseCodecs(media.attributes.CODECS);
46363          videoCodec = codecs.videoCodec;
46364          codecCount = codecs.codecCount;
46365        }
46366
vendor: 49,600 bytes, lines 46367-47810
46367        this.master().playlists.forEach(function (variant) {
46368          var variantCodecs = {
46369            codecCount: 2,
46370            videoCodec: null
46371          };
46372
46373          if (variant.attributes.CODECS) {
46374            variantCodecs = parseCodecs(variant.attributes.CODECS);
46375          }
46376
46377          // if the streams differ in the presence or absence of audio or
46378          // video, they are incompatible
46379          if (variantCodecs.codecCount !== codecCount) {
46380            variant.excludeUntil = Infinity;
46381          }
46382
46383          // if h.264 is specified on the current playlist, some flavor of
46384          // it must be specified on all compatible variants
46385          if (variantCodecs.videoCodec !== videoCodec) {
46386            variant.excludeUntil = Infinity;
46387          }
46388        });
46389      }
46390    }, {
46391      key: 'updateAdCues_',
46392      value: function updateAdCues_(media) {
46393        var offset = 0;
46394        var seekable$$1 = this.seekable();
46395
46396        if (seekable$$1.length) {
46397          offset = seekable$$1.start(0);
46398        }
46399
46400        updateAdCues(media, this.cueTagsTrack_, offset);
46401      }
46402
46403      /**
46404       * Calculates the desired forward buffer length based on current time
46405       *
46406       * @return {Number} Desired forward buffer length in seconds
46407       */
46408
46409    }, {
46410      key: 'goalBufferLength',
46411      value: function goalBufferLength() {
46412        var currentTime = this.tech_.currentTime();
46413        var initial = Config.GOAL_BUFFER_LENGTH;
46414        var rate = Config.GOAL_BUFFER_LENGTH_RATE;
46415        var max = Math.max(initial, Config.MAX_GOAL_BUFFER_LENGTH);
46416
46417        return Math.min(initial + currentTime * rate, max);
46418      }
46419
46420      /**
46421       * Calculates the desired buffer low water line based on current time
46422       *
46423       * @return {Number} Desired buffer low water line in seconds
46424       */
46425
46426    }, {
46427      key: 'bufferLowWaterLine',
46428      value: function bufferLowWaterLine() {
46429        var currentTime = this.tech_.currentTime();
46430        var initial = Config.BUFFER_LOW_WATER_LINE;
46431        var rate = Config.BUFFER_LOW_WATER_LINE_RATE;
46432        var max = Math.max(initial, Config.MAX_BUFFER_LOW_WATER_LINE);
46433
46434        return Math.min(initial + currentTime * rate, max);
46435      }
46436    }]);
46437    return MasterPlaylistController;
46438  }(videojs$1.EventTarget);
46439
46440  /**
46441   * Returns a function that acts as the Enable/disable playlist function.
46442   *
46443   * @param {PlaylistLoader} loader - The master playlist loader
46444   * @param {String} playlistUri - uri of the playlist
46445   * @param {Function} changePlaylistFn - A function to be called after a
46446   * playlist's enabled-state has been changed. Will NOT be called if a
46447   * playlist's enabled-state is unchanged
46448   * @param {Boolean=} enable - Value to set the playlist enabled-state to
46449   * or if undefined returns the current enabled-state for the playlist
46450   * @return {Function} Function for setting/getting enabled
46451   */
46452  var enableFunction = function enableFunction(loader, playlistUri, changePlaylistFn) {
46453    return function (enable) {
46454      var playlist = loader.master.playlists[playlistUri];
46455      var incompatible = isIncompatible(playlist);
46456      var currentlyEnabled = isEnabled(playlist);
46457
46458      if (typeof enable === 'undefined') {
46459        return currentlyEnabled;
46460      }
46461
46462      if (enable) {
46463        delete playlist.disabled;
46464      } else {
46465        playlist.disabled = true;
46466      }
46467
46468      if (enable !== currentlyEnabled && !incompatible) {
46469        // Ensure the outside world knows about our changes
46470        changePlaylistFn();
46471        if (enable) {
46472          loader.trigger('renditionenabled');
46473        } else {
46474          loader.trigger('renditiondisabled');
46475        }
46476      }
46477      return enable;
46478    };
46479  };
46480
46481  /**
46482   * The representation object encapsulates the publicly visible information
46483   * in a media playlist along with a setter/getter-type function (enabled)
46484   * for changing the enabled-state of a particular playlist entry
46485   *
46486   * @class Representation
46487   */
46488
46489  var Representation = function Representation(hlsHandler, playlist, id) {
46490    classCallCheck$3(this, Representation);
46491
46492    // Get a reference to a bound version of fastQualityChange_
46493    var fastChangeFunction = hlsHandler.masterPlaylistController_.fastQualityChange_.bind(hlsHandler.masterPlaylistController_);
46494
46495    // some playlist attributes are optional
46496    if (playlist.attributes.RESOLUTION) {
46497      var resolution = playlist.attributes.RESOLUTION;
46498
46499      this.width = resolution.width;
46500      this.height = resolution.height;
46501    }
46502
46503    this.bandwidth = playlist.attributes.BANDWIDTH;
46504
46505    // The id is simply the ordinality of the media playlist
46506    // within the master playlist
46507    this.id = id;
46508
46509    // Partially-apply the enableFunction to create a playlist-
46510    // specific variant
46511    this.enabled = enableFunction(hlsHandler.playlists, playlist.uri, fastChangeFunction);
46512  };
46513
46514  /**
46515   * A mixin function that adds the `representations` api to an instance
46516   * of the HlsHandler class
46517   * @param {HlsHandler} hlsHandler - An instance of HlsHandler to add the
46518   * representation API into
46519   */
46520
46521  var renditionSelectionMixin = function renditionSelectionMixin(hlsHandler) {
46522    var playlists = hlsHandler.playlists;
46523
46524    // Add a single API-specific function to the HlsHandler instance
46525    hlsHandler.representations = function () {
46526      return playlists.master.playlists.filter(function (media) {
46527        return !isIncompatible(media);
46528      }).map(function (e, i) {
46529        return new Representation(hlsHandler, e, e.uri);
46530      });
46531    };
46532  };
46533
46534  /**
46535   * @file playback-watcher.js
46536   *
46537   * Playback starts, and now my watch begins. It shall not end until my death. I shall
46538   * take no wait, hold no uncleared timeouts, father no bad seeks. I shall wear no crowns
46539   * and win no glory. I shall live and die at my post. I am the corrector of the underflow.
46540   * I am the watcher of gaps. I am the shield that guards the realms of seekable. I pledge
46541   * my life and honor to the Playback Watch, for this Player and all the Players to come.
46542   */
46543
46544  // Set of events that reset the playback-watcher time check logic and clear the timeout
46545  var timerCancelEvents = ['seeking', 'seeked', 'pause', 'playing', 'error'];
46546
46547  /**
46548   * @class PlaybackWatcher
46549   */
46550
46551  var PlaybackWatcher = function () {
46552    /**
46553     * Represents an PlaybackWatcher object.
46554     * @constructor
46555     * @param {object} options an object that includes the tech and settings
46556     */
46557    function PlaybackWatcher(options) {
46558      var _this = this;
46559
46560      classCallCheck$3(this, PlaybackWatcher);
46561
46562      this.tech_ = options.tech;
46563      this.seekable = options.seekable;
46564
46565      this.consecutiveUpdates = 0;
46566      this.lastRecordedTime = null;
46567      this.timer_ = null;
46568      this.checkCurrentTimeTimeout_ = null;
46569      this.logger_ = logger('PlaybackWatcher');
46570
46571      this.logger_('initialize');
46572
46573      var canPlayHandler = function canPlayHandler() {
46574        return _this.monitorCurrentTime_();
46575      };
46576      var waitingHandler = function waitingHandler() {
46577        return _this.techWaiting_();
46578      };
46579      var cancelTimerHandler = function cancelTimerHandler() {
46580        return _this.cancelTimer_();
46581      };
46582      var fixesBadSeeksHandler = function fixesBadSeeksHandler() {
46583        return _this.fixesBadSeeks_();
46584      };
46585
46586      this.tech_.on('seekablechanged', fixesBadSeeksHandler);
46587      this.tech_.on('waiting', waitingHandler);
46588      this.tech_.on(timerCancelEvents, cancelTimerHandler);
46589      this.tech_.on('canplay', canPlayHandler);
46590
46591      // Define the dispose function to clean up our events
46592      this.dispose = function () {
46593        _this.logger_('dispose');
46594        _this.tech_.off('seekablechanged', fixesBadSeeksHandler);
46595        _this.tech_.off('waiting', waitingHandler);
46596        _this.tech_.off(timerCancelEvents, cancelTimerHandler);
46597        _this.tech_.off('canplay', canPlayHandler);
46598        if (_this.checkCurrentTimeTimeout_) {
46599          window_1.clearTimeout(_this.checkCurrentTimeTimeout_);
46600        }
46601        _this.cancelTimer_();
46602      };
46603    }
46604
46605    /**
46606     * Periodically check current time to see if playback stopped
46607     *
46608     * @private
46609     */
46610
46611    createClass$2(PlaybackWatcher, [{
46612      key: 'monitorCurrentTime_',
46613      value: function monitorCurrentTime_() {
46614        this.checkCurrentTime_();
46615
46616        if (this.checkCurrentTimeTimeout_) {
46617          window_1.clearTimeout(this.checkCurrentTimeTimeout_);
46618        }
46619
46620        // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
46621        this.checkCurrentTimeTimeout_ = window_1.setTimeout(this.monitorCurrentTime_.bind(this), 250);
46622      }
46623
46624      /**
46625       * The purpose of this function is to emulate the "waiting" event on
46626       * browsers that do not emit it when they are waiting for more
46627       * data to continue playback
46628       *
46629       * @private
46630       */
46631
46632    }, {
46633      key: 'checkCurrentTime_',
46634      value: function checkCurrentTime_() {
46635        if (this.tech_.seeking() && this.fixesBadSeeks_()) {
46636          this.consecutiveUpdates = 0;
46637          this.lastRecordedTime = this.tech_.currentTime();
46638          return;
46639        }
46640
46641        if (this.tech_.paused() || this.tech_.seeking()) {
46642          return;
46643        }
46644
46645        var currentTime = this.tech_.currentTime();
46646        var buffered = this.tech_.buffered();
46647
46648        if (this.lastRecordedTime === currentTime && (!buffered.length || currentTime + SAFE_TIME_DELTA >= buffered.end(buffered.length - 1))) {
46649          // If current time is at the end of the final buffered region, then any playback
46650          // stall is most likely caused by buffering in a low bandwidth environment. The tech
46651          // should fire a `waiting` event in this scenario, but due to browser and tech
46652          // inconsistencies. Calling `techWaiting_` here allows us to simulate
46653          // responding to a native `waiting` event when the tech fails to emit one.
46654          return this.techWaiting_();
46655        }
46656
46657        if (this.consecutiveUpdates >= 5 && currentTime === this.lastRecordedTime) {
46658          this.consecutiveUpdates++;
46659          this.waiting_();
46660        } else if (currentTime === this.lastRecordedTime) {
46661          this.consecutiveUpdates++;
46662        } else {
46663          this.consecutiveUpdates = 0;
46664          this.lastRecordedTime = currentTime;
46665        }
46666      }
46667
46668      /**
46669       * Cancels any pending timers and resets the 'timeupdate' mechanism
46670       * designed to detect that we are stalled
46671       *
46672       * @private
46673       */
46674
46675    }, {
46676      key: 'cancelTimer_',
46677      value: function cancelTimer_() {
46678        this.consecutiveUpdates = 0;
46679
46680        if (this.timer_) {
46681          this.logger_('cancelTimer_');
46682          clearTimeout(this.timer_);
46683        }
46684
46685        this.timer_ = null;
46686      }
46687
46688      /**
46689       * Fixes situations where there's a bad seek
46690       *
46691       * @return {Boolean} whether an action was taken to fix the seek
46692       * @private
46693       */
46694
46695    }, {
46696      key: 'fixesBadSeeks_',
46697      value: function fixesBadSeeks_() {
46698        var seeking = this.tech_.seeking();
46699        var seekable = this.seekable();
46700        var currentTime = this.tech_.currentTime();
46701        var seekTo = void 0;
46702
46703        if (seeking && this.afterSeekableWindow_(seekable, currentTime)) {
46704          var seekableEnd = seekable.end(seekable.length - 1);
46705
46706          // sync to live point (if VOD, our seekable was updated and we're simply adjusting)
46707          seekTo = seekableEnd;
46708        }
46709
46710        if (seeking && this.beforeSeekableWindow_(seekable, currentTime)) {
46711          var seekableStart = seekable.start(0);
46712
46713          // sync to the beginning of the live window
46714          // provide a buffer of .1 seconds to handle rounding/imprecise numbers
46715          seekTo = seekableStart + SAFE_TIME_DELTA;
46716        }
46717
46718        if (typeof seekTo !== 'undefined') {
46719          this.logger_('Trying to seek outside of seekable at time ' + currentTime + ' with ' + ('seekable range ' + printableRange(seekable) + '. Seeking to ') + (seekTo + '.'));
46720
46721          this.tech_.setCurrentTime(seekTo);
46722          return true;
46723        }
46724
46725        return false;
46726      }
46727
46728      /**
46729       * Handler for situations when we determine the player is waiting.
46730       *
46731       * @private
46732       */
46733
46734    }, {
46735      key: 'waiting_',
46736      value: function waiting_() {
46737        if (this.techWaiting_()) {
46738          return;
46739        }
46740
46741        // All tech waiting checks failed. Use last resort correction
46742        var currentTime = this.tech_.currentTime();
46743        var buffered = this.tech_.buffered();
46744        var currentRange = findRange(buffered, currentTime);
46745
46746        // Sometimes the player can stall for unknown reasons within a contiguous buffered
46747        // region with no indication that anything is amiss (seen in Firefox). Seeking to
46748        // currentTime is usually enough to kickstart the player. This checks that the player
46749        // is currently within a buffered region before attempting a corrective seek.
46750        // Chrome does not appear to continue `timeupdate` events after a `waiting` event
46751        // until there is ~ 3 seconds of forward buffer available. PlaybackWatcher should also
46752        // make sure there is ~3 seconds of forward buffer before taking any corrective action
46753        // to avoid triggering an `unknownwaiting` event when the network is slow.
46754        if (currentRange.length && currentTime + 3 <= currentRange.end(0)) {
46755          this.cancelTimer_();
46756          this.tech_.setCurrentTime(currentTime);
46757
46758          this.logger_('Stopped at ' + currentTime + ' while inside a buffered region ' + ('[' + currentRange.start(0) + ' -> ' + currentRange.end(0) + ']. Attempting to resume ') + 'playback by seeking to the current time.');
46759
46760          // unknown waiting corrections may be useful for monitoring QoS
46761          this.tech_.trigger({ type: 'usage', name: 'hls-unknown-waiting' });
46762          return;
46763        }
46764      }
46765
46766      /**
46767       * Handler for situations when the tech fires a `waiting` event
46768       *
46769       * @return {Boolean}
46770       *         True if an action (or none) was needed to correct the waiting. False if no
46771       *         checks passed
46772       * @private
46773       */
46774
46775    }, {
46776      key: 'techWaiting_',
46777      value: function techWaiting_() {
46778        var seekable = this.seekable();
46779        var currentTime = this.tech_.currentTime();
46780
46781        if (this.tech_.seeking() && this.fixesBadSeeks_()) {
46782          // Tech is seeking or bad seek fixed, no action needed
46783          return true;
46784        }
46785
46786        if (this.tech_.seeking() || this.timer_ !== null) {
46787          // Tech is seeking or already waiting on another action, no action needed
46788          return true;
46789        }
46790
46791        if (this.beforeSeekableWindow_(seekable, currentTime)) {
46792          var livePoint = seekable.end(seekable.length - 1);
46793
46794          this.logger_('Fell out of live window at time ' + currentTime + '. Seeking to ' + ('live point (seekable end) ' + livePoint));
46795          this.cancelTimer_();
46796          this.tech_.setCurrentTime(livePoint);
46797
46798          // live window resyncs may be useful for monitoring QoS
46799          this.tech_.trigger({ type: 'usage', name: 'hls-live-resync' });
46800          return true;
46801        }
46802
46803        var buffered = this.tech_.buffered();
46804        var nextRange = findNextRange(buffered, currentTime);
46805
46806        if (this.videoUnderflow_(nextRange, buffered, currentTime)) {
46807          // Even though the video underflowed and was stuck in a gap, the audio overplayed
46808          // the gap, leading currentTime into a buffered range. Seeking to currentTime
46809          // allows the video to catch up to the audio position without losing any audio
46810          // (only suffering ~3 seconds of frozen video and a pause in audio playback).
46811          this.cancelTimer_();
46812          this.tech_.setCurrentTime(currentTime);
46813
46814          // video underflow may be useful for monitoring QoS
46815          this.tech_.trigger({ type: 'usage', name: 'hls-video-underflow' });
46816          return true;
46817        }
46818
46819        // check for gap
46820        if (nextRange.length > 0) {
46821          var difference = nextRange.start(0) - currentTime;
46822
46823          this.logger_('Stopped at ' + currentTime + ', setting timer for ' + difference + ', seeking ' + ('to ' + nextRange.start(0)));
46824
46825          this.timer_ = setTimeout(this.skipTheGap_.bind(this), difference * 1000, currentTime);
46826          return true;
46827        }
46828
46829        // All checks failed. Returning false to indicate failure to correct waiting
46830        return false;
46831      }
46832    }, {
46833      key: 'afterSeekableWindow_',
46834      value: function afterSeekableWindow_(seekable, currentTime) {
46835        if (!seekable.length) {
46836          // we can't make a solid case if there's no seekable, default to false
46837          return false;
46838        }
46839
46840        if (currentTime > seekable.end(seekable.length - 1) + SAFE_TIME_DELTA) {
46841          return true;
46842        }
46843
46844        return false;
46845      }
46846    }, {
46847      key: 'beforeSeekableWindow_',
46848      value: function beforeSeekableWindow_(seekable, currentTime) {
46849        if (seekable.length &&
46850        // can't fall before 0 and 0 seekable start identifies VOD stream
46851        seekable.start(0) > 0 && currentTime < seekable.start(0) - SAFE_TIME_DELTA) {
46852          return true;
46853        }
46854
46855        return false;
46856      }
46857    }, {
46858      key: 'videoUnderflow_',
46859      value: function videoUnderflow_(nextRange, buffered, currentTime) {
46860        if (nextRange.length === 0) {
46861          // Even if there is no available next range, there is still a possibility we are
46862          // stuck in a gap due to video underflow.
46863          var gap = this.gapFromVideoUnderflow_(buffered, currentTime);
46864
46865          if (gap) {
46866            this.logger_('Encountered a gap in video from ' + gap.start + ' to ' + gap.end + '. ' + ('Seeking to current time ' + currentTime));
46867
46868            return true;
46869          }
46870        }
46871
46872        return false;
46873      }
46874
46875      /**
46876       * Timer callback. If playback still has not proceeded, then we seek
46877       * to the start of the next buffered region.
46878       *
46879       * @private
46880       */
46881
46882    }, {
46883      key: 'skipTheGap_',
46884      value: function skipTheGap_(scheduledCurrentTime) {
46885        var buffered = this.tech_.buffered();
46886        var currentTime = this.tech_.currentTime();
46887        var nextRange = findNextRange(buffered, currentTime);
46888
46889        this.cancelTimer_();
46890
46891        if (nextRange.length === 0 || currentTime !== scheduledCurrentTime) {
46892          return;
46893        }
46894
46895        this.logger_('skipTheGap_:', 'currentTime:', currentTime, 'scheduled currentTime:', scheduledCurrentTime, 'nextRange start:', nextRange.start(0));
46896
46897        // only seek if we still have not played
46898        this.tech_.setCurrentTime(nextRange.start(0) + TIME_FUDGE_FACTOR);
46899
46900        this.tech_.trigger({ type: 'usage', name: 'hls-gap-skip' });
46901      }
46902    }, {
46903      key: 'gapFromVideoUnderflow_',
46904      value: function gapFromVideoUnderflow_(buffered, currentTime) {
46905        // At least in Chrome, if there is a gap in the video buffer, the audio will continue
46906        // playing for ~3 seconds after the video gap starts. This is done to account for
46907        // video buffer underflow/underrun (note that this is not done when there is audio
46908        // buffer underflow/underrun -- in that case the video will stop as soon as it
46909        // encounters the gap, as audio stalls are more noticeable/jarring to a user than
46910        // video stalls). The player's time will reflect the playthrough of audio, so the
46911        // time will appear as if we are in a buffered region, even if we are stuck in a
46912        // "gap."
46913        //
46914        // Example:
46915        // video buffer:   0 => 10.1, 10.2 => 20
46916        // audio buffer:   0 => 20
46917        // overall buffer: 0 => 10.1, 10.2 => 20
46918        // current time: 13
46919        //
46920        // Chrome's video froze at 10 seconds, where the video buffer encountered the gap,
46921        // however, the audio continued playing until it reached ~3 seconds past the gap
46922        // (13 seconds), at which point it stops as well. Since current time is past the
46923        // gap, findNextRange will return no ranges.
46924        //
46925        // To check for this issue, we see if there is a gap that starts somewhere within
46926        // a 3 second range (3 seconds +/- 1 second) back from our current time.
46927        var gaps = findGaps(buffered);
46928
46929        for (var i = 0; i < gaps.length; i++) {
46930          var start = gaps.start(i);
46931          var end = gaps.end(i);
46932
46933          // gap is starts no more than 4 seconds back
46934          if (currentTime - start < 4 && currentTime - start > 2) {
46935            return {
46936              start: start,
46937              end: end
46938            };
46939          }
46940        }
46941
46942        return null;
46943      }
46944    }]);
46945    return PlaybackWatcher;
46946  }();
46947
46948  var defaultOptions = {
46949    errorInterval: 30,
46950    getSource: function getSource(next) {
46951      var tech = this.tech({ IWillNotUseThisInPlugins: true });
46952      var sourceObj = tech.currentSource_;
46953
46954      return next(sourceObj);
46955    }
46956  };
46957
46958  /**
46959   * Main entry point for the plugin
46960   *
46961   * @param {Player} player a reference to a videojs Player instance
46962   * @param {Object} [options] an object with plugin options
46963   * @private
46964   */
46965  var initPlugin = function initPlugin(player, options) {
46966    var lastCalled = 0;
46967    var seekTo = 0;
46968    var localOptions = videojs$1.mergeOptions(defaultOptions, options);
46969
46970    player.ready(function () {
46971      player.trigger({ type: 'usage', name: 'hls-error-reload-initialized' });
46972    });
46973
46974    /**
46975     * Player modifications to perform that must wait until `loadedmetadata`
46976     * has been triggered
46977     *
46978     * @private
46979     */
46980    var loadedMetadataHandler = function loadedMetadataHandler() {
46981      if (seekTo) {
46982        player.currentTime(seekTo);
46983      }
46984    };
46985
46986    /**
46987     * Set the source on the player element, play, and seek if necessary
46988     *
46989     * @param {Object} sourceObj An object specifying the source url and mime-type to play
46990     * @private
46991     */
46992    var setSource = function setSource(sourceObj) {
46993      if (sourceObj === null || sourceObj === undefined) {
46994        return;
46995      }
46996      seekTo = player.duration() !== Infinity && player.currentTime() || 0;
46997
46998      player.one('loadedmetadata', loadedMetadataHandler);
46999
47000      player.src(sourceObj);
47001      player.trigger({ type: 'usage', name: 'hls-error-reload' });
47002      player.play();
47003    };
47004
47005    /**
47006     * Attempt to get a source from either the built-in getSource function
47007     * or a custom function provided via the options
47008     *
47009     * @private
47010     */
47011    var errorHandler = function errorHandler() {
47012      // Do not attempt to reload the source if a source-reload occurred before
47013      // 'errorInterval' time has elapsed since the last source-reload
47014      if (Date.now() - lastCalled < localOptions.errorInterval * 1000) {
47015        player.trigger({ type: 'usage', name: 'hls-error-reload-canceled' });
47016        return;
47017      }
47018
47019      if (!localOptions.getSource || typeof localOptions.getSource !== 'function') {
47020        videojs$1.log.error('ERROR: reloadSourceOnError - The option getSource must be a function!');
47021        return;
47022      }
47023      lastCalled = Date.now();
47024
47025      return localOptions.getSource.call(player, setSource);
47026    };
47027
47028    /**
47029     * Unbind any event handlers that were bound by the plugin
47030     *
47031     * @private
47032     */
47033    var cleanupEvents = function cleanupEvents() {
47034      player.off('loadedmetadata', loadedMetadataHandler);
47035      player.off('error', errorHandler);
47036      player.off('dispose', cleanupEvents);
47037    };
47038
47039    /**
47040     * Cleanup before re-initializing the plugin
47041     *
47042     * @param {Object} [newOptions] an object with plugin options
47043     * @private
47044     */
47045    var reinitPlugin = function reinitPlugin(newOptions) {
47046      cleanupEvents();
47047      initPlugin(player, newOptions);
47048    };
47049
47050    player.on('error', errorHandler);
47051    player.on('dispose', cleanupEvents);
47052
47053    // Overwrite the plugin function so that we can correctly cleanup before
47054    // initializing the plugin
47055    player.reloadSourceOnError = reinitPlugin;
47056  };
47057
47058  /**
47059   * Reload the source when an error is detected as long as there
47060   * wasn't an error previously within the last 30 seconds
47061   *
47062   * @param {Object} [options] an object with plugin options
47063   */
47064  var reloadSourceOnError = function reloadSourceOnError(options) {
47065    initPlugin(this, options);
47066  };
47067
47068  var version$3 = "1.0.2";
47069
47070  /**
47071   * @file videojs-http-streaming.js
47072   *
47073   * The main file for the HLS project.
47074   * License: https://github.com/videojs/videojs-http-streaming/blob/master/LICENSE
47075   */
47076
47077  var Hls$1 = {
47078    PlaylistLoader: PlaylistLoader,
47079    Playlist: Playlist,
47080    Decrypter: Decrypter,
47081    AsyncStream: AsyncStream,
47082    decrypt: decrypt,
47083    utils: utils,
47084
47085    STANDARD_PLAYLIST_SELECTOR: lastBandwidthSelector,
47086    INITIAL_PLAYLIST_SELECTOR: lowestBitrateCompatibleVariantSelector,
47087    comparePlaylistBandwidth: comparePlaylistBandwidth,
47088    comparePlaylistResolution: comparePlaylistResolution,
47089
47090    xhr: xhrFactory()
47091  };
47092
47093  // 0.5 MB/s
47094  var INITIAL_BANDWIDTH = 4194304;
47095
47096  // Define getter/setters for config properites
47097  ['GOAL_BUFFER_LENGTH', 'MAX_GOAL_BUFFER_LENGTH', 'GOAL_BUFFER_LENGTH_RATE', 'BUFFER_LOW_WATER_LINE', 'MAX_BUFFER_LOW_WATER_LINE', 'BUFFER_LOW_WATER_LINE_RATE', 'BANDWIDTH_VARIANCE'].forEach(function (prop) {
47098    Object.defineProperty(Hls$1, prop, {
47099      get: function get$$1() {
47100        videojs$1.log.warn('using Hls.' + prop + ' is UNSAFE be sure you know what you are doing');
47101        return Config[prop];
47102      },
47103      set: function set$$1(value) {
47104        videojs$1.log.warn('using Hls.' + prop + ' is UNSAFE be sure you know what you are doing');
47105
47106        if (typeof value !== 'number' || value < 0) {
47107          videojs$1.log.warn('value of Hls.' + prop + ' must be greater than or equal to 0');
47108          return;
47109        }
47110
47111        Config[prop] = value;
47112      }
47113    });
47114  });
47115
47116  var simpleTypeFromSourceType = function simpleTypeFromSourceType(type) {
47117    var mpegurlRE = /^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i;
47118
47119    if (mpegurlRE.test(type)) {
47120      return 'hls';
47121    }
47122
47123    var dashRE = /^application\/dash\+xml/i;
47124
47125    if (dashRE.test(type)) {
47126      return 'dash';
47127    }
47128
47129    return null;
47130  };
47131
47132  /**
47133   * Updates the selectedIndex of the QualityLevelList when a mediachange happens in hls.
47134   *
47135   * @param {QualityLevelList} qualityLevels The QualityLevelList to update.
47136   * @param {PlaylistLoader} playlistLoader PlaylistLoader containing the new media info.
47137   * @function handleHlsMediaChange
47138   */
47139  var handleHlsMediaChange = function handleHlsMediaChange(qualityLevels, playlistLoader) {
47140    var newPlaylist = playlistLoader.media();
47141    var selectedIndex = -1;
47142
47143    for (var i = 0; i < qualityLevels.length; i++) {
47144      if (qualityLevels[i].id === newPlaylist.uri) {
47145        selectedIndex = i;
47146        break;
47147      }
47148    }
47149
47150    qualityLevels.selectedIndex_ = selectedIndex;
47151    qualityLevels.trigger({
47152      selectedIndex: selectedIndex,
47153      type: 'change'
47154    });
47155  };
47156
47157  /**
47158   * Adds quality levels to list once playlist metadata is available
47159   *
47160   * @param {QualityLevelList} qualityLevels The QualityLevelList to attach events to.
47161   * @param {Object} hls Hls object to listen to for media events.
47162   * @function handleHlsLoadedMetadata
47163   */
47164  var handleHlsLoadedMetadata = function handleHlsLoadedMetadata(qualityLevels, hls) {
47165    hls.representations().forEach(function (rep) {
47166      qualityLevels.addQualityLevel(rep);
47167    });
47168    handleHlsMediaChange(qualityLevels, hls.playlists);
47169  };
47170
47171  // HLS is a source handler, not a tech. Make sure attempts to use it
47172  // as one do not cause exceptions.
47173  Hls$1.canPlaySource = function () {
47174    return videojs$1.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
47175  };
47176
47177  var emeKeySystems = function emeKeySystems(keySystemOptions, videoPlaylist, audioPlaylist) {
47178    if (!keySystemOptions) {
47179      return keySystemOptions;
47180    }
47181
47182    // upsert the content types based on the selected playlist
47183    var keySystemContentTypes = {};
47184
47185    for (var keySystem in keySystemOptions) {
47186      keySystemContentTypes[keySystem] = {
47187        audioContentType: 'audio/mp4; codecs="' + audioPlaylist.attributes.CODECS + '"',
47188        videoContentType: 'video/mp4; codecs="' + videoPlaylist.attributes.CODECS + '"'
47189      };
47190
47191      if (videoPlaylist.contentProtection && videoPlaylist.contentProtection[keySystem] && videoPlaylist.contentProtection[keySystem].pssh) {
47192        keySystemContentTypes[keySystem].pssh = videoPlaylist.contentProtection[keySystem].pssh;
47193      }
47194
47195      // videojs-contrib-eme accepts the option of specifying: 'com.some.cdm': 'url'
47196      // so we need to prevent overwriting the URL entirely
47197      if (typeof keySystemOptions[keySystem] === 'string') {
47198        keySystemContentTypes[keySystem].url = keySystemOptions[keySystem];
47199      }
47200    }
47201
47202    return videojs$1.mergeOptions(keySystemOptions, keySystemContentTypes);
47203  };
47204
47205  var setupEmeOptions = function setupEmeOptions(hlsHandler) {
47206    if (hlsHandler.options_.sourceType !== 'dash') {
47207      return;
47208    }
47209    var player = videojs$1.players[hlsHandler.tech_.options_.playerId];
47210
47211    if (player.eme) {
47212      var sourceOptions = emeKeySystems(hlsHandler.source_.keySystems, hlsHandler.playlists.media(), hlsHandler.masterPlaylistController_.mediaTypes_.AUDIO.activePlaylistLoader.media());
47213
47214      if (sourceOptions) {
47215        player.currentSource().keySystems = sourceOptions;
47216      }
47217    }
47218  };
47219
47220  /**
47221   * Whether the browser has built-in HLS support.
47222   */
47223  Hls$1.supportsNativeHls = function () {
47224    var video = document_1.createElement('video');
47225
47226    // native HLS is definitely not supported if HTML5 video isn't
47227    if (!videojs$1.getTech('Html5').isSupported()) {
47228      return false;
47229    }
47230
47231    // HLS manifests can go by many mime-types
47232    var canPlay = [
47233    // Apple santioned
47234    'application/vnd.apple.mpegurl',
47235    // Apple sanctioned for backwards compatibility
47236    'audio/mpegurl',
47237    // Very common
47238    'audio/x-mpegurl',
47239    // Very common
47240    'application/x-mpegurl',
47241    // Included for completeness
47242    'video/x-mpegurl', 'video/mpegurl', 'application/mpegurl'];
47243
47244    return canPlay.some(function (canItPlay) {
47245      return (/maybe|probably/i.test(video.canPlayType(canItPlay))
47246      );
47247    });
47248  }();
47249
47250  Hls$1.supportsNativeDash = function () {
47251    if (!videojs$1.getTech('Html5').isSupported()) {
47252      return false;
47253    }
47254
47255    return (/maybe|probably/i.test(document_1.createElement('video').canPlayType('application/dash+xml'))
47256    );
47257  }();
47258
47259  Hls$1.supportsTypeNatively = function (type) {
47260    if (type === 'hls') {
47261      return Hls$1.supportsNativeHls;
47262    }
47263
47264    if (type === 'dash') {
47265      return Hls$1.supportsNativeDash;
47266    }
47267
47268    return false;
47269  };
47270
47271  /**
47272   * HLS is a source handler, not a tech. Make sure attempts to use it
47273   * as one do not cause exceptions.
47274   */
47275  Hls$1.isSupported = function () {
47276    return videojs$1.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
47277  };
47278
47279  var Component$1 = videojs$1.getComponent('Component');
47280
47281  /**
47282   * The Hls Handler object, where we orchestrate all of the parts
47283   * of HLS to interact with video.js
47284   *
47285   * @class HlsHandler
47286   * @extends videojs.Component
47287   * @param {Object} source the soruce object
47288   * @param {Tech} tech the parent tech object
47289   * @param {Object} options optional and required options
47290   */
47291
47292  var HlsHandler = function (_Component) {
47293    inherits$3(HlsHandler, _Component);
47294
47295    function HlsHandler(source, tech, options) {
47296      classCallCheck$3(this, HlsHandler);
47297
47298      // tech.player() is deprecated but setup a reference to HLS for
47299      // backwards-compatibility
47300      var _this = possibleConstructorReturn$3(this, (HlsHandler.__proto__ || Object.getPrototypeOf(HlsHandler)).call(this, tech, options.hls));
47301
47302      if (tech.options_ && tech.options_.playerId) {
47303        var _player = videojs$1(tech.options_.playerId);
47304
47305        if (!_player.hasOwnProperty('hls')) {
47306          Object.defineProperty(_player, 'hls', {
47307            get: function get$$1() {
47308              videojs$1.log.warn('player.hls is deprecated. Use player.tech_.hls instead.');
47309              tech.trigger({ type: 'usage', name: 'hls-player-access' });
47310              return _this;
47311            }
47312          });
47313        }
47314
47315        // Set up a reference to the HlsHandler from player.vhs. This allows users to start
47316        // migrating from player.tech_.hls... to player.vhs... for API access. Although this
47317        // isn't the most appropriate form of reference for video.js (since all APIs should
47318        // be provided through core video.js), it is a common pattern for plugins, and vhs
47319        // will act accordingly.
47320        _player.vhs = _this;
47321        // deprecated, for backwards compatibility
47322        _player.dash = _this;
47323      }
47324
47325      _this.tech_ = tech;
47326      _this.source_ = source;
47327      _this.stats = {};
47328      _this.ignoreNextSeekingEvent_ = false;
47329      _this.setOptions_();
47330
47331      // overriding native HLS only works if audio tracks have been emulated
47332      // error early if we're misconfigured:
47333      if (_this.options_.overrideNative && (tech.featuresNativeVideoTracks || tech.featuresNativeAudioTracks)) {
47334        throw new Error('Overriding native HLS requires emulated tracks. ' + 'See https://git.io/vMpjB');
47335      }
47336
47337      // listen for fullscreenchange events for this player so that we
47338      // can adjust our quality selection quickly
47339      _this.on(document_1, ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'], function (event) {
47340        var fullscreenElement = document_1.fullscreenElement || document_1.webkitFullscreenElement || document_1.mozFullScreenElement || document_1.msFullscreenElement;
47341
47342        if (fullscreenElement && fullscreenElement.contains(_this.tech_.el())) {
47343          _this.masterPlaylistController_.fastQualityChange_();
47344        }
47345      });
47346
47347      _this.on(_this.tech_, 'seeking', function () {
47348        if (this.ignoreNextSeekingEvent_) {
47349          this.ignoreNextSeekingEvent_ = false;
47350          return;
47351        }
47352
47353        this.setCurrentTime(this.tech_.currentTime());
47354      });
47355      _this.on(_this.tech_, 'error', function () {
47356        if (this.masterPlaylistController_) {
47357          this.masterPlaylistController_.pauseLoading();
47358        }
47359      });
47360
47361      _this.on(_this.tech_, 'play', _this.play);
47362      return _this;
47363    }
47364
47365    createClass$2(HlsHandler, [{
47366      key: 'setOptions_',
47367      value: function setOptions_() {
47368        var _this2 = this;
47369
47370        // defaults
47371        this.options_.withCredentials = this.options_.withCredentials || false;
47372
47373        if (typeof this.options_.blacklistDuration !== 'number') {
47374          this.options_.blacklistDuration = 5 * 60;
47375        }
47376
47377        // start playlist selection at a reasonable bandwidth for
47378        // broadband internet (0.5 MB/s) or mobile (0.0625 MB/s)
47379        if (typeof this.options_.bandwidth !== 'number') {
47380          this.options_.bandwidth = INITIAL_BANDWIDTH;
47381        }
47382
47383        // If the bandwidth number is unchanged from the initial setting
47384        // then this takes precedence over the enableLowInitialPlaylist option
47385        this.options_.enableLowInitialPlaylist = this.options_.enableLowInitialPlaylist && this.options_.bandwidth === INITIAL_BANDWIDTH;
47386
47387        // grab options passed to player.src
47388        ['withCredentials', 'bandwidth'].forEach(function (option) {
47389          if (typeof _this2.source_[option] !== 'undefined') {
47390            _this2.options_[option] = _this2.source_[option];
47391          }
47392        });
47393
47394        this.bandwidth = this.options_.bandwidth;
47395      }
47396      /**
47397       * called when player.src gets called, handle a new source
47398       *
47399       * @param {Object} src the source object to handle
47400       */
47401
47402    }, {
47403      key: 'src',
47404      value: function src(_src, type) {
47405        var _this3 = this;
47406
47407        // do nothing if the src is falsey
47408        if (!_src) {
47409          return;
47410        }
47411        this.setOptions_();
47412        // add master playlist controller options
47413        this.options_.url = this.source_.src;
47414        this.options_.tech = this.tech_;
47415        this.options_.externHls = Hls$1;
47416        this.options_.sourceType = simpleTypeFromSourceType(type);
47417
47418        this.masterPlaylistController_ = new MasterPlaylistController(this.options_);
47419        this.playbackWatcher_ = new PlaybackWatcher(videojs$1.mergeOptions(this.options_, {
47420          seekable: function seekable$$1() {
47421            return _this3.seekable();
47422          }
47423        }));
47424
47425        this.masterPlaylistController_.on('error', function () {
47426          var player = videojs$1.players[_this3.tech_.options_.playerId];
47427
47428          player.error(_this3.masterPlaylistController_.error);
47429        });
47430
47431        // `this` in selectPlaylist should be the HlsHandler for backwards
47432        // compatibility with < v2
47433        this.masterPlaylistController_.selectPlaylist = this.selectPlaylist ? this.selectPlaylist.bind(this) : Hls$1.STANDARD_PLAYLIST_SELECTOR.bind(this);
47434
47435        this.masterPlaylistController_.selectInitialPlaylist = Hls$1.INITIAL_PLAYLIST_SELECTOR.bind(this);
47436
47437        // re-expose some internal objects for backwards compatibility with < v2
47438        this.playlists = this.masterPlaylistController_.masterPlaylistLoader_;
47439        this.mediaSource = this.masterPlaylistController_.mediaSource;
47440
47441        // Proxy assignment of some properties to the master playlist
47442        // controller. Using a custom property for backwards compatibility
47443        // with < v2
47444        Object.defineProperties(this, {
47445          selectPlaylist: {
47446            get: function get$$1() {
47447              return this.masterPlaylistController_.selectPlaylist;
47448            },
47449            set: function set$$1(selectPlaylist) {
47450              this.masterPlaylistController_.selectPlaylist = selectPlaylist.bind(this);
47451            }
47452          },
47453          throughput: {
47454            get: function get$$1() {
47455              return this.masterPlaylistController_.mainSegmentLoader_.throughput.rate;
47456            },
47457            set: function set$$1(throughput) {
47458              this.masterPlaylistController_.mainSegmentLoader_.throughput.rate = throughput;
47459              // By setting `count` to 1 the throughput value becomes the starting value
47460              // for the cumulative average
47461              this.masterPlaylistController_.mainSegmentLoader_.throughput.count = 1;
47462            }
47463          },
47464          bandwidth: {
47465            get: function get$$1() {
47466              return this.masterPlaylistController_.mainSegmentLoader_.bandwidth;
47467            },
47468            set: function set$$1(bandwidth) {
47469              this.masterPlaylistController_.mainSegmentLoader_.bandwidth = bandwidth;
47470              // setting the bandwidth manually resets the throughput counter
47471              // `count` is set to zero that current value of `rate` isn't included
47472              // in the cumulative average
47473              this.masterPlaylistController_.mainSegmentLoader_.throughput = {
47474                rate: 0,
47475                count: 0
47476              };
47477            }
47478          },
47479          /**
47480           * `systemBandwidth` is a combination of two serial processes bit-rates. The first
47481           * is the network bitrate provided by `bandwidth` and the second is the bitrate of
47482           * the entire process after that - decryption, transmuxing, and appending - provided
47483           * by `throughput`.
47484           *
47485           * Since the two process are serial, the overall system bandwidth is given by:
47486           *   sysBandwidth = 1 / (1 / bandwidth + 1 / throughput)
47487           */
47488          systemBandwidth: {
47489            get: function get$$1() {
47490              var invBandwidth = 1 / (this.bandwidth || 1);
47491              var invThroughput = void 0;
47492
47493              if (this.throughput > 0) {
47494                invThroughput = 1 / this.throughput;
47495              } else {
47496                invThroughput = 0;
47497              }
47498
47499              var systemBitrate = Math.floor(1 / (invBandwidth + invThroughput));
47500
47501              return systemBitrate;
47502            },
47503            set: function set$$1() {
47504              videojs$1.log.error('The "systemBandwidth" property is read-only');
47505            }
47506          }
47507        });
47508
47509        Object.defineProperties(this.stats, {
47510          bandwidth: {
47511            get: function get$$1() {
47512              return _this3.bandwidth || 0;
47513            },
47514            enumerable: true
47515          },
47516          mediaRequests: {
47517            get: function get$$1() {
47518              return _this3.masterPlaylistController_.mediaRequests_() || 0;
47519            },
47520            enumerable: true
47521          },
47522          mediaRequestsAborted: {
47523            get: function get$$1() {
47524              return _this3.masterPlaylistController_.mediaRequestsAborted_() || 0;
47525            },
47526            enumerable: true
47527          },
47528          mediaRequestsTimedout: {
47529            get: function get$$1() {
47530              return _this3.masterPlaylistController_.mediaRequestsTimedout_() || 0;
47531            },
47532            enumerable: true
47533          },
47534          mediaRequestsErrored: {
47535            get: function get$$1() {
47536              return _this3.masterPlaylistController_.mediaRequestsErrored_() || 0;
47537            },
47538            enumerable: true
47539          },
47540          mediaTransferDuration: {
47541            get: function get$$1() {
47542              return _this3.masterPlaylistController_.mediaTransferDuration_() || 0;
47543            },
47544            enumerable: true
47545          },
47546          mediaBytesTransferred: {
47547            get: function get$$1() {
47548              return _this3.masterPlaylistController_.mediaBytesTransferred_() || 0;
47549            },
47550            enumerable: true
47551          },
47552          mediaSecondsLoaded: {
47553            get: function get$$1() {
47554              return _this3.masterPlaylistController_.mediaSecondsLoaded_() || 0;
47555            },
47556            enumerable: true
47557          },
47558          buffered: {
47559            get: function get$$1() {
47560              return timeRangesToArray(_this3.tech_.buffered());
47561            },
47562            enumerable: true
47563          },
47564          currentTime: {
47565            get: function get$$1() {
47566              return _this3.tech_.currentTime();
47567            },
47568            enumerable: true
47569          },
47570          currentSource: {
47571            get: function get$$1() {
47572              return _this3.tech_.currentSource_;
47573            },
47574            enumerable: true
47575          },
47576          currentTech: {
47577            get: function get$$1() {
47578              return _this3.tech_.name_;
47579            },
47580            enumerable: true
47581          },
47582          duration: {
47583            get: function get$$1() {
47584              return _this3.tech_.duration();
47585            },
47586            enumerable: true
47587          },
47588          master: {
47589            get: function get$$1() {
47590              return _this3.playlists.master;
47591            },
47592            enumerable: true
47593          },
47594          playerDimensions: {
47595            get: function get$$1() {
47596              return _this3.tech_.currentDimensions();
47597            },
47598            enumerable: true
47599          },
47600          seekable: {
47601            get: function get$$1() {
47602              return timeRangesToArray(_this3.tech_.seekable());
47603            },
47604            enumerable: true
47605          },
47606          timestamp: {
47607            get: function get$$1() {
47608              return Date.now();
47609            },
47610            enumerable: true
47611          },
47612          videoPlaybackQuality: {
47613            get: function get$$1() {
47614              return _this3.tech_.getVideoPlaybackQuality();
47615            },
47616            enumerable: true
47617          }
47618        });
47619
47620        this.tech_.one('canplay', this.masterPlaylistController_.setupFirstPlay.bind(this.masterPlaylistController_));
47621
47622        this.masterPlaylistController_.on('selectedinitialmedia', function () {
47623          // Add the manual rendition mix-in to HlsHandler
47624          renditionSelectionMixin(_this3);
47625          setupEmeOptions(_this3);
47626        });
47627
47628        // the bandwidth of the primary segment loader is our best
47629        // estimate of overall bandwidth
47630        this.on(this.masterPlaylistController_, 'progress', function () {
47631          this.tech_.trigger('progress');
47632        });
47633
47634        // In the live case, we need to ignore the very first `seeking` event since
47635        // that will be the result of the seek-to-live behavior
47636        this.on(this.masterPlaylistController_, 'firstplay', function () {
47637          this.ignoreNextSeekingEvent_ = true;
47638        });
47639
47640        this.tech_.ready(function () {
47641          return _this3.setupQualityLevels_();
47642        });
47643
47644        // do nothing if the tech has been disposed already
47645        // this can occur if someone sets the src in player.ready(), for instance
47646        if (!this.tech_.el()) {
47647          return;
47648        }
47649
47650        this.tech_.src(videojs$1.URL.createObjectURL(this.masterPlaylistController_.mediaSource));
47651      }
47652
47653      /**
47654       * Initializes the quality levels and sets listeners to update them.
47655       *
47656       * @method setupQualityLevels_
47657       * @private
47658       */
47659
47660    }, {
47661      key: 'setupQualityLevels_',
47662      value: function setupQualityLevels_() {
47663        var _this4 = this;
47664
47665        var player = videojs$1.players[this.tech_.options_.playerId];
47666
47667        if (player && player.qualityLevels) {
47668          this.qualityLevels_ = player.qualityLevels();
47669
47670          this.masterPlaylistController_.on('selectedinitialmedia', function () {
47671            handleHlsLoadedMetadata(_this4.qualityLevels_, _this4);
47672          });
47673
47674          this.playlists.on('mediachange', function () {
47675            handleHlsMediaChange(_this4.qualityLevels_, _this4.playlists);
47676          });
47677        }
47678      }
47679
47680      /**
47681       * Begin playing the video.
47682       */
47683
47684    }, {
47685      key: 'play',
47686      value: function play() {
47687        this.masterPlaylistController_.play();
47688      }
47689
47690      /**
47691       * a wrapper around the function in MasterPlaylistController
47692       */
47693
47694    }, {
47695      key: 'setCurrentTime',
47696      value: function setCurrentTime(currentTime) {
47697        this.masterPlaylistController_.setCurrentTime(currentTime);
47698      }
47699
47700      /**
47701       * a wrapper around the function in MasterPlaylistController
47702       */
47703
47704    }, {
47705      key: 'duration',
47706      value: function duration$$1() {
47707        return this.masterPlaylistController_.duration();
47708      }
47709
47710      /**
47711       * a wrapper around the function in MasterPlaylistController
47712       */
47713
47714    }, {
47715      key: 'seekable',
47716      value: function seekable$$1() {
47717        return this.masterPlaylistController_.seekable();
47718      }
47719
47720      /**
47721       * Abort all outstanding work and cleanup.
47722       */
47723
47724    }, {
47725      key: 'dispose',
47726      value: function dispose() {
47727        if (this.playbackWatcher_) {
47728          this.playbackWatcher_.dispose();
47729        }
47730        if (this.masterPlaylistController_) {
47731          this.masterPlaylistController_.dispose();
47732        }
47733        if (this.qualityLevels_) {
47734          this.qualityLevels_.dispose();
47735        }
47736        get$2(HlsHandler.prototype.__proto__ || Object.getPrototypeOf(HlsHandler.prototype), 'dispose', this).call(this);
47737      }
47738    }]);
47739    return HlsHandler;
47740  }(Component$1);
47741
47742  /**
47743   * The Source Handler object, which informs video.js what additional
47744   * MIME types are supported and sets up playback. It is registered
47745   * automatically to the appropriate tech based on the capabilities of
47746   * the browser it is running in. It is not necessary to use or modify
47747   * this object in normal usage.
47748   */
47749
47750  var HlsSourceHandler = {
47751    name: 'videojs-http-streaming',
47752    VERSION: version$3,
47753    canHandleSource: function canHandleSource(srcObj) {
47754      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
47755
47756      var localOptions = videojs$1.mergeOptions(videojs$1.options, options);
47757
47758      return HlsSourceHandler.canPlayType(srcObj.type, localOptions);
47759    },
47760    handleSource: function handleSource(source, tech) {
47761      var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
47762
47763      var localOptions = videojs$1.mergeOptions(videojs$1.options, options);
47764
47765      tech.hls = new HlsHandler(source, tech, localOptions);
47766      tech.hls.xhr = xhrFactory();
47767
47768      tech.hls.src(source.src, source.type);
47769      return tech.hls;
47770    },
47771    canPlayType: function canPlayType(type) {
47772      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
47773
47774      var _videojs$mergeOptions = videojs$1.mergeOptions(videojs$1.options, options),
47775          overrideNative = _videojs$mergeOptions.hls.overrideNative;
47776
47777      var supportedType = simpleTypeFromSourceType(type);
47778      var canUseMsePlayback = supportedType && (!Hls$1.supportsTypeNatively(supportedType) || overrideNative);
47779
47780      return canUseMsePlayback ? 'maybe' : '';
47781    }
47782  };
47783
47784  if (typeof videojs$1.MediaSource === 'undefined' || typeof videojs$1.URL === 'undefined') {
47785    videojs$1.MediaSource = MediaSource;
47786    videojs$1.URL = URL$1;
47787  }
47788
47789  // register source handlers with the appropriate techs
47790  if (MediaSource.supportsNativeMediaSources()) {
47791    videojs$1.getTech('Html5').registerSourceHandler(HlsSourceHandler, 0);
47792  }
47793
47794  videojs$1.HlsHandler = HlsHandler;
47795  videojs$1.HlsSourceHandler = HlsSourceHandler;
47796  videojs$1.Hls = Hls$1;
47797  if (!videojs$1.use) {
47798    videojs$1.registerComponent('Hls', Hls$1);
47799  }
47800  videojs$1.options.hls = videojs$1.options.hls || {};
47801
47802  if (videojs$1.registerPlugin) {
47803    videojs$1.registerPlugin('reloadSourceOnError', reloadSourceOnError);
47804  } else {
47805    videojs$1.plugin('reloadSourceOnError', reloadSourceOnError);
47806  }
47807
47808  return videojs$1;
47809
47810})));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.