PageSourceSearch

https://tunisavia.com.tn/wp-content/plugins/wpvr/public/js/video.js?ver=1

js tunisavia.com.tn collected 2026-10-02 23:16:42 UTC 1,522,301 bytes, 48,094 lines download raw bytes

vendor: 23,175 bytes, lines 1-831
1/**
2 * @license
3 * Video.js 7.1.0 <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.1.0";
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
vendor: 4,356 bytes, lines 832-989
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
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 {
vendor: 4,616 bytes, lines 1485-1622
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;
vendor: 12,968 bytes, lines 1623-2093
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: 28,573 bytes, lines 2762-3727
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-u
3940sing-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
vendor: 10,069 bytes, lines 4111-4381
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      // declare as variables so they are properly available in timeout function
4318      // eslint-disable-next-line
4319      var timeoutId, disposeFn;
4320
4321      fn = bind(this, fn);
4322
4323      timeoutId = window_1.setTimeout(function () {
4324        _this2.off('dispose', disposeFn);
4325        fn();
4326      }, timeout);
4327
4328      disposeFn = function disposeFn() {
4329        return _this2.clearTimeout(timeoutId);
4330      };
4331
4332      disposeFn.guid = 'vjs-timeout-' + timeoutId;
4333
4334      this.on('dispose', disposeFn);
4335
4336      return timeoutId;
4337    };
4338
4339    /**
4340     * Clears a timeout that gets created via `window.setTimeout` or
4341     * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout}
4342     * use this function instead of `window.clearTimout`. If you don't your dispose
4343     * listener will not get cleaned up until {@link Component#dispose}!
4344     *
4345     * @param {number} timeoutId
4346     *        The id of the timeout to clear. The return value of
4347     *        {@link Component#setTimeout} or `window.setTimeout`.
4348     *
4349     * @return {number}
4350     *         Returns the timeout id that was cleared.
4351     *
4352     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout}
4353     */
4354
4355
4356    Component.prototype.clearTimeout = function clearTimeout(timeoutId) {
4357      window_1.clearTimeout(timeoutId);
4358
4359      var disposeFn = function disposeFn() {};
4360
4361      disposeFn.guid = 'vjs-timeout-' + timeoutId;
4362
4363      this.off('dispose', disposeFn);
4364
4365      return timeoutId;
4366    };
4367
4368    /**
4369     * Creates a function that gets run every `x` milliseconds. This function is a wrapper
4370     * around `window.setInterval`. There are a few reasons to use this one instead though.
4371     * 1. It gets cleared via  {@link Component#clearInterval} when
4372     *    {@link Component#dispose} gets called.
4373     * 2. The function callback will be a {@link Component~GenericCallback}
4374     *
4375     * @param {Component~GenericCallback} fn
4376     *        The function to run every `x` seconds.
4377     *
4378     * @param {number} interval
4379     *        Execute the specified function every `x` milliseconds.
4380     *
4381     * @return {number}
4382     *         Returns an id that can be used to identify the interval. It can also be be used in
4383     *         {@link Component#clearInterval} to clear the interval.
4384     *
4385     * @listens Component#dispose
4386     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval}
4387     */
4388
4389
4390    Component.prototype.setInterval = function setInterval(fn, interval) {
4391      var _this3 = this;
4392
4393      fn = bind(this, fn);
4394
4395      var intervalId = window_1.setInterval(fn, interval);
4396
4397      var disposeFn = function disposeFn() {
4398        return _this3.clearInterval(intervalId);
4399      };
4400
4401      disposeFn.guid = 'vjs-interval-' + intervalId;
4402
4403      this.on('dispose', disposeFn);
4404
4405      return intervalId;
4406    };
4407
4408    /**
4409     * Clears an interval that gets created via `window.setInterval` or
4410     * {@link Component#setInterval}. If you set an inteval via {@link Component#setInterval}
4411     * use this function instead of `window.clearInterval`. If you don't your dispose
4412     * listener will not get cleaned up until {@link Component#dispose}!
4413     *
4414     * @param {number} intervalId
4415     *        The id of the interval to clear. The return value of
4416     *        {@link Component#setInterval} or `window.setInterval`.
4417     *
4418     * @return {number}
4419     *         Returns the interval id that was cleared.
4420     *
4421     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval}
4422     */
4423
4424
4425    Component.prototype.clearInterval = function clearInterval(intervalId) {
4426      window_1.clearInterval(intervalId);
4427
4428      var disposeFn = function disposeFn() {};
4429
4430      disposeFn.guid = 'vjs-interval-' + intervalId;
4431
4432      this.off('dispose', disposeFn);
4433
4434      return intervalId;
4435    };
4436
4437    /**
4438     * Queues up a callback to be passed to requestAnimationFrame (rAF), but
4439     * with a few extra bonuses:
4440     *
4441     * - Supports browsers that do not support rAF by falling back to
4442     *   {@link Component#setTimeout}.
4443     *
4444     * - The callback is turned into a {@link Component~GenericCallback} (i.e.
4445     *   bound to the component).
4446     *
4447     * - Automatic cancellation of the rAF callback is handled if the component
4448     *   is disposed before it is called.
4449     *
4450     * @param  {Component~GenericCallback} fn
4451     *         A function that will be bound to this component and executed just
4452     *         before the browser's next repaint.
4453     *
4454     * @return {number}
4455     *         Returns an rAF ID that gets used to identify the timeout. It can
4456     *         also be used in {@link Component#cancelAnimationFrame} to cancel
4457     *         the animation frame callback.
4458     *
4459     * @listens Component#dispose
4460     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame}
4461     */
4462
4463
4464    Component.prototype.requestAnimationFrame = function requestAnimationFrame(fn) {
4465      var _this4 = this;
4466
4467      // declare as variables so they are properly available in rAF function
4468      // eslint-disable-next-line
4469      var id, disposeFn;
4470
4471      if (this.supportsRaf_) {
4472        fn = bind(this, fn);
4473
4474        id = window_1.requestAnimationFrame(function () {
4475          _this4.off('dispose', disposeFn);
4476          fn();
4477        });
4478
4479        disposeFn = function disposeFn() {
4480          return _this4.cancelAnimationFrame(id);
4481        };
4482
4483        disposeFn.guid = 'vjs-raf-' + id;
4484        this.on('dispose', disposeFn);
4485
4486        return id;
4487      }
4488
4489      // Fall back to using a timer.
4490      return this.setTimeout(fn, 1000 / 60);
4491    };
4492
4493    /**
4494     * Cancels a queued callback passed to {@link Component#requestAnimationFrame}
4495     * (rAF).
4496     *
4497     * If you queue an rAF callback via {@link Component#requestAnimationFrame},
4498     * use this function instead of `window.cancelAnimationFrame`. If you don't,
4499     * your dispose listener will not get cleaned up until {@link Component#dispose}!
4500     *
4501     * @param {number} id
4502     *        The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}.
4503     *
4504     * @return {number}
4505     *         Returns the rAF ID that was cleared.
4506     *
4507     * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame}
4508     */
4509
4510
4511    Component.prototype.cancelAnimationFrame = function cancelAnimationFrame(id) {
4512      if (this.supportsRaf_) {
4513        window_1.cancelAnimationFrame(id);
4514
4515        var disposeFn = function disposeFn() {};
4516
4517        disposeFn.guid = 'vjs-raf-' + id;
4518
4519        this.off('dispose', disposeFn);
4520
4521        return id;
4522      }
4523
4524      // Fall back to using a timer.
4525      return this.clearTimeout(id);
4526    };
4527
4528    /**
4529     * Register a `Component` with `videojs` given the name and the component.
4530     *
4531     * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s
4532     *         should be registered using {@link Tech.registerTech} or
vendor: 13,371 bytes, lines 4533-4973
4533     *         {@link videojs:videojs.registerTech}.
4534     *
4535     * > NOTE: This function can also be seen on videojs as
4536     *         {@link videojs:videojs.registerComponent}.
4537     *
4538     * @param {string} name
4539     *        The name of the `Component` to register.
4540     *
4541     * @param {Component} ComponentToRegister
4542     *        The `Component` class to register.
4543     *
4544     * @return {Component}
4545     *         The `Component` that was registered.
4546     */
4547
4548
4549    Component.registerComponent = function registerComponent(name, ComponentToRegister) {
4550      if (typeof name !== 'string' || !name) {
4551        throw new Error('Illegal component name, "' + name + '"; must be a non-empty string.');
4552      }
4553
4554      var Tech = Component.getComponent('Tech');
4555
4556      // We need to make sure this check is only done if Tech has been registered.
4557      var isTech = Tech && Tech.isTech(ComponentToRegister);
4558      var isComp = Component === ComponentToRegister || Component.prototype.isPrototypeOf(ComponentToRegister.prototype);
4559
4560      if (isTech || !isComp) {
4561        var reason = void 0;
4562
4563        if (isTech) {
4564          reason = 'techs must be registered using Tech.registerTech()';
4565        } else {
4566          reason = 'must be a Component subclass';
4567        }
4568
4569        throw new Error('Illegal component, "' + name + '"; ' + reason + '.');
4570      }
4571
4572      name = toTitleCase(name);
4573
4574      if (!Component.components_) {
4575        Component.components_ = {};
4576      }
4577
4578      var Player = Component.getComponent('Player');
4579
4580      if (name === 'Player' && Player && Player.players) {
4581        var players = Player.players;
4582        var playerNames = Object.keys(players);
4583
4584        // If we have players that were disposed, then their name will still be
4585        // in Players.players. So, we must loop through and verify that the value
4586        // for each item is not null. This allows registration of the Player component
4587        // after all players have been disposed or before any were created.
4588        if (players && playerNames.length > 0 && playerNames.map(function (pname) {
4589          return players[pname];
4590        }).every(Boolean)) {
4591          throw new Error('Can not register Player component after player has been created.');
4592        }
4593      }
4594
4595      Component.components_[name] = ComponentToRegister;
4596
4597      return ComponentToRegister;
4598    };
4599
4600    /**
4601     * Get a `Component` based on the name it was registered with.
4602     *
4603     * @param {string} name
4604     *        The Name of the component to get.
4605     *
4606     * @return {Component}
4607     *         The `Component` that got registered under the given name.
4608     *
4609     * @deprecated In `videojs` 6 this will not return `Component`s that were not
4610     *             registered using {@link Component.registerComponent}. Currently we
4611     *             check the global `videojs` object for a `Component` name and
4612     *             return that if it exists.
4613     */
4614
4615
4616    Component.getComponent = function getComponent(name) {
4617      if (!name) {
4618        return;
4619      }
4620
4621      name = toTitleCase(name);
4622
4623      if (Component.components_ && Component.components_[name]) {
4624        return Component.components_[name];
4625      }
4626    };
4627
4628    return Component;
4629  }();
4630
4631  /**
4632   * Whether or not this component supports `requestAnimationFrame`.
4633   *
4634   * This is exposed primarily for testing purposes.
4635   *
4636   * @private
4637   * @type {Boolean}
4638   */
4639
4640
4641  Component.prototype.supportsRaf_ = typeof window_1.requestAnimationFrame === 'function' && typeof window_1.cancelAnimationFrame === 'function';
4642
4643  Component.registerComponent('Component', Component);
4644
4645  /**
4646   * @file browser.js
4647   * @module browser
4648   */
4649
4650  var USER_AGENT = window_1.navigator && window_1.navigator.userAgent || '';
4651  var webkitVersionMap = /AppleWebKit\/([\d.]+)/i.exec(USER_AGENT);
4652  var appleWebkitVersion = webkitVersionMap ? parseFloat(webkitVersionMap.pop()) : null;
4653
4654  /*
4655   * Device is an iPhone
4656   *
4657   * @type {Boolean}
4658   * @constant
4659   * @private
4660   */
4661  var IS_IPAD = /iPad/i.test(USER_AGENT);
4662
4663  // The Facebook app's UIWebView identifies as both an iPhone and iPad, so
4664  // to identify iPhones, we need to exclude iPads.
4665  // http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/
4666  var IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD;
4667  var IS_IPOD = /iPod/i.test(USER_AGENT);
4668  var IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD;
4669
4670  var IOS_VERSION = function () {
4671    var match = USER_AGENT.match(/OS (\d+)_/i);
4672
4673    if (match && match[1]) {
4674      return match[1];
4675    }
4676    return null;
4677  }();
4678
4679  var IS_ANDROID = /Android/i.test(USER_AGENT);
4680  var ANDROID_VERSION = function () {
4681    // This matches Android Major.Minor.Patch versions
4682    // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned
4683    var match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i);
4684
4685    if (!match) {
4686      return null;
4687    }
4688
4689    var major = match[1] && parseFloat(match[1]);
4690    var minor = match[2] && parseFloat(match[2]);
4691
4692    if (major && minor) {
4693      return parseFloat(match[1] + '.' + match[2]);
4694    } else if (major) {
4695      return major;
4696    }
4697    return null;
4698  }();
4699
4700  var IS_NATIVE_ANDROID = IS_ANDROID && ANDROID_VERSION < 5 && appleWebkitVersion < 537;
4701
4702  var IS_FIREFOX = /Firefox/i.test(USER_AGENT);
4703  var IS_EDGE = /Edge/i.test(USER_AGENT);
4704  var IS_CHROME = !IS_EDGE && (/Chrome/i.test(USER_AGENT) || /CriOS/i.test(USER_AGENT));
4705  var CHROME_VERSION = function () {
4706    var match = USER_AGENT.match(/(Chrome|CriOS)\/(\d+)/);
4707
4708    if (match && match[2]) {
4709      return parseFloat(match[2]);
4710    }
4711    return null;
4712  }();
4713  var IE_VERSION = function () {
4714    var result = /MSIE\s(\d+)\.\d/.exec(USER_AGENT);
4715    var version = result && parseFloat(result[1]);
4716
4717    if (!version && /Trident\/7.0/i.test(USER_AGENT) && /rv:11.0/.test(USER_AGENT)) {
4718      // IE 11 has a different user agent string than other IE versions
4719      version = 11.0;
4720    }
4721
4722    return version;
4723  }();
4724
4725  var IS_SAFARI = /Safari/i.test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE;
4726  var IS_ANY_SAFARI = (IS_SAFARI || IS_IOS) && !IS_CHROME;
4727
4728  var TOUCH_ENABLED = isReal() && ('ontouchstart' in window_1 || window_1.navigator.maxTouchPoints || window_1.DocumentTouch && window_1.document instanceof window_1.DocumentTouch);
4729
4730  var browser = /*#__PURE__*/Object.freeze({
4731    IS_IPAD: IS_IPAD,
4732    IS_IPHONE: IS_IPHONE,
4733    IS_IPOD: IS_IPOD,
4734    IS_IOS: IS_IOS,
4735    IOS_VERSION: IOS_VERSION,
4736    IS_ANDROID: IS_ANDROID,
4737    ANDROID_VERSION: ANDROID_VERSION,
4738    IS_NATIVE_ANDROID: IS_NATIVE_ANDROID,
4739    IS_FIREFOX: IS_FIREFOX,
4740    IS_EDGE: IS_EDGE,
4741    IS_CHROME: IS_CHROME,
4742    CHROME_VERSION: CHROME_VERSION,
4743    IE_VERSION: IE_VERSION,
4744    IS_SAFARI: IS_SAFARI,
4745    IS_ANY_SAFARI: IS_ANY_SAFARI,
4746    TOUCH_ENABLED: TOUCH_ENABLED
4747  });
4748
4749  /**
4750   * @file time-ranges.js
4751   * @module time-ranges
4752   */
4753
4754  /**
4755   * Returns the time for the specified index at the start or end
4756   * of a TimeRange object.
4757   *
4758   * @function time-ranges:indexFunction
4759   *
4760   * @param {number} [index=0]
4761   *        The range number to return the time for.
4762   *
4763   * @return {number}
4764   *         The time that offset at the specified index.
4765   *
4766   * @depricated index must be set to a value, in the future this will throw an error.
4767   */
4768
4769  /**
4770   * An object that contains ranges of time for various reasons.
4771   *
4772   * @typedef {Object} TimeRange
4773   *
4774   * @property {number} length
4775   *           The number of time ranges represented by this Object
4776   *
4777   * @property {time-ranges:indexFunction} start
4778   *           Returns the time offset at which a specified time range begins.
4779   *
4780   * @property {time-ranges:indexFunction} end
4781   *           Returns the time offset at which a specified time range ends.
4782   *
4783   * @see https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges
4784   */
4785
4786  /**
4787   * Check if any of the time ranges are over the maximum index.
4788   *
4789   * @param {string} fnName
4790   *        The function name to use for logging
4791   *
4792   * @param {number} index
4793   *        The index to check
4794   *
4795   * @param {number} maxIndex
4796   *        The maximum possible index
4797   *
4798   * @throws {Error} if the timeRanges provided are over the maxIndex
4799   */
4800  function rangeCheck(fnName, index, maxIndex) {
4801    if (typeof index !== 'number' || index < 0 || index > maxIndex) {
4802      throw new Error('Failed to execute \'' + fnName + '\' on \'TimeRanges\': The index provided (' + index + ') is non-numeric or out of bounds (0-' + maxIndex + ').');
4803    }
4804  }
4805
4806  /**
4807   * Get the time for the specified index at the start or end
4808   * of a TimeRange object.
4809   *
4810   * @param {string} fnName
4811   *        The function name to use for logging
4812   *
4813   * @param {string} valueIndex
4814   *        The property that should be used to get the time. should be 'start' or 'end'
4815   *
4816   * @param {Array} ranges
4817   *        An array of time ranges
4818   *
4819   * @param {Array} [rangeIndex=0]
4820   *        The index to start the search at
4821   *
4822   * @return {number}
4823   *         The time that offset at the specified index.
4824   *
4825   *
4826   * @depricated rangeIndex must be set to a value, in the future this will throw an error.
4827   * @throws {Error} if rangeIndex is more than the length of ranges
4828   */
4829  function getRange(fnName, valueIndex, ranges, rangeIndex) {
4830    rangeCheck(fnName, rangeIndex, ranges.length - 1);
4831    return ranges[rangeIndex][valueIndex];
4832  }
4833
4834  /**
4835   * Create a time range object given ranges of time.
4836   *
4837   * @param {Array} [ranges]
4838   *        An array of time ranges.
4839   */
4840  function createTimeRangesObj(ranges) {
4841    if (ranges === undefined || ranges.length === 0) {
4842      return {
4843        length: 0,
4844        start: function start() {
4845          throw new Error('This TimeRanges object is empty');
4846        },
4847        end: function end() {
4848          throw new Error('This TimeRanges object is empty');
4849        }
4850      };
4851    }
4852    return {
4853      length: ranges.length,
4854      start: getRange.bind(null, 'start', 0, ranges),
4855      end: getRange.bind(null, 'end', 1, ranges)
4856    };
4857  }
4858
4859  /**
4860   * Should create a fake `TimeRange` object which mimics an HTML5 time range instance.
4861   *
4862   * @param {number|Array} start
4863   *        The start of a single range or an array of ranges
4864   *
4865   * @param {number} end
4866   *        The end of a single range.
4867   *
4868   * @private
4869   */
4870  function createTimeRanges(start, end) {
4871    if (Array.isArray(start)) {
4872      return createTimeRangesObj(start);
4873    } else if (start === undefined || end === undefined) {
4874      return createTimeRangesObj();
4875    }
4876    return createTimeRangesObj([[start, end]]);
4877  }
4878
4879  /**
4880   * @file buffer.js
4881   * @module buffer
4882   */
4883
4884  /**
4885   * Compute the percentage of the media that has been buffered.
4886   *
4887   * @param {TimeRange} buffered
4888   *        The current `TimeRange` object representing buffered time ranges
4889   *
4890   * @param {number} duration
4891   *        Total duration of the media
4892   *
4893   * @return {number}
4894   *         Percent buffered of the total duration in decimal form.
4895   */
4896  function bufferedPercent(buffered, duration) {
4897    var bufferedDuration = 0;
4898    var start = void 0;
4899    var end = void 0;
4900
4901    if (!duration) {
4902      return 0;
4903    }
4904
4905    if (!buffered || !buffered.length) {
4906      buffered = createTimeRanges(0, 0);
4907    }
4908
4909    for (var i = 0; i < buffered.length; i++) {
4910      start = buffered.start(i);
4911      end = buffered.end(i);
4912
4913      // buffered end can be bigger than duration by a very small fraction
4914      if (end > duration) {
4915        end = duration;
4916      }
4917
4918      bufferedDuration += end - start;
4919    }
4920
4921    return bufferedDuration / duration;
4922  }
4923
4924  /**
4925   * @file fullscreen-api.js
4926   * @module fullscreen-api
4927   * @private
4928   */
4929
4930  /**
4931   * Store the browser-specific methods for the fullscreen API.
4932   *
4933   * @type {Object}
4934   * @see [Specification]{@link https://fullscreen.spec.whatwg.org}
4935   * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js}
4936   */
4937  var FullscreenApi = {};
4938
4939  // browser API methods
4940  var apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror'],
4941  // WebKit
4942  ['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror'],
4943  // Old WebKit (Safari 5.1)
4944  ['webkitRequestFullScreen', 'webkitCancelFullScreen', 'webkitCurrentFullScreenElement', 'webkitCancelFullScreen', 'webkitfullscreenchange', 'webkitfullscreenerror'],
4945  // Mozilla
4946  ['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror'],
4947  // Microsoft
4948  ['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError']];
4949
4950  var specApi = apiMap[0];
4951  var browserApi = void 0;
4952
4953  // determine the supported set of functions
4954  for (var i = 0; i < apiMap.length; i++) {
4955    // check for exitFullscreen function
4956    if (apiMap[i][1] in document_1) {
4957      browserApi = apiMap[i];
4958      break;
4959    }
4960  }
4961
4962  // map the browser API names to the spec API names
4963  if (browserApi) {
4964    for (var _i = 0; _i < browserApi.length; _i++) {
4965      FullscreenApi[specApi[_i]] = browserApi[_i];
4966    }
4967  }
4968
4969  /**
4970   * @file media-error.js
4971   */
4972
4973  /**
4974   * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class.
4975   *
4976   * @param {number|string|Object|MediaError} value
4977   *        This can be of multiple types:
4978   *        - number: should be a standard error code
4979   *        - string: an error message (the code will be 0)
4980   *        - Object: arbitrary properties
4981   *        - `MediaError` (native): used to populate a video.js `MediaError` object
4982   *        - `MediaError` (video.js): will return itself if it's already a
4983   *          video.js `MediaError` object.
4984   *
4985   * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror}
4986   * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes}
4987   *
4988   * @class MediaError
4989   */
4990  function MediaError(value) {
4991
4992    // Allow redundant calls to this constructor to avoid having `instanceof`
4993    // checks peppered around the code.
4994    if (value instanceof MediaError) {
4995      return value;
4996    }
4997
4998    if (typeof value === 'number') {
4999      this.code = value;
5000    } else if (typeof value === 'string') {
vendor: 4,803 bytes, lines 5001-5151
5001      // default code is zero, so this is a custom error
5002      this.message = value;
5003    } else if (isObject(value)) {
5004
5005      // We assign the `code` property manually because native `MediaError` objects
5006      // do not expose it as an own/enumerable property of the object.
5007      if (typeof value.code === 'number') {
5008        this.code = value.code;
5009      }
5010
5011      assign(this, value);
5012    }
5013
5014    if (!this.message) {
5015      this.message = MediaError.defaultMessages[this.code] || '';
5016    }
5017  }
5018
5019  /**
5020   * The error code that refers two one of the defined `MediaError` types
5021   *
5022   * @type {Number}
5023   */
5024  MediaError.prototype.code = 0;
5025
5026  /**
5027   * An optional message that to show with the error. Message is not part of the HTML5
5028   * video spec but allows for more informative custom errors.
5029   *
5030   * @type {String}
5031   */
5032  MediaError.prototype.message = '';
5033
5034  /**
5035   * An optional status code that can be set by plugins to allow even more detail about
5036   * the error. For example a plugin might provide a specific HTTP status code and an
5037   * error message for that code. Then when the plugin gets that error this class will
5038   * know how to display an error message for it. This allows a custom message to show
5039   * up on the `Player` error overlay.
5040   *
5041   * @type {Array}
5042   */
5043  MediaError.prototype.status = null;
5044
5045  /**
5046   * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the
5047   * specification listed under {@link MediaError} for more information.
5048   *
5049   * @enum {array}
5050   * @readonly
5051   * @property {string} 0 - MEDIA_ERR_CUSTOM
5052   * @property {string} 1 - MEDIA_ERR_CUSTOM
5053   * @property {string} 2 - MEDIA_ERR_ABORTED
5054   * @property {string} 3 - MEDIA_ERR_NETWORK
5055   * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED
5056   * @property {string} 5 - MEDIA_ERR_ENCRYPTED
5057   */
5058  MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED'];
5059
5060  /**
5061   * The default `MediaError` messages based on the {@link MediaError.errorTypes}.
5062   *
5063   * @type {Array}
5064   * @constant
5065   */
5066  MediaError.defaultMessages = {
5067    1: 'You aborted the media playback',
5068    2: 'A network error caused the media download to fail part-way.',
5069    3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.',
5070    4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.',
5071    5: 'The media is encrypted and we do not have the keys to decrypt it.'
5072  };
5073
5074  // Add types as properties on MediaError
5075  // e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
5076  for (var errNum = 0; errNum < MediaError.errorTypes.length; errNum++) {
5077    MediaError[MediaError.errorTypes[errNum]] = errNum;
5078    // values should be accessible on both the class and instance
5079    MediaError.prototype[MediaError.errorTypes[errNum]] = errNum;
5080  }
5081
5082  var tuple = SafeParseTuple;
5083
5084  function SafeParseTuple(obj, reviver) {
5085      var json;
5086      var error = null;
5087
5088      try {
5089          json = JSON.parse(obj, reviver);
5090      } catch (err) {
5091          error = err;
5092      }
5093
5094      return [error, json];
5095  }
5096
5097  /**
5098   * Returns whether an object is `Promise`-like (i.e. has a `then` method).
5099   *
5100   * @param  {Object}  value
5101   *         An object that may or may not be `Promise`-like.
5102   *
5103   * @return {Boolean}
5104   *         Whether or not the object is `Promise`-like.
5105   */
5106  function isPromise(value) {
5107    return value !== undefined && value !== null && typeof value.then === 'function';
5108  }
5109
5110  /**
5111   * Silence a Promise-like object.
5112   *
5113   * This is useful for avoiding non-harmful, but potentially confusing "uncaught
5114   * play promise" rejection error messages.
5115   *
5116   * @param  {Object} value
5117   *         An object that may or may not be `Promise`-like.
5118   */
5119  function silencePromise(value) {
5120    if (isPromise(value)) {
5121      value.then(null, function (e) {});
5122    }
5123  }
5124
5125  /**
5126   * @file text-track-list-converter.js Utilities for capturing text track state and
5127   * re-creating tracks based on a capture.
5128   *
5129   * @module text-track-list-converter
5130   */
5131
5132  /**
5133   * Examine a single {@link TextTrack} and return a JSON-compatible javascript object that
5134   * represents the {@link TextTrack}'s state.
5135   *
5136   * @param {TextTrack} track
5137   *        The text track to query.
5138   *
5139   * @return {Object}
5140   *         A serializable javascript representation of the TextTrack.
5141   * @private
5142   */
5143  var trackToJson_ = function trackToJson_(track) {
5144    var ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce(function (acc, prop, i) {
5145
5146      if (track[prop]) {
5147        acc[prop] = track[prop];
5148      }
5149
5150      return acc;
5151    }, {
vendor: 45,150 bytes, lines 5152-6836
5152      cues: track.cues && Array.prototype.map.call(track.cues, function (cue) {
5153        return {
5154          startTime: cue.startTime,
5155          endTime: cue.endTime,
5156          text: cue.text,
5157          id: cue.id
5158        };
5159      })
5160    });
5161
5162    return ret;
5163  };
5164
5165  /**
5166   * Examine a {@link Tech} and return a JSON-compatible javascript array that represents the
5167   * state of all {@link TextTrack}s currently configured. The return array is compatible with
5168   * {@link text-track-list-converter:jsonToTextTracks}.
5169   *
5170   * @param {Tech} tech
5171   *        The tech object to query
5172   *
5173   * @return {Array}
5174   *         A serializable javascript representation of the {@link Tech}s
5175   *         {@link TextTrackList}.
5176   */
5177  var textTracksToJson = function textTracksToJson(tech) {
5178
5179    var trackEls = tech.$$('track');
5180
5181    var trackObjs = Array.prototype.map.call(trackEls, function (t) {
5182      return t.track;
5183    });
5184    var tracks = Array.prototype.map.call(trackEls, function (trackEl) {
5185      var json = trackToJson_(trackEl.track);
5186
5187      if (trackEl.src) {
5188        json.src = trackEl.src;
5189      }
5190      return json;
5191    });
5192
5193    return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) {
5194      return trackObjs.indexOf(track) === -1;
5195    }).map(trackToJson_));
5196  };
5197
5198  /**
5199   * Create a set of remote {@link TextTrack}s on a {@link Tech} based on an array of javascript
5200   * object {@link TextTrack} representations.
5201   *
5202   * @param {Array} json
5203   *        An array of `TextTrack` representation objects, like those that would be
5204   *        produced by `textTracksToJson`.
5205   *
5206   * @param {Tech} tech
5207   *        The `Tech` to create the `TextTrack`s on.
5208   */
5209  var jsonToTextTracks = function jsonToTextTracks(json, tech) {
5210    json.forEach(function (track) {
5211      var addedTrack = tech.addRemoteTextTrack(track).track;
5212
5213      if (!track.src && track.cues) {
5214        track.cues.forEach(function (cue) {
5215          return addedTrack.addCue(cue);
5216        });
5217      }
5218    });
5219
5220    return tech.textTracks();
5221  };
5222
5223  var textTrackConverter = { textTracksToJson: textTracksToJson, jsonToTextTracks: jsonToTextTracks, trackToJson_: trackToJson_ };
5224
5225  /**
5226   * @file modal-dialog.js
5227   */
5228
5229  var MODAL_CLASS_NAME = 'vjs-modal-dialog';
5230  var ESC = 27;
5231
5232  /**
5233   * The `ModalDialog` displays over the video and its controls, which blocks
5234   * interaction with the player until it is closed.
5235   *
5236   * Modal dialogs include a "Close" button and will close when that button
5237   * is activated - or when ESC is pressed anywhere.
5238   *
5239   * @extends Component
5240   */
5241
5242  var ModalDialog = function (_Component) {
5243    inherits(ModalDialog, _Component);
5244
5245    /**
5246     * Create an instance of this class.
5247     *
5248     * @param {Player} player
5249     *        The `Player` that this class should be attached to.
5250     *
5251     * @param {Object} [options]
5252     *        The key/value store of player options.
5253     *
5254     * @param {Mixed} [options.content=undefined]
5255     *        Provide customized content for this modal.
5256     *
5257     * @param {string} [options.description]
5258     *        A text description for the modal, primarily for accessibility.
5259     *
5260     * @param {boolean} [options.fillAlways=false]
5261     *        Normally, modals are automatically filled only the first time
5262     *        they open. This tells the modal to refresh its content
5263     *        every time it opens.
5264     *
5265     * @param {string} [options.label]
5266     *        A text label for the modal, primarily for accessibility.
5267     *
5268     * @param {boolean} [options.temporary=true]
5269     *        If `true`, the modal can only be opened once; it will be
5270     *        disposed as soon as it's closed.
5271     *
5272     * @param {boolean} [options.uncloseable=false]
5273     *        If `true`, the user will not be able to close the modal
5274     *        through the UI in the normal ways. Programmatic closing is
5275     *        still possible.
5276     */
5277    function ModalDialog(player, options) {
5278      classCallCheck(this, ModalDialog);
5279
5280      var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
5281
5282      _this.opened_ = _this.hasBeenOpened_ = _this.hasBeenFilled_ = false;
5283
5284      _this.closeable(!_this.options_.uncloseable);
5285      _this.content(_this.options_.content);
5286
5287      // Make sure the contentEl is defined AFTER any children are initialized
5288      // because we only want the contents of the modal in the contentEl
5289      // (not the UI elements like the close button).
5290      _this.contentEl_ = createEl('div', {
5291        className: MODAL_CLASS_NAME + '-content'
5292      }, {
5293        role: 'document'
5294      });
5295
5296      _this.descEl_ = createEl('p', {
5297        className: MODAL_CLASS_NAME + '-description vjs-control-text',
5298        id: _this.el().getAttribute('aria-describedby')
5299      });
5300
5301      textContent(_this.descEl_, _this.description());
5302      _this.el_.appendChild(_this.descEl_);
5303      _this.el_.appendChild(_this.contentEl_);
5304      return _this;
5305    }
5306
5307    /**
5308     * Create the `ModalDialog`'s DOM element
5309     *
5310     * @return {Element}
5311     *         The DOM element that gets created.
5312     */
5313
5314
5315    ModalDialog.prototype.createEl = function createEl$$1() {
5316      return _Component.prototype.createEl.call(this, 'div', {
5317        className: this.buildCSSClass(),
5318        tabIndex: -1
5319      }, {
5320        'aria-describedby': this.id() + '_description',
5321        'aria-hidden': 'true',
5322        'aria-label': this.label(),
5323        'role': 'dialog'
5324      });
5325    };
5326
5327    ModalDialog.prototype.dispose = function dispose() {
5328      this.contentEl_ = null;
5329      this.descEl_ = null;
5330      this.previouslyActiveEl_ = null;
5331
5332      _Component.prototype.dispose.call(this);
5333    };
5334
5335    /**
5336     * Builds the default DOM `className`.
5337     *
5338     * @return {string}
5339     *         The DOM `className` for this object.
5340     */
5341
5342
5343    ModalDialog.prototype.buildCSSClass = function buildCSSClass() {
5344      return MODAL_CLASS_NAME + ' vjs-hidden ' + _Component.prototype.buildCSSClass.call(this);
5345    };
5346
5347    /**
5348     * Handles `keydown` events on the document, looking for ESC, which closes
5349     * the modal.
5350     *
5351     * @param {EventTarget~Event} e
5352     *        The keypress that triggered this event.
5353     *
5354     * @listens keydown
5355     */
5356
5357
5358    ModalDialog.prototype.handleKeyPress = function handleKeyPress(e) {
5359      if (e.which === ESC && this.closeable()) {
5360        this.close();
5361      }
5362    };
5363
5364    /**
5365     * Returns the label string for this modal. Primarily used for accessibility.
5366     *
5367     * @return {string}
5368     *         the localized or raw label of this modal.
5369     */
5370
5371
5372    ModalDialog.prototype.label = function label() {
5373      return this.localize(this.options_.label || 'Modal Window');
5374    };
5375
5376    /**
5377     * Returns the description string for this modal. Primarily used for
5378     * accessibility.
5379     *
5380     * @return {string}
5381     *         The localized or raw description of this modal.
5382     */
5383
5384
5385    ModalDialog.prototype.description = function description() {
5386      var desc = this.options_.description || this.localize('This is a modal window.');
5387
5388      // Append a universal closeability message if the modal is closeable.
5389      if (this.closeable()) {
5390        desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.');
5391      }
5392
5393      return desc;
5394    };
5395
5396    /**
5397     * Opens the modal.
5398     *
5399     * @fires ModalDialog#beforemodalopen
5400     * @fires ModalDialog#modalopen
5401     */
5402
5403
5404    ModalDialog.prototype.open = function open() {
5405      if (!this.opened_) {
5406        var player = this.player();
5407
5408        /**
5409          * Fired just before a `ModalDialog` is opened.
5410          *
5411          * @event ModalDialog#beforemodalopen
5412          * @type {EventTarget~Event}
5413          */
5414        this.trigger('beforemodalopen');
5415        this.opened_ = true;
5416
5417        // Fill content if the modal has never opened before and
5418        // never been filled.
5419        if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) {
5420          this.fill();
5421        }
5422
5423        // If the player was playing, pause it and take note of its previously
5424        // playing state.
5425        this.wasPlaying_ = !player.paused();
5426
5427        if (this.options_.pauseOnOpen && this.wasPlaying_) {
5428          player.pause();
5429        }
5430
5431        if (this.closeable()) {
5432          this.on(this.el_.ownerDocument, 'keydown', bind(this, this.handleKeyPress));
5433        }
5434
5435        // Hide controls and note if they were enabled.
5436        this.hadControls_ = player.controls();
5437        player.controls(false);
5438
5439        this.show();
5440        this.conditionalFocus_();
5441        this.el().setAttribute('aria-hidden', 'false');
5442
5443        /**
5444          * Fired just after a `ModalDialog` is opened.
5445          *
5446          * @event ModalDialog#modalopen
5447          * @type {EventTarget~Event}
5448          */
5449        this.trigger('modalopen');
5450        this.hasBeenOpened_ = true;
5451      }
5452    };
5453
5454    /**
5455     * If the `ModalDialog` is currently open or closed.
5456     *
5457     * @param  {boolean} [value]
5458     *         If given, it will open (`true`) or close (`false`) the modal.
5459     *
5460     * @return {boolean}
5461     *         the current open state of the modaldialog
5462     */
5463
5464
5465    ModalDialog.prototype.opened = function opened(value) {
5466      if (typeof value === 'boolean') {
5467        this[value ? 'open' : 'close']();
5468      }
5469      return this.opened_;
5470    };
5471
5472    /**
5473     * Closes the modal, does nothing if the `ModalDialog` is
5474     * not open.
5475     *
5476     * @fires ModalDialog#beforemodalclose
5477     * @fires ModalDialog#modalclose
5478     */
5479
5480
5481    ModalDialog.prototype.close = function close() {
5482      if (!this.opened_) {
5483        return;
5484      }
5485      var player = this.player();
5486
5487      /**
5488        * Fired just before a `ModalDialog` is closed.
5489        *
5490        * @event ModalDialog#beforemodalclose
5491        * @type {EventTarget~Event}
5492        */
5493      this.trigger('beforemodalclose');
5494      this.opened_ = false;
5495
5496      if (this.wasPlaying_ && this.options_.pauseOnOpen) {
5497        player.play();
5498      }
5499
5500      if (this.closeable()) {
5501        this.off(this.el_.ownerDocument, 'keydown', bind(this, this.handleKeyPress));
5502      }
5503
5504      if (this.hadControls_) {
5505        player.controls(true);
5506      }
5507
5508      this.hide();
5509      this.el().setAttribute('aria-hidden', 'true');
5510
5511      /**
5512        * Fired just after a `ModalDialog` is closed.
5513        *
5514        * @event ModalDialog#modalclose
5515        * @type {EventTarget~Event}
5516        */
5517      this.trigger('modalclose');
5518      this.conditionalBlur_();
5519
5520      if (this.options_.temporary) {
5521        this.dispose();
5522      }
5523    };
5524
5525    /**
5526     * Check to see if the `ModalDialog` is closeable via the UI.
5527     *
5528     * @param  {boolean} [value]
5529     *         If given as a boolean, it will set the `closeable` option.
5530     *
5531     * @return {boolean}
5532     *         Returns the final value of the closable option.
5533     */
5534
5535
5536    ModalDialog.prototype.closeable = function closeable(value) {
5537      if (typeof value === 'boolean') {
5538        var closeable = this.closeable_ = !!value;
5539        var close = this.getChild('closeButton');
5540
5541        // If this is being made closeable and has no close button, add one.
5542        if (closeable && !close) {
5543
5544          // The close button should be a child of the modal - not its
5545          // content element, so temporarily change the content element.
5546          var temp = this.contentEl_;
5547
5548          this.contentEl_ = this.el_;
5549          close = this.addChild('closeButton', { controlText: 'Close Modal Dialog' });
5550          this.contentEl_ = temp;
5551          this.on(close, 'close', this.close);
5552        }
5553
5554        // If this is being made uncloseable and has a close button, remove it.
5555        if (!closeable && close) {
5556          this.off(close, 'close', this.close);
5557          this.removeChild(close);
5558          close.dispose();
5559        }
5560      }
5561      return this.closeable_;
5562    };
5563
5564    /**
5565     * Fill the modal's content element with the modal's "content" option.
5566     * The content element will be emptied before this change takes place.
5567     */
5568
5569
5570    ModalDialog.prototype.fill = function fill() {
5571      this.fillWith(this.content());
5572    };
5573
5574    /**
5575     * Fill the modal's content element with arbitrary content.
5576     * The content element will be emptied before this change takes place.
5577     *
5578     * @fires ModalDialog#beforemodalfill
5579     * @fires ModalDialog#modalfill
5580     *
5581     * @param {Mixed} [content]
5582     *        The same rules apply to this as apply to the `content` option.
5583     */
5584
5585
5586    ModalDialog.prototype.fillWith = function fillWith(content) {
5587      var contentEl = this.contentEl();
5588      var parentEl = contentEl.parentNode;
5589      var nextSiblingEl = contentEl.nextSibling;
5590
5591      /**
5592       * Fired just before a `ModalDialog` is filled with content.
5593       *
5594       * @event ModalDialog#beforemodalfill
5595       * @type {EventTarget~Event}
5596       */
5597      this.trigger('beforemodalfill');
5598      this.hasBeenFilled_ = true;
5599
5600      // Detach the content element from the DOM before performing
5601      // manipulation to avoid modifying the live DOM multiple times.
5602      parentEl.removeChild(contentEl);
5603      this.empty();
5604      insertContent(contentEl, content);
5605      /**
5606       * Fired just after a `ModalDialog` is filled with content.
5607       *
5608       * @event ModalDialog#modalfill
5609       * @type {EventTarget~Event}
5610       */
5611      this.trigger('modalfill');
5612
5613      // Re-inject the re-filled content element.
5614      if (nextSiblingEl) {
5615        parentEl.insertBefore(contentEl, nextSiblingEl);
5616      } else {
5617        parentEl.appendChild(contentEl);
5618      }
5619
5620      // make sure that the close button is last in the dialog DOM
5621      var closeButton = this.getChild('closeButton');
5622
5623      if (closeButton) {
5624        parentEl.appendChild(closeButton.el_);
5625      }
5626    };
5627
5628    /**
5629     * Empties the content element. This happens anytime the modal is filled.
5630     *
5631     * @fires ModalDialog#beforemodalempty
5632     * @fires ModalDialog#modalempty
5633     */
5634
5635
5636    ModalDialog.prototype.empty = function empty() {
5637      /**
5638       * Fired just before a `ModalDialog` is emptied.
5639       *
5640       * @event ModalDialog#beforemodalempty
5641       * @type {EventTarget~Event}
5642       */
5643      this.trigger('beforemodalempty');
5644      emptyEl(this.contentEl());
5645
5646      /**
5647       * Fired just after a `ModalDialog` is emptied.
5648       *
5649       * @event ModalDialog#modalempty
5650       * @type {EventTarget~Event}
5651       */
5652      this.trigger('modalempty');
5653    };
5654
5655    /**
5656     * Gets or sets the modal content, which gets normalized before being
5657     * rendered into the DOM.
5658     *
5659     * This does not update the DOM or fill the modal, but it is called during
5660     * that process.
5661     *
5662     * @param  {Mixed} [value]
5663     *         If defined, sets the internal content value to be used on the
5664     *         next call(s) to `fill`. This value is normalized before being
5665     *         inserted. To "clear" the internal content value, pass `null`.
5666     *
5667     * @return {Mixed}
5668     *         The current content of the modal dialog
5669     */
5670
5671
5672    ModalDialog.prototype.content = function content(value) {
5673      if (typeof value !== 'undefined') {
5674        this.content_ = value;
5675      }
5676      return this.content_;
5677    };
5678
5679    /**
5680     * conditionally focus the modal dialog if focus was previously on the player.
5681     *
5682     * @private
5683     */
5684
5685
5686    ModalDialog.prototype.conditionalFocus_ = function conditionalFocus_() {
5687      var activeEl = document_1.activeElement;
5688      var playerEl = this.player_.el_;
5689
5690      this.previouslyActiveEl_ = null;
5691
5692      if (playerEl.contains(activeEl) || playerEl === activeEl) {
5693        this.previouslyActiveEl_ = activeEl;
5694
5695        this.focus();
5696
5697        this.on(document_1, 'keydown', this.handleKeyDown);
5698      }
5699    };
5700
5701    /**
5702     * conditionally blur the element and refocus the last focused element
5703     *
5704     * @private
5705     */
5706
5707
5708    ModalDialog.prototype.conditionalBlur_ = function conditionalBlur_() {
5709      if (this.previouslyActiveEl_) {
5710        this.previouslyActiveEl_.focus();
5711        this.previouslyActiveEl_ = null;
5712      }
5713
5714      this.off(document_1, 'keydown', this.handleKeyDown);
5715    };
5716
5717    /**
5718     * Keydown handler. Attached when modal is focused.
5719     *
5720     * @listens keydown
5721     */
5722
5723
5724    ModalDialog.prototype.handleKeyDown = function handleKeyDown(event) {
5725      // exit early if it isn't a tab key
5726      if (event.which !== 9) {
5727        return;
5728      }
5729
5730      var focusableEls = this.focusableEls_();
5731      var activeEl = this.el_.querySelector(':focus');
5732      var focusIndex = void 0;
5733
5734      for (var i = 0; i < focusableEls.length; i++) {
5735        if (activeEl === focusableEls[i]) {
5736          focusIndex = i;
5737          break;
5738        }
5739      }
5740
5741      if (document_1.activeElement === this.el_) {
5742        focusIndex = 0;
5743      }
5744
5745      if (event.shiftKey && focusIndex === 0) {
5746        focusableEls[focusableEls.length - 1].focus();
5747        event.preventDefault();
5748      } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) {
5749        focusableEls[0].focus();
5750        event.preventDefault();
5751      }
5752    };
5753
5754    /**
5755     * get all focusable elements
5756     *
5757     * @private
5758     */
5759
5760
5761    ModalDialog.prototype.focusableEls_ = function focusableEls_() {
5762      var allChildren = this.el_.querySelectorAll('*');
5763
5764      return Array.prototype.filter.call(allChildren, function (child) {
5765        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');
5766      });
5767    };
5768
5769    return ModalDialog;
5770  }(Component);
5771
5772  /**
5773   * Default options for `ModalDialog` default options.
5774   *
5775   * @type {Object}
5776   * @private
5777   */
5778
5779
5780  ModalDialog.prototype.options_ = {
5781    pauseOnOpen: true,
5782    temporary: true
5783  };
5784
5785  Component.registerComponent('ModalDialog', ModalDialog);
5786
5787  /**
5788   * @file track-list.js
5789   */
5790
5791  /**
5792   * Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and
5793   * {@link VideoTrackList}
5794   *
5795   * @extends EventTarget
5796   */
5797
5798  var TrackList = function (_EventTarget) {
5799    inherits(TrackList, _EventTarget);
5800
5801    /**
5802     * Create an instance of this class
5803     *
5804     * @param {Track[]} tracks
5805     *        A list of tracks to initialize the list with.
5806     *
5807     * @abstract
5808     */
5809    function TrackList() {
5810      var tracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
5811      classCallCheck(this, TrackList);
5812
5813      var _this = possibleConstructorReturn(this, _EventTarget.call(this));
5814
5815      _this.tracks_ = [];
5816
5817      /**
5818       * @memberof TrackList
5819       * @member {number} length
5820       *         The current number of `Track`s in the this Trackist.
5821       * @instance
5822       */
5823      Object.defineProperty(_this, 'length', {
5824        get: function get$$1() {
5825          return this.tracks_.length;
5826        }
5827      });
5828
5829      for (var i = 0; i < tracks.length; i++) {
5830        _this.addTrack(tracks[i]);
5831      }
5832      return _this;
5833    }
5834
5835    /**
5836     * Add a {@link Track} to the `TrackList`
5837     *
5838     * @param {Track} track
5839     *        The audio, video, or text track to add to the list.
5840     *
5841     * @fires TrackList#addtrack
5842     */
5843
5844
5845    TrackList.prototype.addTrack = function addTrack(track) {
5846      var index = this.tracks_.length;
5847
5848      if (!('' + index in this)) {
5849        Object.defineProperty(this, index, {
5850          get: function get$$1() {
5851            return this.tracks_[index];
5852          }
5853        });
5854      }
5855
5856      // Do not add duplicate tracks
5857      if (this.tracks_.indexOf(track) === -1) {
5858        this.tracks_.push(track);
5859        /**
5860         * Triggered when a track is added to a track list.
5861         *
5862         * @event TrackList#addtrack
5863         * @type {EventTarget~Event}
5864         * @property {Track} track
5865         *           A reference to track that was added.
5866         */
5867        this.trigger({
5868          track: track,
5869          type: 'addtrack'
5870        });
5871      }
5872    };
5873
5874    /**
5875     * Remove a {@link Track} from the `TrackList`
5876     *
5877     * @param {Track} rtrack
5878     *        The audio, video, or text track to remove from the list.
5879     *
5880     * @fires TrackList#removetrack
5881     */
5882
5883
5884    TrackList.prototype.removeTrack = function removeTrack(rtrack) {
5885      var track = void 0;
5886
5887      for (var i = 0, l = this.length; i < l; i++) {
5888        if (this[i] === rtrack) {
5889          track = this[i];
5890          if (track.off) {
5891            track.off();
5892          }
5893
5894          this.tracks_.splice(i, 1);
5895
5896          break;
5897        }
5898      }
5899
5900      if (!track) {
5901        return;
5902      }
5903
5904      /**
5905       * Triggered when a track is removed from track list.
5906       *
5907       * @event TrackList#removetrack
5908       * @type {EventTarget~Event}
5909       * @property {Track} track
5910       *           A reference to track that was removed.
5911       */
5912      this.trigger({
5913        track: track,
5914        type: 'removetrack'
5915      });
5916    };
5917
5918    /**
5919     * Get a Track from the TrackList by a tracks id
5920     *
5921     * @param {String} id - the id of the track to get
5922     * @method getTrackById
5923     * @return {Track}
5924     * @private
5925     */
5926
5927
5928    TrackList.prototype.getTrackById = function getTrackById(id) {
5929      var result = null;
5930
5931      for (var i = 0, l = this.length; i < l; i++) {
5932        var track = this[i];
5933
5934        if (track.id === id) {
5935          result = track;
5936          break;
5937        }
5938      }
5939
5940      return result;
5941    };
5942
5943    return TrackList;
5944  }(EventTarget);
5945
5946  /**
5947   * Triggered when a different track is selected/enabled.
5948   *
5949   * @event TrackList#change
5950   * @type {EventTarget~Event}
5951   */
5952
5953  /**
5954   * Events that can be called with on + eventName. See {@link EventHandler}.
5955   *
5956   * @property {Object} TrackList#allowedEvents_
5957   * @private
5958   */
5959
5960
5961  TrackList.prototype.allowedEvents_ = {
5962    change: 'change',
5963    addtrack: 'addtrack',
5964    removetrack: 'removetrack'
5965  };
5966
5967  // emulate attribute EventHandler support to allow for feature detection
5968  for (var event in TrackList.prototype.allowedEvents_) {
5969    TrackList.prototype['on' + event] = null;
5970  }
5971
5972  /**
5973   * @file audio-track-list.js
5974   */
5975
5976  /**
5977   * Anywhere we call this function we diverge from the spec
5978   * as we only support one enabled audiotrack at a time
5979   *
5980   * @param {AudioTrackList} list
5981   *        list to work on
5982   *
5983   * @param {AudioTrack} track
5984   *        The track to skip
5985   *
5986   * @private
5987   */
5988  var disableOthers = function disableOthers(list, track) {
5989    for (var i = 0; i < list.length; i++) {
5990      if (!Object.keys(list[i]).length || track.id === list[i].id) {
5991        continue;
5992      }
5993      // another audio track is enabled, disable it
5994      list[i].enabled = false;
5995    }
5996  };
5997
5998  /**
5999   * The current list of {@link AudioTrack} for a media file.
6000   *
6001   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist}
6002   * @extends TrackList
6003   */
6004
6005  var AudioTrackList = function (_TrackList) {
6006    inherits(AudioTrackList, _TrackList);
6007
6008    /**
6009     * Create an instance of this class.
6010     *
6011     * @param {AudioTrack[]} [tracks=[]]
6012     *        A list of `AudioTrack` to instantiate the list with.
6013     */
6014    function AudioTrackList() {
6015      var tracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
6016      classCallCheck(this, AudioTrackList);
6017
6018      // make sure only 1 track is enabled
6019      // sorted from last index to first index
6020      for (var i = tracks.length - 1; i >= 0; i--) {
6021        if (tracks[i].enabled) {
6022          disableOthers(tracks, tracks[i]);
6023          break;
6024        }
6025      }
6026
6027      var _this = possibleConstructorReturn(this, _TrackList.call(this, tracks));
6028
6029      _this.changing_ = false;
6030      return _this;
6031    }
6032
6033    /**
6034     * Add an {@link AudioTrack} to the `AudioTrackList`.
6035     *
6036     * @param {AudioTrack} track
6037     *        The AudioTrack to add to the list
6038     *
6039     * @fires TrackList#addtrack
6040     */
6041
6042
6043    AudioTrackList.prototype.addTrack = function addTrack(track) {
6044      var _this2 = this;
6045
6046      if (track.enabled) {
6047        disableOthers(this, track);
6048      }
6049
6050      _TrackList.prototype.addTrack.call(this, track);
6051      // native tracks don't have this
6052      if (!track.addEventListener) {
6053        return;
6054      }
6055
6056      /**
6057       * @listens AudioTrack#enabledchange
6058       * @fires TrackList#change
6059       */
6060      track.addEventListener('enabledchange', function () {
6061        // when we are disabling other tracks (since we don't support
6062        // more than one track at a time) we will set changing_
6063        // to true so that we don't trigger additional change events
6064        if (_this2.changing_) {
6065          return;
6066        }
6067        _this2.changing_ = true;
6068        disableOthers(_this2, track);
6069        _this2.changing_ = false;
6070        _this2.trigger('change');
6071      });
6072    };
6073
6074    return AudioTrackList;
6075  }(TrackList);
6076
6077  /**
6078   * @file video-track-list.js
6079   */
6080
6081  /**
6082   * Un-select all other {@link VideoTrack}s that are selected.
6083   *
6084   * @param {VideoTrackList} list
6085   *        list to work on
6086   *
6087   * @param {VideoTrack} track
6088   *        The track to skip
6089   *
6090   * @private
6091   */
6092  var disableOthers$1 = function disableOthers(list, track) {
6093    for (var i = 0; i < list.length; i++) {
6094      if (!Object.keys(list[i]).length || track.id === list[i].id) {
6095        continue;
6096      }
6097      // another video track is enabled, disable it
6098      list[i].selected = false;
6099    }
6100  };
6101
6102  /**
6103   * The current list of {@link VideoTrack} for a video.
6104   *
6105   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist}
6106   * @extends TrackList
6107   */
6108
6109  var VideoTrackList = function (_TrackList) {
6110    inherits(VideoTrackList, _TrackList);
6111
6112    /**
6113     * Create an instance of this class.
6114     *
6115     * @param {VideoTrack[]} [tracks=[]]
6116     *        A list of `VideoTrack` to instantiate the list with.
6117     */
6118    function VideoTrackList() {
6119      var tracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
6120      classCallCheck(this, VideoTrackList);
6121
6122      // make sure only 1 track is enabled
6123      // sorted from last index to first index
6124      for (var i = tracks.length - 1; i >= 0; i--) {
6125        if (tracks[i].selected) {
6126          disableOthers$1(tracks, tracks[i]);
6127          break;
6128        }
6129      }
6130
6131      var _this = possibleConstructorReturn(this, _TrackList.call(this, tracks));
6132
6133      _this.changing_ = false;
6134
6135      /**
6136       * @member {number} VideoTrackList#selectedIndex
6137       *         The current index of the selected {@link VideoTrack`}.
6138       */
6139      Object.defineProperty(_this, 'selectedIndex', {
6140        get: function get$$1() {
6141          for (var _i = 0; _i < this.length; _i++) {
6142            if (this[_i].selected) {
6143              return _i;
6144            }
6145          }
6146          return -1;
6147        },
6148        set: function set$$1() {}
6149      });
6150      return _this;
6151    }
6152
6153    /**
6154     * Add a {@link VideoTrack} to the `VideoTrackList`.
6155     *
6156     * @param {VideoTrack} track
6157     *        The VideoTrack to add to the list
6158     *
6159     * @fires TrackList#addtrack
6160     */
6161
6162
6163    VideoTrackList.prototype.addTrack = function addTrack(track) {
6164      var _this2 = this;
6165
6166      if (track.selected) {
6167        disableOthers$1(this, track);
6168      }
6169
6170      _TrackList.prototype.addTrack.call(this, track);
6171      // native tracks don't have this
6172      if (!track.addEventListener) {
6173        return;
6174      }
6175
6176      /**
6177       * @listens VideoTrack#selectedchange
6178       * @fires TrackList#change
6179       */
6180      track.addEventListener('selectedchange', function () {
6181        if (_this2.changing_) {
6182          return;
6183        }
6184        _this2.changing_ = true;
6185        disableOthers$1(_this2, track);
6186        _this2.changing_ = false;
6187        _this2.trigger('change');
6188      });
6189    };
6190
6191    return VideoTrackList;
6192  }(TrackList);
6193
6194  /**
6195   * @file text-track-list.js
6196   */
6197
6198  /**
6199   * The current list of {@link TextTrack} for a media file.
6200   *
6201   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist}
6202   * @extends TrackList
6203   */
6204
6205  var TextTrackList = function (_TrackList) {
6206    inherits(TextTrackList, _TrackList);
6207
6208    function TextTrackList() {
6209      classCallCheck(this, TextTrackList);
6210      return possibleConstructorReturn(this, _TrackList.apply(this, arguments));
6211    }
6212
6213    /**
6214     * Add a {@link TextTrack} to the `TextTrackList`
6215     *
6216     * @param {TextTrack} track
6217     *        The text track to add to the list.
6218     *
6219     * @fires TrackList#addtrack
6220     */
6221    TextTrackList.prototype.addTrack = function addTrack(track) {
6222      _TrackList.prototype.addTrack.call(this, track);
6223
6224      /**
6225       * @listens TextTrack#modechange
6226       * @fires TrackList#change
6227       */
6228      track.addEventListener('modechange', bind(this, function () {
6229        this.trigger('change');
6230      }));
6231
6232      var nonLanguageTextTrackKind = ['metadata', 'chapters'];
6233
6234      if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) {
6235        track.addEventListener('modechange', bind(this, function () {
6236          this.trigger('selectedlanguagechange');
6237        }));
6238      }
6239    };
6240
6241    return TextTrackList;
6242  }(TrackList);
6243
6244  /**
6245   * @file html-track-element-list.js
6246   */
6247
6248  /**
6249   * The current list of {@link HtmlTrackElement}s.
6250   */
6251  var HtmlTrackElementList = function () {
6252
6253    /**
6254     * Create an instance of this class.
6255     *
6256     * @param {HtmlTrackElement[]} [tracks=[]]
6257     *        A list of `HtmlTrackElement` to instantiate the list with.
6258     */
6259    function HtmlTrackElementList() {
6260      var trackElements = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
6261      classCallCheck(this, HtmlTrackElementList);
6262
6263      this.trackElements_ = [];
6264
6265      /**
6266       * @memberof HtmlTrackElementList
6267       * @member {number} length
6268       *         The current number of `Track`s in the this Trackist.
6269       * @instance
6270       */
6271      Object.defineProperty(this, 'length', {
6272        get: function get$$1() {
6273          return this.trackElements_.length;
6274        }
6275      });
6276
6277      for (var i = 0, length = trackElements.length; i < length; i++) {
6278        this.addTrackElement_(trackElements[i]);
6279      }
6280    }
6281
6282    /**
6283     * Add an {@link HtmlTrackElement} to the `HtmlTrackElementList`
6284     *
6285     * @param {HtmlTrackElement} trackElement
6286     *        The track element to add to the list.
6287     *
6288     * @private
6289     */
6290
6291
6292    HtmlTrackElementList.prototype.addTrackElement_ = function addTrackElement_(trackElement) {
6293      var index = this.trackElements_.length;
6294
6295      if (!('' + index in this)) {
6296        Object.defineProperty(this, index, {
6297          get: function get$$1() {
6298            return this.trackElements_[index];
6299          }
6300        });
6301      }
6302
6303      // Do not add duplicate elements
6304      if (this.trackElements_.indexOf(trackElement) === -1) {
6305        this.trackElements_.push(trackElement);
6306      }
6307    };
6308
6309    /**
6310     * Get an {@link HtmlTrackElement} from the `HtmlTrackElementList` given an
6311     * {@link TextTrack}.
6312     *
6313     * @param {TextTrack} track
6314     *        The track associated with a track element.
6315     *
6316     * @return {HtmlTrackElement|undefined}
6317     *         The track element that was found or undefined.
6318     *
6319     * @private
6320     */
6321
6322
6323    HtmlTrackElementList.prototype.getTrackElementByTrack_ = function getTrackElementByTrack_(track) {
6324      var trackElement_ = void 0;
6325
6326      for (var i = 0, length = this.trackElements_.length; i < length; i++) {
6327        if (track === this.trackElements_[i].track) {
6328          trackElement_ = this.trackElements_[i];
6329
6330          break;
6331        }
6332      }
6333
6334      return trackElement_;
6335    };
6336
6337    /**
6338     * Remove a {@link HtmlTrackElement} from the `HtmlTrackElementList`
6339     *
6340     * @param {HtmlTrackElement} trackElement
6341     *        The track element to remove from the list.
6342     *
6343     * @private
6344     */
6345
6346
6347    HtmlTrackElementList.prototype.removeTrackElement_ = function removeTrackElement_(trackElement) {
6348      for (var i = 0, length = this.trackElements_.length; i < length; i++) {
6349        if (trackElement === this.trackElements_[i]) {
6350          this.trackElements_.splice(i, 1);
6351
6352          break;
6353        }
6354      }
6355    };
6356
6357    return HtmlTrackElementList;
6358  }();
6359
6360  /**
6361   * @file text-track-cue-list.js
6362   */
6363
6364  /**
6365   * @typedef {Object} TextTrackCueList~TextTrackCue
6366   *
6367   * @property {string} id
6368   *           The unique id for this text track cue
6369   *
6370   * @property {number} startTime
6371   *           The start time for this text track cue
6372   *
6373   * @property {number} endTime
6374   *           The end time for this text track cue
6375   *
6376   * @property {boolean} pauseOnExit
6377   *           Pause when the end time is reached if true.
6378   *
6379   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcue}
6380   */
6381
6382  /**
6383   * A List of TextTrackCues.
6384   *
6385   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist}
6386   */
6387  var TextTrackCueList = function () {
6388
6389    /**
6390     * Create an instance of this class..
6391     *
6392     * @param {Array} cues
6393     *        A list of cues to be initialized with
6394     */
6395    function TextTrackCueList(cues) {
6396      classCallCheck(this, TextTrackCueList);
6397
6398      TextTrackCueList.prototype.setCues_.call(this, cues);
6399
6400      /**
6401       * @memberof TextTrackCueList
6402       * @member {number} length
6403       *         The current number of `TextTrackCue`s in the TextTrackCueList.
6404       * @instance
6405       */
6406      Object.defineProperty(this, 'length', {
6407        get: function get$$1() {
6408          return this.length_;
6409        }
6410      });
6411    }
6412
6413    /**
6414     * A setter for cues in this list. Creates getters
6415     * an an index for the cues.
6416     *
6417     * @param {Array} cues
6418     *        An array of cues to set
6419     *
6420     * @private
6421     */
6422
6423
6424    TextTrackCueList.prototype.setCues_ = function setCues_(cues) {
6425      var oldLength = this.length || 0;
6426      var i = 0;
6427      var l = cues.length;
6428
6429      this.cues_ = cues;
6430      this.length_ = cues.length;
6431
6432      var defineProp = function defineProp(index) {
6433        if (!('' + index in this)) {
6434          Object.defineProperty(this, '' + index, {
6435            get: function get$$1() {
6436              return this.cues_[index];
6437            }
6438          });
6439        }
6440      };
6441
6442      if (oldLength < l) {
6443        i = oldLength;
6444
6445        for (; i < l; i++) {
6446          defineProp.call(this, i);
6447        }
6448      }
6449    };
6450
6451    /**
6452     * Get a `TextTrackCue` that is currently in the `TextTrackCueList` by id.
6453     *
6454     * @param {string} id
6455     *        The id of the cue that should be searched for.
6456     *
6457     * @return {TextTrackCueList~TextTrackCue|null}
6458     *         A single cue or null if none was found.
6459     */
6460
6461
6462    TextTrackCueList.prototype.getCueById = function getCueById(id) {
6463      var result = null;
6464
6465      for (var i = 0, l = this.length; i < l; i++) {
6466        var cue = this[i];
6467
6468        if (cue.id === id) {
6469          result = cue;
6470          break;
6471        }
6472      }
6473
6474      return result;
6475    };
6476
6477    return TextTrackCueList;
6478  }();
6479
6480  /**
6481   * @file track-kinds.js
6482   */
6483
6484  /**
6485   * All possible `VideoTrackKind`s
6486   *
6487   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind
6488   * @typedef VideoTrack~Kind
6489   * @enum
6490   */
6491  var VideoTrackKind = {
6492    alternative: 'alternative',
6493    captions: 'captions',
6494    main: 'main',
6495    sign: 'sign',
6496    subtitles: 'subtitles',
6497    commentary: 'commentary'
6498  };
6499
6500  /**
6501   * All possible `AudioTrackKind`s
6502   *
6503   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind
6504   * @typedef AudioTrack~Kind
6505   * @enum
6506   */
6507  var AudioTrackKind = {
6508    'alternative': 'alternative',
6509    'descriptions': 'descriptions',
6510    'main': 'main',
6511    'main-desc': 'main-desc',
6512    'translation': 'translation',
6513    'commentary': 'commentary'
6514  };
6515
6516  /**
6517   * All possible `TextTrackKind`s
6518   *
6519   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-texttrack-kind
6520   * @typedef TextTrack~Kind
6521   * @enum
6522   */
6523  var TextTrackKind = {
6524    subtitles: 'subtitles',
6525    captions: 'captions',
6526    descriptions: 'descriptions',
6527    chapters: 'chapters',
6528    metadata: 'metadata'
6529  };
6530
6531  /**
6532   * All possible `TextTrackMode`s
6533   *
6534   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode
6535   * @typedef TextTrack~Mode
6536   * @enum
6537   */
6538  var TextTrackMode = {
6539    disabled: 'disabled',
6540    hidden: 'hidden',
6541    showing: 'showing'
6542  };
6543
6544  /**
6545   * @file track.js
6546   */
6547
6548  /**
6549   * A Track class that contains all of the common functionality for {@link AudioTrack},
6550   * {@link VideoTrack}, and {@link TextTrack}.
6551   *
6552   * > Note: This class should not be used directly
6553   *
6554   * @see {@link https://html.spec.whatwg.org/multipage/embedded-content.html}
6555   * @extends EventTarget
6556   * @abstract
6557   */
6558
6559  var Track = function (_EventTarget) {
6560    inherits(Track, _EventTarget);
6561
6562    /**
6563     * Create an instance of this class.
6564     *
6565     * @param {Object} [options={}]
6566     *        Object of option names and values
6567     *
6568     * @param {string} [options.kind='']
6569     *        A valid kind for the track type you are creating.
6570     *
6571     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
6572     *        A unique id for this AudioTrack.
6573     *
6574     * @param {string} [options.label='']
6575     *        The menu label for this track.
6576     *
6577     * @param {string} [options.language='']
6578     *        A valid two character language code.
6579     *
6580     * @abstract
6581     */
6582    function Track() {
6583      var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
6584      classCallCheck(this, Track);
6585
6586      var _this = possibleConstructorReturn(this, _EventTarget.call(this));
6587
6588      var trackProps = {
6589        id: options.id || 'vjs_track_' + newGUID(),
6590        kind: options.kind || '',
6591        label: options.label || '',
6592        language: options.language || ''
6593      };
6594
6595      /**
6596       * @memberof Track
6597       * @member {string} id
6598       *         The id of this track. Cannot be changed after creation.
6599       * @instance
6600       *
6601       * @readonly
6602       */
6603
6604      /**
6605       * @memberof Track
6606       * @member {string} kind
6607       *         The kind of track that this is. Cannot be changed after creation.
6608       * @instance
6609       *
6610       * @readonly
6611       */
6612
6613      /**
6614       * @memberof Track
6615       * @member {string} label
6616       *         The label of this track. Cannot be changed after creation.
6617       * @instance
6618       *
6619       * @readonly
6620       */
6621
6622      /**
6623       * @memberof Track
6624       * @member {string} language
6625       *         The two letter language code for this track. Cannot be changed after
6626       *         creation.
6627       * @instance
6628       *
6629       * @readonly
6630       */
6631
6632      var _loop = function _loop(key) {
6633        Object.defineProperty(_this, key, {
6634          get: function get$$1() {
6635            return trackProps[key];
6636          },
6637          set: function set$$1() {}
6638        });
6639      };
6640
6641      for (var key in trackProps) {
6642        _loop(key);
6643      }
6644      return _this;
6645    }
6646
6647    return Track;
6648  }(EventTarget);
6649
6650  /**
6651   * @file url.js
6652   * @module url
6653   */
6654
6655  /**
6656   * @typedef {Object} url:URLObject
6657   *
6658   * @property {string} protocol
6659   *           The protocol of the url that was parsed.
6660   *
6661   * @property {string} hostname
6662   *           The hostname of the url that was parsed.
6663   *
6664   * @property {string} port
6665   *           The port of the url that was parsed.
6666   *
6667   * @property {string} pathname
6668   *           The pathname of the url that was parsed.
6669   *
6670   * @property {string} search
6671   *           The search query of the url that was parsed.
6672   *
6673   * @property {string} hash
6674   *           The hash of the url that was parsed.
6675   *
6676   * @property {string} host
6677   *           The host of the url that was parsed.
6678   */
6679
6680  /**
6681   * Resolve and parse the elements of a URL.
6682   *
6683   * @param  {String} url
6684   *         The url to parse
6685   *
6686   * @return {url:URLObject}
6687   *         An object of url details
6688   */
6689  var parseUrl = function parseUrl(url) {
6690    var props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host'];
6691
6692    // add the url to an anchor and let the browser parse the URL
6693    var a = document_1.createElement('a');
6694
6695    a.href = url;
6696
6697    // IE8 (and 9?) Fix
6698    // ie8 doesn't parse the URL correctly until the anchor is actually
6699    // added to the body, and an innerHTML is needed to trigger the parsing
6700    var addToBody = a.host === '' && a.protocol !== 'file:';
6701    var div = void 0;
6702
6703    if (addToBody) {
6704      div = document_1.createElement('div');
6705      div.innerHTML = '<a href="' + url + '"></a>';
6706      a = div.firstChild;
6707      // prevent the div from affecting layout
6708      div.setAttribute('style', 'display:none; position:absolute;');
6709      document_1.body.appendChild(div);
6710    }
6711
6712    // Copy the specific URL properties to a new object
6713    // This is also needed for IE8 because the anchor loses its
6714    // properties when it's removed from the dom
6715    var details = {};
6716
6717    for (var i = 0; i < props.length; i++) {
6718      details[props[i]] = a[props[i]];
6719    }
6720
6721    // IE9 adds the port to the host property unlike everyone else. If
6722    // a port identifier is added for standard ports, strip it.
6723    if (details.protocol === 'http:') {
6724      details.host = details.host.replace(/:80$/, '');
6725    }
6726
6727    if (details.protocol === 'https:') {
6728      details.host = details.host.replace(/:443$/, '');
6729    }
6730
6731    if (!details.protocol) {
6732      details.protocol = window_1.location.protocol;
6733    }
6734
6735    if (addToBody) {
6736      document_1.body.removeChild(div);
6737    }
6738
6739    return details;
6740  };
6741
6742  /**
6743   * Get absolute version of relative URL. Used to tell flash correct URL.
6744   *
6745   *
6746   * @param  {string} url
6747   *         URL to make absolute
6748   *
6749   * @return {string}
6750   *         Absolute URL
6751   *
6752   * @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
6753   */
6754  var getAbsoluteURL = function getAbsoluteURL(url) {
6755    // Check if absolute URL
6756    if (!url.match(/^https?:\/\//)) {
6757      // Convert to absolute URL. Flash hosted off-site needs an absolute URL.
6758      var div = document_1.createElement('div');
6759
6760      div.innerHTML = '<a href="' + url + '">x</a>';
6761      url = div.firstChild.href;
6762    }
6763
6764    return url;
6765  };
6766
6767  /**
6768   * Returns the extension of the passed file name. It will return an empty string
6769   * if passed an invalid path.
6770   *
6771   * @param {string} path
6772   *        The fileName path like '/path/to/file.mp4'
6773   *
6774   * @returns {string}
6775   *          The extension in lower case or an empty string if no
6776   *          extension could be found.
6777   */
6778  var getFileExtension = function getFileExtension(path) {
6779    if (typeof path === 'string') {
6780      var splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/i;
6781      var pathParts = splitPathRe.exec(path);
6782
6783      if (pathParts) {
6784        return pathParts.pop().toLowerCase();
6785      }
6786    }
6787
6788    return '';
6789  };
6790
6791  /**
6792   * Returns whether the url passed is a cross domain request or not.
6793   *
6794   * @param {string} url
6795   *        The url to check.
6796   *
6797   * @return {boolean}
6798   *         Whether it is a cross domain request or not.
6799   */
6800  var isCrossOrigin = function isCrossOrigin(url) {
6801    var winLoc = window_1.location;
6802    var urlInfo = parseUrl(url);
6803
6804    // IE8 protocol relative urls will return ':' for protocol
6805    var srcProtocol = urlInfo.protocol === ':' ? winLoc.protocol : urlInfo.protocol;
6806
6807    // Check if url is for another domain/origin
6808    // IE8 doesn't know location.origin, so we won't rely on it here
6809    var crossOrigin = srcProtocol + urlInfo.host !== winLoc.protocol + winLoc.host;
6810
6811    return crossOrigin;
6812  };
6813
6814  var Url = /*#__PURE__*/Object.freeze({
6815    parseUrl: parseUrl,
6816    getAbsoluteURL: getAbsoluteURL,
6817    getFileExtension: getFileExtension,
6818    isCrossOrigin: isCrossOrigin
6819  });
6820
6821  var isFunction_1 = isFunction;
6822
6823  var toString$1 = Object.prototype.toString;
6824
6825  function isFunction(fn) {
6826    var string = toString$1.call(fn);
6827    return string === '[object Function]' || typeof fn === 'function' && string !== '[object RegExp]' || typeof window !== 'undefined' && (
6828    // IE8 and below
6829    fn === window.setTimeout || fn === window.alert || fn === window.confirm || fn === window.prompt);
6830  }
6831
6832  var isFunction$1 = /*#__PURE__*/Object.freeze({
6833    default: isFunction_1,
6834    __moduleExports: isFunction_1
6835  });
6836
6837  var trim_1 = createCommonjsModule(function (module, exports) {
6838    exports = module.exports = trim;
6839
6840    function trim(str) {
6841      return str.replace(/^\s*|\s*$/g, '');
6842    }
6843
6844    exports.left = function (str) {
6845      return str.replace(/^\s*/, '');
6846    };
6847
6848    exports.right = function (str) {
6849      return str.replace(/\s*$/, '');
6850    };
6851  });
6852  var trim_2 = trim_1.left;
6853  var trim_3 = trim_1.right;
6854
6855  var trim = /*#__PURE__*/Object.freeze({
6856    default: trim_1,
6857    __moduleExports: trim_1,
6858    left: trim_2,
6859    right: trim_3
6860  });
6861
6862  var isFunction$2 = ( isFunction$1 && isFunction_1 ) || isFunction$1;
6863
6864  var forEach_1 = forEach;
6865
6866  var toString$2 = Object.prototype.toString;
6867  var hasOwnProperty = Object.prototype.hasOwnProperty;
6868
6869  function forEach(list, iterator, context) {
6870      if (!isFunction$2(iterator)) {
6871          throw new TypeError('iterator must be a function');
6872      }
6873
6874      if (arguments.length < 3) {
6875          context = this;
6876      }
6877
6878      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);
6879  }
6880
6881  function forEachArray(array, iterator, context) {
6882      for (var i = 0, len = array.length; i < len; i++) {
6883          if (hasOwnProperty.call(array, i)) {
6884              iterator.call(context, array[i], i, array);
6885          }
6886      }
6887  }
6888
6889  function forEachString(string, iterator, context) {
6890      for (var i = 0, len = string.length; i < len; i++) {
6891          // no such thing as a sparse string.
6892          iterator.call(context, string.charAt(i), i, string);
6893      }
6894  }
6895
6896  function forEachObject(object, iterator, context) {
6897      for (var k in object) {
6898          if (hasOwnProperty.call(object, k)) {
6899              iterator.call(context, object[k], k, object);
6900          }
6901      }
6902  }
6903
6904  var forEach$1 = /*#__PURE__*/Object.freeze({
6905    default: forEach_1,
6906    __moduleExports: forEach_1
6907  });
6908
6909  var trim$1 = ( trim && trim_1 ) || trim;
6910
6911  var forEach$2 = ( forEach$1 && forEach_1 ) || forEach$1;
6912
6913  var isArray = function isArray(arg) {
6914    return Object.prototype.toString.call(arg) === '[object Array]';
6915  };
6916
6917  var parseHeaders = function parseHeaders(headers) {
6918    if (!headers) return {};
6919
6920    var result = {};
6921
6922    forEach$2(trim$1(headers).split('\n'), function (row) {
6923      var index = row.indexOf(':'),
6924          key = trim$1(row.slice(0, index)).toLowerCase(),
6925          value = trim$1(row.slice(index + 1));
6926
6927      if (typeof result[key] === 'undefined') {
6928        result[key] = value;
6929      } else if (isArray(result[key])) {
6930        result[key].push(value);
6931      } else {
6932        result[key] = [result[key], value];
6933      }
6934    });
6935
6936    return result;
6937  };
6938
6939  var parseHeaders$1 = /*#__PURE__*/Object.freeze({
6940    default: parseHeaders,
6941    __moduleExports: parseHeaders
6942  });
6943
6944  var immutable = extend;
6945
6946  var hasOwnProperty$1 = Object.prototype.hasOwnProperty;
6947
6948  function extend() {
6949      var target = {};
6950
6951      for (var i = 0; i < arguments.length; i++) {
6952          var source = arguments[i];
6953
6954          for (var key in source) {
6955              if (hasOwnProperty$1.call(source, key)) {
6956                  target[key] = source[key];
6957              }
6958          }
6959      }
6960
6961      return target;
6962  }
6963
6964  var immutable$1 = /*#__PURE__*/Object.freeze({
6965    default: immutable,
6966    __moduleExports: immutable
6967  });
6968
6969  var parseHeaders$2 = ( parseHeaders$1 && parseHeaders ) || parseHeaders$1;
6970
6971  var xtend = ( immutable$1 && immutable ) || immutable$1;
6972
6973  var xhr = createXHR;
6974  createXHR.XMLHttpRequest = window_1.XMLHttpRequest || noop;
6975  createXHR.XDomainRequest = "withCredentials" in new createXHR.XMLHttpRequest() ? createXHR.XMLHttpRequest : window_1.XDomainRequest;
6976
6977  forEachArray$1(["get", "put", "post", "patch", "head", "delete"], function (method) {
6978      createXHR[method === "delete" ? "del" : method] = function (uri, options, callback) {
6979          options = initParams(uri, options, callback);
6980          options.method = method.toUpperCase();
6981          return _createXHR(options);
6982      };
6983  });
6984
6985  function forEachArray$1(array, iterator) {
6986      for (var i = 0; i < array.length; i++) {
6987          iterator(array[i]);
6988      }
6989  }
6990
6991  function isEmpty(obj) {
6992      for (var i in obj) {
6993          if (obj.hasOwnProperty(i)) return false;
vendor: 4,140 bytes, lines 6994-7131
6994      }
6995      return true;
6996  }
6997
6998  function initParams(uri, options, callback) {
6999      var params = uri;
7000
7001      if (isFunction$2(options)) {
7002          callback = options;
7003          if (typeof uri === "string") {
7004              params = { uri: uri };
7005          }
7006      } else {
7007          params = xtend(options, { uri: uri });
7008      }
7009
7010      params.callback = callback;
7011      return params;
7012  }
7013
7014  function createXHR(uri, options, callback) {
7015      options = initParams(uri, options, callback);
7016      return _createXHR(options);
7017  }
7018
7019  function _createXHR(options) {
7020      if (typeof options.callback === "undefined") {
7021          throw new Error("callback argument missing");
7022      }
7023
7024      var called = false;
7025      var callback = function cbOnce(err, response, body) {
7026          if (!called) {
7027              called = true;
7028              options.callback(err, response, body);
7029          }
7030      };
7031
7032      function readystatechange() {
7033          if (xhr.readyState === 4) {
7034              setTimeout(loadFunc, 0);
7035          }
7036      }
7037
7038      function getBody() {
7039          // Chrome with requestType=blob throws errors arround when even testing access to responseText
7040          var body = undefined;
7041
7042          if (xhr.response) {
7043              body = xhr.response;
7044          } else {
7045              body = xhr.responseText || getXml(xhr);
7046          }
7047
7048          if (isJson) {
7049              try {
7050                  body = JSON.parse(body);
7051              } catch (e) {}
7052          }
7053
7054          return body;
7055      }
7056
7057      function errorFunc(evt) {
7058          clearTimeout(timeoutTimer);
7059          if (!(evt instanceof Error)) {
7060              evt = new Error("" + (evt || "Unknown XMLHttpRequest Error"));
7061          }
7062          evt.statusCode = 0;
7063          return callback(evt, failureResponse);
7064      }
7065
7066      // will load the data & process the response in a special response object
7067      function loadFunc() {
7068          if (aborted) return;
7069          var status;
7070          clearTimeout(timeoutTimer);
7071          if (options.useXDR && xhr.status === undefined) {
7072              //IE8 CORS GET successful response doesn't have a status field, but body is fine
7073              status = 200;
7074          } else {
7075              status = xhr.status === 1223 ? 204 : xhr.status;
7076          }
7077          var response = failureResponse;
7078          var err = null;
7079
7080          if (status !== 0) {
7081              response = {
7082                  body: getBody(),
7083                  statusCode: status,
7084                  method: method,
7085                  headers: {},
7086                  url: uri,
7087                  rawRequest: xhr
7088              };
7089              if (xhr.getAllResponseHeaders) {
7090                  //remember xhr can in fact be XDR for CORS in IE
7091                  response.headers = parseHeaders$2(xhr.getAllResponseHeaders());
7092              }
7093          } else {
7094              err = new Error("Internal XMLHttpRequest Error");
7095          }
7096          return callback(err, response, response.body);
7097      }
7098
7099      var xhr = options.xhr || null;
7100
7101      if (!xhr) {
7102          if (options.cors || options.useXDR) {
7103              xhr = new createXHR.XDomainRequest();
7104          } else {
7105              xhr = new createXHR.XMLHttpRequest();
7106          }
7107      }
7108
7109      var key;
7110      var aborted;
7111      var uri = xhr.url = options.uri || options.url;
7112      var method = xhr.method = options.method || "GET";
7113      var body = options.body || options.data;
7114      var headers = xhr.headers = options.headers || {};
7115      var sync = !!options.sync;
7116      var isJson = false;
7117      var timeoutTimer;
7118      var failureResponse = {
7119          body: undefined,
7120          headers: {},
7121          statusCode: 0,
7122          method: method,
7123          url: uri,
7124          rawRequest: xhr
7125      };
7126
7127      if ("json" in options && options.json !== false) {
7128          isJson = true;
7129          headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json"); //Don't override existing accept header declared by user
7130          if (method !== "GET" && method !== "HEAD") {
7131              headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "a
vendor: 6,684 bytes, lines 7131-7352
7131pplication/json"); //Don't override existing accept header declared by user
7132              body = JSON.stringify(options.json === true ? body : options.json);
7133          }
7134      }
7135
7136      xhr.onreadystatechange = readystatechange;
7137      xhr.onload = loadFunc;
7138      xhr.onerror = errorFunc;
7139      // IE9 must have onprogress be set to a unique function.
7140      xhr.onprogress = function () {
7141          // IE must die
7142      };
7143      xhr.onabort = function () {
7144          aborted = true;
7145      };
7146      xhr.ontimeout = errorFunc;
7147      xhr.open(method, uri, !sync, options.username, options.password);
7148      //has to be after open
7149      if (!sync) {
7150          xhr.withCredentials = !!options.withCredentials;
7151      }
7152      // Cannot set timeout with sync request
7153      // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly
7154      // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent
7155      if (!sync && options.timeout > 0) {
7156          timeoutTimer = setTimeout(function () {
7157              if (aborted) return;
7158              aborted = true; //IE9 may still call readystatechange
7159              xhr.abort("timeout");
7160              var e = new Error("XMLHttpRequest timeout");
7161              e.code = "ETIMEDOUT";
7162              errorFunc(e);
7163          }, options.timeout);
7164      }
7165
7166      if (xhr.setRequestHeader) {
7167          for (key in headers) {
7168              if (headers.hasOwnProperty(key)) {
7169                  xhr.setRequestHeader(key, headers[key]);
7170              }
7171          }
7172      } else if (options.headers && !isEmpty(options.headers)) {
7173          throw new Error("Headers cannot be set on an XDomainRequest object");
7174      }
7175
7176      if ("responseType" in options) {
7177          xhr.responseType = options.responseType;
7178      }
7179
7180      if ("beforeSend" in options && typeof options.beforeSend === "function") {
7181          options.beforeSend(xhr);
7182      }
7183
7184      // Microsoft Edge browser sends "undefined" when send is called with undefined value.
7185      // XMLHttpRequest spec says to pass null as body to indicate no body
7186      // See https://github.com/naugtur/xhr/issues/100.
7187      xhr.send(body || null);
7188
7189      return xhr;
7190  }
7191
7192  function getXml(xhr) {
7193      if (xhr.responseType === "document") {
7194          return xhr.responseXML;
7195      }
7196      var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror";
7197      if (xhr.responseType === "" && !firefoxBugTakenEffect) {
7198          return xhr.responseXML;
7199      }
7200
7201      return null;
7202  }
7203
7204  function noop() {}
7205
7206  /**
7207   * @file text-track.js
7208   */
7209
7210  /**
7211   * Takes a webvtt file contents and parses it into cues
7212   *
7213   * @param {string} srcContent
7214   *        webVTT file contents
7215   *
7216   * @param {TextTrack} track
7217   *        TextTrack to add cues to. Cues come from the srcContent.
7218   *
7219   * @private
7220   */
7221  var parseCues = function parseCues(srcContent, track) {
7222    var parser = new window_1.WebVTT.Parser(window_1, window_1.vttjs, window_1.WebVTT.StringDecoder());
7223    var errors = [];
7224
7225    parser.oncue = function (cue) {
7226      track.addCue(cue);
7227    };
7228
7229    parser.onparsingerror = function (error) {
7230      errors.push(error);
7231    };
7232
7233    parser.onflush = function () {
7234      track.trigger({
7235        type: 'loadeddata',
7236        target: track
7237      });
7238    };
7239
7240    parser.parse(srcContent);
7241    if (errors.length > 0) {
7242      if (window_1.console && window_1.console.groupCollapsed) {
7243        window_1.console.groupCollapsed('Text Track parsing errors for ' + track.src);
7244      }
7245      errors.forEach(function (error) {
7246        return log$1.error(error);
7247      });
7248      if (window_1.console && window_1.console.groupEnd) {
7249        window_1.console.groupEnd();
7250      }
7251    }
7252
7253    parser.flush();
7254  };
7255
7256  /**
7257   * Load a `TextTrack` from a specified url.
7258   *
7259   * @param {string} src
7260   *        Url to load track from.
7261   *
7262   * @param {TextTrack} track
7263   *        Track to add cues to. Comes from the content at the end of `url`.
7264   *
7265   * @private
7266   */
7267  var loadTrack = function loadTrack(src, track) {
7268    var opts = {
7269      uri: src
7270    };
7271    var crossOrigin = isCrossOrigin(src);
7272
7273    if (crossOrigin) {
7274      opts.cors = crossOrigin;
7275    }
7276
7277    xhr(opts, bind(this, function (err, response, responseBody) {
7278      if (err) {
7279        return log$1.error(err, response);
7280      }
7281
7282      track.loaded_ = true;
7283
7284      // Make sure that vttjs has loaded, otherwise, wait till it finished loading
7285      // NOTE: this is only used for the alt/video.novtt.js build
7286      if (typeof window_1.WebVTT !== 'function') {
7287        if (track.tech_) {
7288          var loadHandler = function loadHandler() {
7289            return parseCues(responseBody, track);
7290          };
7291
7292          track.tech_.on('vttjsloaded', loadHandler);
7293          track.tech_.on('vttjserror', function () {
7294            log$1.error('vttjs failed to load, stopping trying to process ' + track.src);
7295            track.tech_.off('vttjsloaded', loadHandler);
7296          });
7297        }
7298      } else {
7299        parseCues(responseBody, track);
7300      }
7301    }));
7302  };
7303
7304  /**
7305   * A representation of a single `TextTrack`.
7306   *
7307   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack}
7308   * @extends Track
7309   */
7310
7311  var TextTrack = function (_Track) {
7312    inherits(TextTrack, _Track);
7313
7314    /**
7315     * Create an instance of this class.
7316     *
7317     * @param {Object} options={}
7318     *        Object of option names and values
7319     *
7320     * @param {Tech} options.tech
7321     *        A reference to the tech that owns this TextTrack.
7322     *
7323     * @param {TextTrack~Kind} [options.kind='subtitles']
7324     *        A valid text track kind.
7325     *
7326     * @param {TextTrack~Mode} [options.mode='disabled']
7327     *        A valid text track mode.
7328     *
7329     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
7330     *        A unique id for this TextTrack.
7331     *
7332     * @param {string} [options.label='']
7333     *        The menu label for this track.
7334     *
7335     * @param {string} [options.language='']
7336     *        A valid two character language code.
7337     *
7338     * @param {string} [options.srclang='']
7339     *        A valid two character language code. An alternative, but deprioritized
7340     *        version of `options.language`
7341     *
7342     * @param {string} [options.src]
7343     *        A url to TextTrack cues.
7344     *
7345     * @param {boolean} [options.default]
7346     *        If this track should default to on or off.
7347     */
7348    function TextTrack() {
7349      var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
7350      classCallCheck(this, TextTrack);
7351
7352      if (!options.tech) {
vendor: 17,280 bytes, lines 7353-7967
7353        throw new Error('A tech was not provided.');
7354      }
7355
7356      var settings = mergeOptions(options, {
7357        kind: TextTrackKind[options.kind] || 'subtitles',
7358        language: options.language || options.srclang || ''
7359      });
7360      var mode = TextTrackMode[settings.mode] || 'disabled';
7361      var default_ = settings.default;
7362
7363      if (settings.kind === 'metadata' || settings.kind === 'chapters') {
7364        mode = 'hidden';
7365      }
7366
7367      var _this = possibleConstructorReturn(this, _Track.call(this, settings));
7368
7369      _this.tech_ = settings.tech;
7370
7371      _this.cues_ = [];
7372      _this.activeCues_ = [];
7373
7374      var cues = new TextTrackCueList(_this.cues_);
7375      var activeCues = new TextTrackCueList(_this.activeCues_);
7376      var changed = false;
7377      var timeupdateHandler = bind(_this, function () {
7378
7379        // Accessing this.activeCues for the side-effects of updating itself
7380        // due to it's nature as a getter function. Do not remove or cues will
7381        // stop updating!
7382        /* eslint-disable no-unused-expressions */
7383        this.activeCues;
7384        /* eslint-enable no-unused-expressions */
7385        if (changed) {
7386          this.trigger('cuechange');
7387          changed = false;
7388        }
7389      });
7390
7391      if (mode !== 'disabled') {
7392        _this.tech_.ready(function () {
7393          _this.tech_.on('timeupdate', timeupdateHandler);
7394        }, true);
7395      }
7396
7397      Object.defineProperties(_this, {
7398        /**
7399         * @memberof TextTrack
7400         * @member {boolean} default
7401         *         If this track was set to be on or off by default. Cannot be changed after
7402         *         creation.
7403         * @instance
7404         *
7405         * @readonly
7406         */
7407        default: {
7408          get: function get$$1() {
7409            return default_;
7410          },
7411          set: function set$$1() {}
7412        },
7413
7414        /**
7415         * @memberof TextTrack
7416         * @member {string} mode
7417         *         Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will
7418         *         not be set if setting to an invalid mode.
7419         * @instance
7420         *
7421         * @fires TextTrack#modechange
7422         */
7423        mode: {
7424          get: function get$$1() {
7425            return mode;
7426          },
7427          set: function set$$1(newMode) {
7428            var _this2 = this;
7429
7430            if (!TextTrackMode[newMode]) {
7431              return;
7432            }
7433            mode = newMode;
7434            if (mode === 'showing') {
7435
7436              this.tech_.ready(function () {
7437                _this2.tech_.on('timeupdate', timeupdateHandler);
7438              }, true);
7439            }
7440            /**
7441             * An event that fires when mode changes on this track. This allows
7442             * the TextTrackList that holds this track to act accordingly.
7443             *
7444             * > Note: This is not part of the spec!
7445             *
7446             * @event TextTrack#modechange
7447             * @type {EventTarget~Event}
7448             */
7449            this.trigger('modechange');
7450          }
7451        },
7452
7453        /**
7454         * @memberof TextTrack
7455         * @member {TextTrackCueList} cues
7456         *         The text track cue list for this TextTrack.
7457         * @instance
7458         */
7459        cues: {
7460          get: function get$$1() {
7461            if (!this.loaded_) {
7462              return null;
7463            }
7464
7465            return cues;
7466          },
7467          set: function set$$1() {}
7468        },
7469
7470        /**
7471         * @memberof TextTrack
7472         * @member {TextTrackCueList} activeCues
7473         *         The list text track cues that are currently active for this TextTrack.
7474         * @instance
7475         */
7476        activeCues: {
7477          get: function get$$1() {
7478            if (!this.loaded_) {
7479              return null;
7480            }
7481
7482            // nothing to do
7483            if (this.cues.length === 0) {
7484              return activeCues;
7485            }
7486
7487            var ct = this.tech_.currentTime();
7488            var active = [];
7489
7490            for (var i = 0, l = this.cues.length; i < l; i++) {
7491              var cue = this.cues[i];
7492
7493              if (cue.startTime <= ct && cue.endTime >= ct) {
7494                active.push(cue);
7495              } else if (cue.startTime === cue.endTime && cue.startTime <= ct && cue.startTime + 0.5 >= ct) {
7496                active.push(cue);
7497              }
7498            }
7499
7500            changed = false;
7501
7502            if (active.length !== this.activeCues_.length) {
7503              changed = true;
7504            } else {
7505              for (var _i = 0; _i < active.length; _i++) {
7506                if (this.activeCues_.indexOf(active[_i]) === -1) {
7507                  changed = true;
7508                }
7509              }
7510            }
7511
7512            this.activeCues_ = active;
7513            activeCues.setCues_(this.activeCues_);
7514
7515            return activeCues;
7516          },
7517          set: function set$$1() {}
7518        }
7519      });
7520
7521      if (settings.src) {
7522        _this.src = settings.src;
7523        loadTrack(settings.src, _this);
7524      } else {
7525        _this.loaded_ = true;
7526      }
7527      return _this;
7528    }
7529
7530    /**
7531     * Add a cue to the internal list of cues.
7532     *
7533     * @param {TextTrack~Cue} cue
7534     *        The cue to add to our internal list
7535     */
7536
7537
7538    TextTrack.prototype.addCue = function addCue(originalCue) {
7539      var cue = originalCue;
7540
7541      if (window_1.vttjs && !(originalCue instanceof window_1.vttjs.VTTCue)) {
7542        cue = new window_1.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text);
7543
7544        for (var prop in originalCue) {
7545          if (!(prop in cue)) {
7546            cue[prop] = originalCue[prop];
7547          }
7548        }
7549
7550        // make sure that `id` is copied over
7551        cue.id = originalCue.id;
7552        cue.originalCue_ = originalCue;
7553      }
7554
7555      var tracks = this.tech_.textTracks();
7556
7557      for (var i = 0; i < tracks.length; i++) {
7558        if (tracks[i] !== this) {
7559          tracks[i].removeCue(cue);
7560        }
7561      }
7562
7563      this.cues_.push(cue);
7564      this.cues.setCues_(this.cues_);
7565    };
7566
7567    /**
7568     * Remove a cue from our internal list
7569     *
7570     * @param {TextTrack~Cue} removeCue
7571     *        The cue to remove from our internal list
7572     */
7573
7574
7575    TextTrack.prototype.removeCue = function removeCue(_removeCue) {
7576      var i = this.cues_.length;
7577
7578      while (i--) {
7579        var cue = this.cues_[i];
7580
7581        if (cue === _removeCue || cue.originalCue_ && cue.originalCue_ === _removeCue) {
7582          this.cues_.splice(i, 1);
7583          this.cues.setCues_(this.cues_);
7584          break;
7585        }
7586      }
7587    };
7588
7589    return TextTrack;
7590  }(Track);
7591
7592  /**
7593   * cuechange - One or more cues in the track have become active or stopped being active.
7594   */
7595
7596
7597  TextTrack.prototype.allowedEvents_ = {
7598    cuechange: 'cuechange'
7599  };
7600
7601  /**
7602   * A representation of a single `AudioTrack`. If it is part of an {@link AudioTrackList}
7603   * only one `AudioTrack` in the list will be enabled at a time.
7604   *
7605   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack}
7606   * @extends Track
7607   */
7608
7609  var AudioTrack = function (_Track) {
7610    inherits(AudioTrack, _Track);
7611
7612    /**
7613     * Create an instance of this class.
7614     *
7615     * @param {Object} [options={}]
7616     *        Object of option names and values
7617     *
7618     * @param {AudioTrack~Kind} [options.kind='']
7619     *        A valid audio track kind
7620     *
7621     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
7622     *        A unique id for this AudioTrack.
7623     *
7624     * @param {string} [options.label='']
7625     *        The menu label for this track.
7626     *
7627     * @param {string} [options.language='']
7628     *        A valid two character language code.
7629     *
7630     * @param {boolean} [options.enabled]
7631     *        If this track is the one that is currently playing. If this track is part of
7632     *        an {@link AudioTrackList}, only one {@link AudioTrack} will be enabled.
7633     */
7634    function AudioTrack() {
7635      var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
7636      classCallCheck(this, AudioTrack);
7637
7638      var settings = mergeOptions(options, {
7639        kind: AudioTrackKind[options.kind] || ''
7640      });
7641
7642      var _this = possibleConstructorReturn(this, _Track.call(this, settings));
7643
7644      var enabled = false;
7645
7646      /**
7647       * @memberof AudioTrack
7648       * @member {boolean} enabled
7649       *         If this `AudioTrack` is enabled or not. When setting this will
7650       *         fire {@link AudioTrack#enabledchange} if the state of enabled is changed.
7651       * @instance
7652       *
7653       * @fires VideoTrack#selectedchange
7654       */
7655      Object.defineProperty(_this, 'enabled', {
7656        get: function get$$1() {
7657          return enabled;
7658        },
7659        set: function set$$1(newEnabled) {
7660          // an invalid or unchanged value
7661          if (typeof newEnabled !== 'boolean' || newEnabled === enabled) {
7662            return;
7663          }
7664          enabled = newEnabled;
7665
7666          /**
7667           * An event that fires when enabled changes on this track. This allows
7668           * the AudioTrackList that holds this track to act accordingly.
7669           *
7670           * > Note: This is not part of the spec! Native tracks will do
7671           *         this internally without an event.
7672           *
7673           * @event AudioTrack#enabledchange
7674           * @type {EventTarget~Event}
7675           */
7676          this.trigger('enabledchange');
7677        }
7678      });
7679
7680      // if the user sets this track to selected then
7681      // set selected to that true value otherwise
7682      // we keep it false
7683      if (settings.enabled) {
7684        _this.enabled = settings.enabled;
7685      }
7686      _this.loaded_ = true;
7687      return _this;
7688    }
7689
7690    return AudioTrack;
7691  }(Track);
7692
7693  /**
7694   * A representation of a single `VideoTrack`.
7695   *
7696   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack}
7697   * @extends Track
7698   */
7699
7700  var VideoTrack = function (_Track) {
7701    inherits(VideoTrack, _Track);
7702
7703    /**
7704     * Create an instance of this class.
7705     *
7706     * @param {Object} [options={}]
7707     *        Object of option names and values
7708     *
7709     * @param {string} [options.kind='']
7710     *        A valid {@link VideoTrack~Kind}
7711     *
7712     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
7713     *        A unique id for this AudioTrack.
7714     *
7715     * @param {string} [options.label='']
7716     *        The menu label for this track.
7717     *
7718     * @param {string} [options.language='']
7719     *        A valid two character language code.
7720     *
7721     * @param {boolean} [options.selected]
7722     *        If this track is the one that is currently playing.
7723     */
7724    function VideoTrack() {
7725      var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
7726      classCallCheck(this, VideoTrack);
7727
7728      var settings = mergeOptions(options, {
7729        kind: VideoTrackKind[options.kind] || ''
7730      });
7731
7732      var _this = possibleConstructorReturn(this, _Track.call(this, settings));
7733
7734      var selected = false;
7735
7736      /**
7737       * @memberof VideoTrack
7738       * @member {boolean} selected
7739       *         If this `VideoTrack` is selected or not. When setting this will
7740       *         fire {@link VideoTrack#selectedchange} if the state of selected changed.
7741       * @instance
7742       *
7743       * @fires VideoTrack#selectedchange
7744       */
7745      Object.defineProperty(_this, 'selected', {
7746        get: function get$$1() {
7747          return selected;
7748        },
7749        set: function set$$1(newSelected) {
7750          // an invalid or unchanged value
7751          if (typeof newSelected !== 'boolean' || newSelected === selected) {
7752            return;
7753          }
7754          selected = newSelected;
7755
7756          /**
7757           * An event that fires when selected changes on this track. This allows
7758           * the VideoTrackList that holds this track to act accordingly.
7759           *
7760           * > Note: This is not part of the spec! Native tracks will do
7761           *         this internally without an event.
7762           *
7763           * @event VideoTrack#selectedchange
7764           * @type {EventTarget~Event}
7765           */
7766          this.trigger('selectedchange');
7767        }
7768      });
7769
7770      // if the user sets this track to selected then
7771      // set selected to that true value otherwise
7772      // we keep it false
7773      if (settings.selected) {
7774        _this.selected = settings.selected;
7775      }
7776      return _this;
7777    }
7778
7779    return VideoTrack;
7780  }(Track);
7781
7782  /**
7783   * @file html-track-element.js
7784   */
7785
7786  /**
7787   * @memberof HTMLTrackElement
7788   * @typedef {HTMLTrackElement~ReadyState}
7789   * @enum {number}
7790   */
7791  var NONE = 0;
7792  var LOADING = 1;
7793  var LOADED = 2;
7794  var ERROR = 3;
7795
7796  /**
7797   * A single track represented in the DOM.
7798   *
7799   * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement}
7800   * @extends EventTarget
7801   */
7802
7803  var HTMLTrackElement = function (_EventTarget) {
7804    inherits(HTMLTrackElement, _EventTarget);
7805
7806    /**
7807     * Create an instance of this class.
7808     *
7809     * @param {Object} options={}
7810     *        Object of option names and values
7811     *
7812     * @param {Tech} options.tech
7813     *        A reference to the tech that owns this HTMLTrackElement.
7814     *
7815     * @param {TextTrack~Kind} [options.kind='subtitles']
7816     *        A valid text track kind.
7817     *
7818     * @param {TextTrack~Mode} [options.mode='disabled']
7819     *        A valid text track mode.
7820     *
7821     * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
7822     *        A unique id for this TextTrack.
7823     *
7824     * @param {string} [options.label='']
7825     *        The menu label for this track.
7826     *
7827     * @param {string} [options.language='']
7828     *        A valid two character language code.
7829     *
7830     * @param {string} [options.srclang='']
7831     *        A valid two character language code. An alternative, but deprioritized
7832     *        vesion of `options.language`
7833     *
7834     * @param {string} [options.src]
7835     *        A url to TextTrack cues.
7836     *
7837     * @param {boolean} [options.default]
7838     *        If this track should default to on or off.
7839     */
7840    function HTMLTrackElement() {
7841      var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
7842      classCallCheck(this, HTMLTrackElement);
7843
7844      var _this = possibleConstructorReturn(this, _EventTarget.call(this));
7845
7846      var readyState = void 0;
7847
7848      var track = new TextTrack(options);
7849
7850      _this.kind = track.kind;
7851      _this.src = track.src;
7852      _this.srclang = track.language;
7853      _this.label = track.label;
7854      _this.default = track.default;
7855
7856      Object.defineProperties(_this, {
7857
7858        /**
7859         * @memberof HTMLTrackElement
7860         * @member {HTMLTrackElement~ReadyState} readyState
7861         *         The current ready state of the track element.
7862         * @instance
7863         */
7864        readyState: {
7865          get: function get$$1() {
7866            return readyState;
7867          }
7868        },
7869
7870        /**
7871         * @memberof HTMLTrackElement
7872         * @member {TextTrack} track
7873         *         The underlying TextTrack object.
7874         * @instance
7875         *
7876         */
7877        track: {
7878          get: function get$$1() {
7879            return track;
7880          }
7881        }
7882      });
7883
7884      readyState = NONE;
7885
7886      /**
7887       * @listens TextTrack#loadeddata
7888       * @fires HTMLTrackElement#load
7889       */
7890      track.addEventListener('loadeddata', function () {
7891        readyState = LOADED;
7892
7893        _this.trigger({
7894          type: 'load',
7895          target: _this
7896        });
7897      });
7898      return _this;
7899    }
7900
7901    return HTMLTrackElement;
7902  }(EventTarget);
7903
7904  HTMLTrackElement.prototype.allowedEvents_ = {
7905    load: 'load'
7906  };
7907
7908  HTMLTrackElement.NONE = NONE;
7909  HTMLTrackElement.LOADING = LOADING;
7910  HTMLTrackElement.LOADED = LOADED;
7911  HTMLTrackElement.ERROR = ERROR;
7912
7913  /*
7914   * This file contains all track properties that are used in
7915   * player.js, tech.js, html5.js and possibly other techs in the future.
7916   */
7917
7918  var NORMAL = {
7919    audio: {
7920      ListClass: AudioTrackList,
7921      TrackClass: AudioTrack,
7922      capitalName: 'Audio'
7923    },
7924    video: {
7925      ListClass: VideoTrackList,
7926      TrackClass: VideoTrack,
7927      capitalName: 'Video'
7928    },
7929    text: {
7930      ListClass: TextTrackList,
7931      TrackClass: TextTrack,
7932      capitalName: 'Text'
7933    }
7934  };
7935
7936  Object.keys(NORMAL).forEach(function (type) {
7937    NORMAL[type].getterName = type + 'Tracks';
7938    NORMAL[type].privateName = type + 'Tracks_';
7939  });
7940
7941  var REMOTE = {
7942    remoteText: {
7943      ListClass: TextTrackList,
7944      TrackClass: TextTrack,
7945      capitalName: 'RemoteText',
7946      getterName: 'remoteTextTracks',
7947      privateName: 'remoteTextTracks_'
7948    },
7949    remoteTextEl: {
7950      ListClass: HtmlTrackElementList,
7951      TrackClass: HTMLTrackElement,
7952      capitalName: 'RemoteTextTrackEls',
7953      getterName: 'remoteTextTrackEls',
7954      privateName: 'remoteTextTrackEls_'
7955    }
7956  };
7957
7958  var ALL = mergeOptions(NORMAL, REMOTE);
7959
7960  REMOTE.names = Object.keys(REMOTE);
7961  NORMAL.names = Object.keys(NORMAL);
7962  ALL.names = [].concat(REMOTE.names).concat(NORMAL.names);
7963
7964  /**
7965   * Copyright 2013 vtt.js Contributors
7966   *
7967   * Licensed under the Apache License, Version 2.0 (the "License");
7968   * you may not use this file except in compliance with the License.
7969   * You may obtain a copy of the License at
7970   *
7971   *   http://www.apache.org/licenses/LICENSE-2.0
7972   *
7973   * Unless required by applicable law or agreed to in writing, software
7974   * distributed under the License is distributed on an "AS IS" BASIS,
7975   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7976   * See the License for the specific language governing permissions and
7977   * limitations under the License.
7978   */
7979
7980  /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
7981  /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
7982  var _objCreate = Object.create || function () {
7983    function F() {}
7984    return function (o) {
7985      if (arguments.length !== 1) {
7986        throw new Error('Object.create shim only accepts one parameter.');
7987      }
7988      F.prototype = o;
7989      return new F();
7990    };
7991  }();
7992
7993  // Creates a new ParserError object from an errorData object. The errorData
7994  // object should have default code and message properties. The default message
7995  // property can be overriden by passing in a message parameter.
7996  // See ParsingError.Errors below for acceptable errors.
7997  function ParsingError(errorData, message) {
7998    this.name = "ParsingError";
7999    this.code = errorData.code;
8000    this.message = message || errorData.message;
8001  }
8002  ParsingError.prototype = _objCreate(Error.prototype);
8003  ParsingError.prototype.constructor = ParsingError;
8004
8005  // ParsingError metadata for acceptable ParsingErrors.
8006  ParsingError.Errors = {
8007    BadSignature: {
8008      code: 0,
8009      message: "Malformed WebVTT signature."
8010    },
8011    BadTimeStamp: {
8012      code: 1,
8013      message: "Malformed time stamp."
8014    }
8015  };
8016
8017  // Try to parse input as a time stamp.
8018  function parseTimeStamp(input) {
8019
8020    function computeSeconds(h, m, s, f) {
8021      return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000;
8022    }
8023
8024    var m = input.match(/^(\d+):(\d{2})(:\d{2})?\.(\d{3})/);
8025    if (!m) {
8026      return null;
8027    }
8028
8029    if (m[3]) {
8030      // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds]
8031      return computeSeconds(m[1], m[2], m[3].replace(":", ""), m[4]);
8032    } else if (m[1] > 59) {
8033      // Timestamp takes the form of [hours]:[minutes].[milliseconds]
8034      // First position is hours as it's over 59.
8035      return computeSeconds(m[1], m[2], 0, m[4]);
8036    } else {
8037      // Timestamp takes the form of [minutes]:[seconds].[milliseconds]
8038      return computeSeconds(0, m[1], m[2], m[4]);
8039    }
8040  }
8041
8042  // A settings object holds key/value pairs and will ignore anything but the first
8043  // assignment to a specific key.
8044  function Settings() {
8045    this.values = _objCreate(null);
8046  }
8047
8048  Settings.prototype = {
8049    // Only accept the first assignment to any key.
8050    set: function set(k, v) {
8051      if (!this.get(k) && v !== "") {
8052        this.values[k] = v;
8053      }
8054    },
8055    // Return the value for a key, or a default value.
8056    // If 'defaultKey' is passed then 'dflt' is assumed to be an object with
8057    // a number of possible default values as properties where 'defaultKey' is
8058    // the key of the property that will be chosen; otherwise it's assumed to be
8059    // a single value.
8060    get: function get(k, dflt, defaultKey) {
8061      if (defaultKey) {
8062        return this.has(k) ? this.values[k] : dflt[defaultKey];
8063      }
8064      return this.has(k) ? this.values[k] : dflt;
8065    },
8066    // Check whether we have a value for a key.
8067    has: function has(k) {
8068      return k in this.values;
8069    },
8070    // Accept a setting if its one of the given alternatives.
8071    alt: function alt(k, v, a) {
8072      for (var n = 0; n < a.length; ++n) {
8073        if (v === a[n]) {
8074          this.set(k, v);
8075          break;
8076        }
8077      }
8078    },
8079    // Accept a setting if its a valid (signed) integer.
8080    integer: function integer(k, v) {
8081      if (/^-?\d+$/.test(v)) {
8082        // integer
8083        this.set(k, parseInt(v, 10));
8084      }
8085    },
8086    // Accept a setting if its a valid percentage.
8087    percent: function percent(k, v) {
8088      var m;
8089      if (m = v.match(/^([\d]{1,3})(\.[\d]*)?%$/)) {
8090        v = parseFloat(v);
8091        if (v >= 0 && v <= 100) {
8092          this.set(k, v);
8093          return true;
8094        }
8095      }
8096      return false;
vendor: 9,025 bytes, lines 8097-8363
8097    }
8098  };
8099
8100  // Helper function to parse input into groups separated by 'groupDelim', and
8101  // interprete each group as a key/value pair separated by 'keyValueDelim'.
8102  function parseOptions(input, callback, keyValueDelim, groupDelim) {
8103    var groups = groupDelim ? input.split(groupDelim) : [input];
8104    for (var i in groups) {
8105      if (typeof groups[i] !== "string") {
8106        continue;
8107      }
8108      var kv = groups[i].split(keyValueDelim);
8109      if (kv.length !== 2) {
8110        continue;
8111      }
8112      var k = kv[0];
8113      var v = kv[1];
8114      callback(k, v);
8115    }
8116  }
8117
8118  function parseCue(input, cue, regionList) {
8119    // Remember the original input if we need to throw an error.
8120    var oInput = input;
8121    // 4.1 WebVTT timestamp
8122    function consumeTimeStamp() {
8123      var ts = parseTimeStamp(input);
8124      if (ts === null) {
8125        throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed timestamp: " + oInput);
8126      }
8127      // Remove time stamp from input.
8128      input = input.replace(/^[^\sa-zA-Z-]+/, "");
8129      return ts;
8130    }
8131
8132    // 4.4.2 WebVTT cue settings
8133    function consumeCueSettings(input, cue) {
8134      var settings = new Settings();
8135
8136      parseOptions(input, function (k, v) {
8137        switch (k) {
8138          case "region":
8139            // Find the last region we parsed with the same region id.
8140            for (var i = regionList.length - 1; i >= 0; i--) {
8141              if (regionList[i].id === v) {
8142                settings.set(k, regionList[i].region);
8143                break;
8144              }
8145            }
8146            break;
8147          case "vertical":
8148            settings.alt(k, v, ["rl", "lr"]);
8149            break;
8150          case "line":
8151            var vals = v.split(","),
8152                vals0 = vals[0];
8153            settings.integer(k, vals0);
8154            settings.percent(k, vals0) ? settings.set("snapToLines", false) : null;
8155            settings.alt(k, vals0, ["auto"]);
8156            if (vals.length === 2) {
8157              settings.alt("lineAlign", vals[1], ["start", "middle", "end"]);
8158            }
8159            break;
8160          case "position":
8161            vals = v.split(",");
8162            settings.percent(k, vals[0]);
8163            if (vals.length === 2) {
8164              settings.alt("positionAlign", vals[1], ["start", "middle", "end"]);
8165            }
8166            break;
8167          case "size":
8168            settings.percent(k, v);
8169            break;
8170          case "align":
8171            settings.alt(k, v, ["start", "middle", "end", "left", "right"]);
8172            break;
8173        }
8174      }, /:/, /\s/);
8175
8176      // Apply default values for any missing fields.
8177      cue.region = settings.get("region", null);
8178      cue.vertical = settings.get("vertical", "");
8179      cue.line = settings.get("line", "auto");
8180      cue.lineAlign = settings.get("lineAlign", "start");
8181      cue.snapToLines = settings.get("snapToLines", true);
8182      cue.size = settings.get("size", 100);
8183      cue.align = settings.get("align", "middle");
8184      cue.position = settings.get("position", {
8185        start: 0,
8186        left: 0,
8187        middle: 50,
8188        end: 100,
8189        right: 100
8190      }, cue.align);
8191      cue.positionAlign = settings.get("positionAlign", {
8192        start: "start",
8193        left: "start",
8194        middle: "middle",
8195        end: "end",
8196        right: "end"
8197      }, cue.align);
8198    }
8199
8200    function skipWhitespace() {
8201      input = input.replace(/^\s+/, "");
8202    }
8203
8204    // 4.1 WebVTT cue timings.
8205    skipWhitespace();
8206    cue.startTime = consumeTimeStamp(); // (1) collect cue start time
8207    skipWhitespace();
8208    if (input.substr(0, 3) !== "-->") {
8209      // (3) next characters must match "-->"
8210      throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed time stamp (time stamps must be separated by '-->'): " + oInput);
8211    }
8212    input = input.substr(3);
8213    skipWhitespace();
8214    cue.endTime = consumeTimeStamp(); // (5) collect cue end time
8215
8216    // 4.1 WebVTT cue settings list.
8217    skipWhitespace();
8218    consumeCueSettings(input, cue);
8219  }
8220
8221  var ESCAPE = {
8222    "&amp;": "&",
8223    "&lt;": "<",
8224    "&gt;": ">",
8225    "&lrm;": "\u200E",
8226    "&rlm;": "\u200F",
8227    "&nbsp;": "\xA0"
8228  };
8229
8230  var TAG_NAME = {
8231    c: "span",
8232    i: "i",
8233    b: "b",
8234    u: "u",
8235    ruby: "ruby",
8236    rt: "rt",
8237    v: "span",
8238    lang: "span"
8239  };
8240
8241  var TAG_ANNOTATION = {
8242    v: "title",
8243    lang: "lang"
8244  };
8245
8246  var NEEDS_PARENT = {
8247    rt: "ruby"
8248  };
8249
8250  // Parse content into a document fragment.
8251  function parseContent(window, input) {
8252    function nextToken() {
8253      // Check for end-of-string.
8254      if (!input) {
8255        return null;
8256      }
8257
8258      // Consume 'n' characters from the input.
8259      function consume(result) {
8260        input = input.substr(result.length);
8261        return result;
8262      }
8263
8264      var m = input.match(/^([^<]*)(<[^>]*>?)?/);
8265      // If there is some text before the next tag, return it, otherwise return
8266      // the tag.
8267      return consume(m[1] ? m[1] : m[2]);
8268    }
8269
8270    // Unescape a string 's'.
8271    function unescape1(e) {
8272      return ESCAPE[e];
8273    }
8274    function unescape(s) {
8275      while (m = s.match(/&(amp|lt|gt|lrm|rlm|nbsp);/)) {
8276        s = s.replace(m[0], unescape1);
8277      }
8278      return s;
8279    }
8280
8281    function shouldAdd(current, element) {
8282      return !NEEDS_PARENT[element.localName] || NEEDS_PARENT[element.localName] === current.localName;
8283    }
8284
8285    // Create an element for this tag.
8286    function createElement(type, annotation) {
8287      var tagName = TAG_NAME[type];
8288      if (!tagName) {
8289        return null;
8290      }
8291      var element = window.document.createElement(tagName);
8292      element.localName = tagName;
8293      var name = TAG_ANNOTATION[type];
8294      if (name && annotation) {
8295        element[name] = annotation.trim();
8296      }
8297      return element;
8298    }
8299
8300    var rootDiv = window.document.createElement("div"),
8301        current = rootDiv,
8302        t,
8303        tagStack = [];
8304
8305    while ((t = nextToken()) !== null) {
8306      if (t[0] === '<') {
8307        if (t[1] === "/") {
8308          // If the closing tag matches, move back up to the parent node.
8309          if (tagStack.length && tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) {
8310            tagStack.pop();
8311            current = current.parentNode;
8312          }
8313          // Otherwise just ignore the end tag.
8314          continue;
8315        }
8316        var ts = parseTimeStamp(t.substr(1, t.length - 2));
8317        var node;
8318        if (ts) {
8319          // Timestamps are lead nodes as well.
8320          node = window.document.createProcessingInstruction("timestamp", ts);
8321          current.appendChild(node);
8322          continue;
8323        }
8324        var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/);
8325        // If we can't parse the tag, skip to the next tag.
8326        if (!m) {
8327          continue;
8328        }
8329        // Try to construct an element, and ignore the tag if we couldn't.
8330        node = createElement(m[1], m[3]);
8331        if (!node) {
8332          continue;
8333        }
8334        // Determine if the tag should be added based on the context of where it
8335        // is placed in the cuetext.
8336        if (!shouldAdd(current, node)) {
8337          continue;
8338        }
8339        // Set the class list (as a list of classes, separated by space).
8340        if (m[2]) {
8341          node.className = m[2].substr(1).replace('.', ' ');
8342        }
8343        // Append the node to the current node, and enter the scope of the new
8344        // node.
8345        tagStack.push(m[1]);
8346        current.appendChild(node);
8347        current = node;
8348        continue;
8349      }
8350
8351      // Text nodes are leaf nodes.
8352      current.appendChild(window.document.createTextNode(unescape(t)));
8353    }
8354
8355    return rootDiv;
8356  }
8357
8358  // This is a list of all the Unicode characters that have a strong
8359  // right-to-left category. What this means is that these characters are
8360  // written right-to-left for sure. It was generated by pulling all the strong
8361  // right-to-left characters out of the Unicode data table. That table can
8362  // found at: http://www.unicode.org/Public/UNIDATA/UnicodeData.txt
8363  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 8363-8878
83630x1093f], [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]];
8364
8365  function isStrongRTLChar(charCode) {
8366    for (var i = 0; i < strongRTLRanges.length; i++) {
8367      var currentRange = strongRTLRanges[i];
8368      if (charCode >= currentRange[0] && charCode <= currentRange[1]) {
8369        return true;
8370      }
8371    }
8372
8373    return false;
8374  }
8375
8376  function determineBidi(cueDiv) {
8377    var nodeStack = [],
8378        text = "",
8379        charCode;
8380
8381    if (!cueDiv || !cueDiv.childNodes) {
8382      return "ltr";
8383    }
8384
8385    function pushNodes(nodeStack, node) {
8386      for (var i = node.childNodes.length - 1; i >= 0; i--) {
8387        nodeStack.push(node.childNodes[i]);
8388      }
8389    }
8390
8391    function nextTextNode(nodeStack) {
8392      if (!nodeStack || !nodeStack.length) {
8393        return null;
8394      }
8395
8396      var node = nodeStack.pop(),
8397          text = node.textContent || node.innerText;
8398      if (text) {
8399        // TODO: This should match all unicode type B characters (paragraph
8400        // separator characters). See issue #115.
8401        var m = text.match(/^.*(\n|\r)/);
8402        if (m) {
8403          nodeStack.length = 0;
8404          return m[0];
8405        }
8406        return text;
8407      }
8408      if (node.tagName === "ruby") {
8409        return nextTextNode(nodeStack);
8410      }
8411      if (node.childNodes) {
8412        pushNodes(nodeStack, node);
8413        return nextTextNode(nodeStack);
8414      }
8415    }
8416
8417    pushNodes(nodeStack, cueDiv);
8418    while (text = nextTextNode(nodeStack)) {
8419      for (var i = 0; i < text.length; i++) {
8420        charCode = text.charCodeAt(i);
8421        if (isStrongRTLChar(charCode)) {
8422          return "rtl";
8423        }
8424      }
8425    }
8426    return "ltr";
8427  }
8428
8429  function computeLinePos(cue) {
8430    if (typeof cue.line === "number" && (cue.snapToLines || cue.line >= 0 && cue.line <= 100)) {
8431      return cue.line;
8432    }
8433    if (!cue.track || !cue.track.textTrackList || !cue.track.textTrackList.mediaElement) {
8434      return -1;
8435    }
8436    var track = cue.track,
8437        trackList = track.textTrackList,
8438        count = 0;
8439    for (var i = 0; i < trackList.length && trackList[i] !== track; i++) {
8440      if (trackList[i].mode === "showing") {
8441        count++;
8442      }
8443    }
8444    return ++count * -1;
8445  }
8446
8447  function StyleBox() {}
8448
8449  // Apply styles to a div. If there is no div passed then it defaults to the
8450  // div on 'this'.
8451  StyleBox.prototype.applyStyles = function (styles, div) {
8452    div = div || this.div;
8453    for (var prop in styles) {
8454      if (styles.hasOwnProperty(prop)) {
8455        div.style[prop] = styles[prop];
8456      }
8457    }
8458  };
8459
8460  StyleBox.prototype.formatStyle = function (val, unit) {
8461    return val === 0 ? 0 : val + unit;
8462  };
8463
8464  // Constructs the computed display state of the cue (a div). Places the div
8465  // into the overlay which should be a block level element (usually a div).
8466  function CueStyleBox(window, cue, styleOptions) {
8467    StyleBox.call(this);
8468    this.cue = cue;
8469
8470    // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will
8471    // have inline positioning and will function as the cue background box.
8472    this.cueDiv = parseContent(window, cue.text);
8473    var styles = {
8474      color: "rgba(255, 255, 255, 1)",
8475      backgroundColor: "rgba(0, 0, 0, 0.8)",
8476      position: "relative",
8477      left: 0,
8478      right: 0,
8479      top: 0,
8480      bottom: 0,
8481      display: "inline",
8482      writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl",
8483      unicodeBidi: "plaintext"
8484    };
8485
8486    this.applyStyles(styles, this.cueDiv);
8487
8488    // Create an absolutely positioned div that will be used to position the cue
8489    // div. Note, all WebVTT cue-setting alignments are equivalent to the CSS
8490    // mirrors of them except "middle" which is "center" in CSS.
8491    this.div = window.document.createElement("div");
8492    styles = {
8493      direction: determineBidi(this.cueDiv),
8494      writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl",
8495      unicodeBidi: "plaintext",
8496      textAlign: cue.align === "middle" ? "center" : cue.align,
8497      font: styleOptions.font,
8498      whiteSpace: "pre-line",
8499      position: "absolute"
8500    };
8501
8502    this.applyStyles(styles);
8503    this.div.appendChild(this.cueDiv);
8504
8505    // Calculate the distance from the reference edge of the viewport to the text
8506    // position of the cue box. The reference edge will be resolved later when
8507    // the box orientation styles are applied.
8508    var textPos = 0;
8509    switch (cue.positionAlign) {
8510      case "start":
8511        textPos = cue.position;
8512        break;
8513      case "middle":
8514        textPos = cue.position - cue.size / 2;
8515        break;
8516      case "end":
8517        textPos = cue.position - cue.size;
8518        break;
8519    }
8520
8521    // Horizontal box orientation; textPos is the distance from the left edge of the
8522    // area to the left edge of the box and cue.size is the distance extending to
8523    // the right from there.
8524    if (cue.vertical === "") {
8525      this.applyStyles({
8526        left: this.formatStyle(textPos, "%"),
8527        width: this.formatStyle(cue.size, "%")
8528      });
8529      // Vertical box orientation; textPos is the distance from the top edge of the
8530      // area to the top edge of the box and cue.size is the height extending
8531      // downwards from there.
8532    } else {
8533      this.applyStyles({
8534        top: this.formatStyle(textPos, "%"),
8535        height: this.formatStyle(cue.size, "%")
8536      });
8537    }
8538
8539    this.move = function (box) {
8540      this.applyStyles({
8541        top: this.formatStyle(box.top, "px"),
8542        bottom: this.formatStyle(box.bottom, "px"),
8543        left: this.formatStyle(box.left, "px"),
8544        right: this.formatStyle(box.right, "px"),
8545        height: this.formatStyle(box.height, "px"),
8546        width: this.formatStyle(box.width, "px")
8547      });
8548    };
8549  }
8550  CueStyleBox.prototype = _objCreate(StyleBox.prototype);
8551  CueStyleBox.prototype.constructor = CueStyleBox;
8552
8553  // Represents the co-ordinates of an Element in a way that we can easily
8554  // compute things with such as if it overlaps or intersects with another Element.
8555  // Can initialize it with either a StyleBox or another BoxPosition.
8556  function BoxPosition(obj) {
8557    // Either a BoxPosition was passed in and we need to copy it, or a StyleBox
8558    // was passed in and we need to copy the results of 'getBoundingClientRect'
8559    // as the object returned is readonly. All co-ordinate values are in reference
8560    // to the viewport origin (top left).
8561    var lh, height, width, top;
8562    if (obj.div) {
8563      height = obj.div.offsetHeight;
8564      width = obj.div.offsetWidth;
8565      top = obj.div.offsetTop;
8566
8567      var rects = (rects = obj.div.childNodes) && (rects = rects[0]) && rects.getClientRects && rects.getClientRects();
8568      obj = obj.div.getBoundingClientRect();
8569      // In certain cases the outter div will be slightly larger then the sum of
8570      // the inner div's lines. This could be due to bold text, etc, on some platforms.
8571      // In this case we should get the average line height and use that. This will
8572      // result in the desired behaviour.
8573      lh = rects ? Math.max(rects[0] && rects[0].height || 0, obj.height / rects.length) : 0;
8574    }
8575    this.left = obj.left;
8576    this.right = obj.right;
8577    this.top = obj.top || top;
8578    this.height = obj.height || height;
8579    this.bottom = obj.bottom || top + (obj.height || height);
8580    this.width = obj.width || width;
8581    this.lineHeight = lh !== undefined ? lh : obj.lineHeight;
8582  }
8583
8584  // Move the box along a particular axis. Optionally pass in an amount to move
8585  // the box. If no amount is passed then the default is the line height of the
8586  // box.
8587  BoxPosition.prototype.move = function (axis, toMove) {
8588    toMove = toMove !== undefined ? toMove : this.lineHeight;
8589    switch (axis) {
8590      case "+x":
8591        this.left += toMove;
8592        this.right += toMove;
8593        break;
8594      case "-x":
8595        this.left -= toMove;
8596        this.right -= toMove;
8597        break;
8598      case "+y":
8599        this.top += toMove;
8600        this.bottom += toMove;
8601        break;
8602      case "-y":
8603        this.top -= toMove;
8604        this.bottom -= toMove;
8605        break;
8606    }
8607  };
8608
8609  // Check if this box overlaps another box, b2.
8610  BoxPosition.prototype.overlaps = function (b2) {
8611    return this.left < b2.right && this.right > b2.left && this.top < b2.bottom && this.bottom > b2.top;
8612  };
8613
8614  // Check if this box overlaps any other boxes in boxes.
8615  BoxPosition.prototype.overlapsAny = function (boxes) {
8616    for (var i = 0; i < boxes.length; i++) {
8617      if (this.overlaps(boxes[i])) {
8618        return true;
8619      }
8620    }
8621    return false;
8622  };
8623
8624  // Check if this box is within another box.
8625  BoxPosition.prototype.within = function (container) {
8626    return this.top >= container.top && this.bottom <= container.bottom && this.left >= container.left && this.right <= container.right;
8627  };
8628
8629  // Check if this box is entirely within the container or it is overlapping
8630  // on the edge opposite of the axis direction passed. For example, if "+x" is
8631  // passed and the box is overlapping on the left edge of the container, then
8632  // return true.
8633  BoxPosition.prototype.overlapsOppositeAxis = function (container, axis) {
8634    switch (axis) {
8635      case "+x":
8636        return this.left < container.left;
8637      case "-x":
8638        return this.right > container.right;
8639      case "+y":
8640        return this.top < container.top;
8641      case "-y":
8642        return this.bottom > container.bottom;
8643    }
8644  };
8645
8646  // Find the percentage of the area that this box is overlapping with another
8647  // box.
8648  BoxPosition.prototype.intersectPercentage = function (b2) {
8649    var x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)),
8650        y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)),
8651        intersectArea = x * y;
8652    return intersectArea / (this.height * this.width);
8653  };
8654
8655  // Convert the positions from this box to CSS compatible positions using
8656  // the reference container's positions. This has to be done because this
8657  // box's positions are in reference to the viewport origin, whereas, CSS
8658  // values are in referecne to their respective edges.
8659  BoxPosition.prototype.toCSSCompatValues = function (reference) {
8660    return {
8661      top: this.top - reference.top,
8662      bottom: reference.bottom - this.bottom,
8663      left: this.left - reference.left,
8664      right: reference.right - this.right,
8665      height: this.height,
8666      width: this.width
8667    };
8668  };
8669
8670  // Get an object that represents the box's position without anything extra.
8671  // Can pass a StyleBox, HTMLElement, or another BoxPositon.
8672  BoxPosition.getSimpleBoxPosition = function (obj) {
8673    var height = obj.div ? obj.div.offsetHeight : obj.tagName ? obj.offsetHeight : 0;
8674    var width = obj.div ? obj.div.offsetWidth : obj.tagName ? obj.offsetWidth : 0;
8675    var top = obj.div ? obj.div.offsetTop : obj.tagName ? obj.offsetTop : 0;
8676
8677    obj = obj.div ? obj.div.getBoundingClientRect() : obj.tagName ? obj.getBoundingClientRect() : obj;
8678    var ret = {
8679      left: obj.left,
8680      right: obj.right,
8681      top: obj.top || top,
8682      height: obj.height || height,
8683      bottom: obj.bottom || top + (obj.height || height),
8684      width: obj.width || width
8685    };
8686    return ret;
8687  };
8688
8689  // Move a StyleBox to its specified, or next best, position. The containerBox
8690  // is the box that contains the StyleBox, such as a div. boxPositions are
8691  // a list of other boxes that the styleBox can't overlap with.
8692  function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) {
8693
8694    // Find the best position for a cue box, b, on the video. The axis parameter
8695    // is a list of axis, the order of which, it will move the box along. For example:
8696    // Passing ["+x", "-x"] will move the box first along the x axis in the positive
8697    // direction. If it doesn't find a good position for it there it will then move
8698    // it along the x axis in the negative direction.
8699    function findBestPosition(b, axis) {
8700      var bestPosition,
8701          specifiedPosition = new BoxPosition(b),
8702          percentage = 1; // Highest possible so the first thing we get is better.
8703
8704      for (var i = 0; i < axis.length; i++) {
8705        while (b.overlapsOppositeAxis(containerBox, axis[i]) || b.within(containerBox) && b.overlapsAny(boxPositions)) {
8706          b.move(axis[i]);
8707        }
8708        // We found a spot where we aren't overlapping anything. This is our
8709        // best position.
8710        if (b.within(containerBox)) {
8711          return b;
8712        }
8713        var p = b.intersectPercentage(containerBox);
8714        // If we're outside the container box less then we were on our last try
8715        // then remember this position as the best position.
8716        if (percentage > p) {
8717          bestPosition = new BoxPosition(b);
8718          percentage = p;
8719        }
8720        // Reset the box position to the specified position.
8721        b = new BoxPosition(specifiedPosition);
8722      }
8723      return bestPosition || specifiedPosition;
8724    }
8725
8726    var boxPosition = new BoxPosition(styleBox),
8727        cue = styleBox.cue,
8728        linePos = computeLinePos(cue),
8729        axis = [];
8730
8731    // If we have a line number to align the cue to.
8732    if (cue.snapToLines) {
8733      var size;
8734      switch (cue.vertical) {
8735        case "":
8736          axis = ["+y", "-y"];
8737          size = "height";
8738          break;
8739        case "rl":
8740          axis = ["+x", "-x"];
8741          size = "width";
8742          break;
8743        case "lr":
8744          axis = ["-x", "+x"];
8745          size = "width";
8746          break;
8747      }
8748
8749      var step = boxPosition.lineHeight,
8750          position = step * Math.round(linePos),
8751          maxPosition = containerBox[size] + step,
8752          initialAxis = axis[0];
8753
8754      // If the specified intial position is greater then the max position then
8755      // clamp the box to the amount of steps it would take for the box to
8756      // reach the max position.
8757      if (Math.abs(position) > maxPosition) {
8758        position = position < 0 ? -1 : 1;
8759        position *= Math.ceil(maxPosition / step) * step;
8760      }
8761
8762      // If computed line position returns negative then line numbers are
8763      // relative to the bottom of the video instead of the top. Therefore, we
8764      // need to increase our initial position by the length or width of the
8765      // video, depending on the writing direction, and reverse our axis directions.
8766      if (linePos < 0) {
8767        position += cue.vertical === "" ? containerBox.height : containerBox.width;
8768        axis = axis.reverse();
8769      }
8770
8771      // Move the box to the specified position. This may not be its best
8772      // position.
8773      boxPosition.move(initialAxis, position);
8774    } else {
8775      // If we have a percentage line value for the cue.
8776      var calculatedPercentage = boxPosition.lineHeight / containerBox.height * 100;
8777
8778      switch (cue.lineAlign) {
8779        case "middle":
8780          linePos -= calculatedPercentage / 2;
8781          break;
8782        case "end":
8783          linePos -= calculatedPercentage;
8784          break;
8785      }
8786
8787      // Apply initial line position to the cue box.
8788      switch (cue.vertical) {
8789        case "":
8790          styleBox.applyStyles({
8791            top: styleBox.formatStyle(linePos, "%")
8792          });
8793          break;
8794        case "rl":
8795          styleBox.applyStyles({
8796            left: styleBox.formatStyle(linePos, "%")
8797          });
8798          break;
8799        case "lr":
8800          styleBox.applyStyles({
8801            right: styleBox.formatStyle(linePos, "%")
8802          });
8803          break;
8804      }
8805
8806      axis = ["+y", "-x", "+x", "-y"];
8807
8808      // Get the box position again after we've applied the specified positioning
8809      // to it.
8810      boxPosition = new BoxPosition(styleBox);
8811    }
8812
8813    var bestPosition = findBestPosition(boxPosition, axis);
8814    styleBox.move(bestPosition.toCSSCompatValues(containerBox));
8815  }
8816
8817  function WebVTT$1() {}
8818  // Nothing
8819
8820
8821  // Helper to allow strings to be decoded instead of the default binary utf8 data.
8822  WebVTT$1.StringDecoder = function () {
8823    return {
8824      decode: function decode(data) {
8825        if (!data) {
8826          return "";
8827        }
8828        if (typeof data !== "string") {
8829          throw new Error("Error - expected string data.");
8830        }
8831        return decodeURIComponent(encodeURIComponent(data));
8832      }
8833    };
8834  };
8835
8836  WebVTT$1.convertCueToDOMTree = function (window, cuetext) {
8837    if (!window || !cuetext) {
8838      return null;
8839    }
8840    return parseContent(window, cuetext);
8841  };
8842
8843  var FONT_SIZE_PERCENT = 0.05;
8844  var FONT_STYLE = "sans-serif";
8845  var CUE_BACKGROUND_PADDING = "1.5%";
8846
8847  // Runs the processing model over the cues and regions passed to it.
8848  // @param overlay A block level element (usually a div) that the computed cues
8849  //                and regions will be placed into.
8850  WebVTT$1.processCues = function (window, cues, overlay) {
8851    if (!window || !cues || !overlay) {
8852      return null;
8853    }
8854
8855    // Remove all previous children.
8856    while (overlay.firstChild) {
8857      overlay.removeChild(overlay.firstChild);
8858    }
8859
8860    var paddedOverlay = window.document.createElement("div");
8861    paddedOverlay.style.position = "absolute";
8862    paddedOverlay.style.left = "0";
8863    paddedOverlay.style.right = "0";
8864    paddedOverlay.style.top = "0";
8865    paddedOverlay.style.bottom = "0";
8866    paddedOverlay.style.margin = CUE_BACKGROUND_PADDING;
8867    overlay.appendChild(paddedOverlay);
8868
8869    // Determine if we need to compute the display states of the cues. This could
8870    // be the case if a cue's state has been changed since the last computation or
8871    // if it has not been computed yet.
8872    function shouldCompute(cues) {
8873      for (var i = 0; i < cues.length; i++) {
8874        if (cues[i].hasBeenReset || !cues[i].displayState) {
8875          return true;
8876        }
8877      }
8878      return false;
vendor: 16,058 bytes, lines 8879-9412
8879    }
8880
8881    // We don't need to recompute the cues' display states. Just reuse them.
8882    if (!shouldCompute(cues)) {
8883      for (var i = 0; i < cues.length; i++) {
8884        paddedOverlay.appendChild(cues[i].displayState);
8885      }
8886      return;
8887    }
8888
8889    var boxPositions = [],
8890        containerBox = BoxPosition.getSimpleBoxPosition(paddedOverlay),
8891        fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100;
8892    var styleOptions = {
8893      font: fontSize + "px " + FONT_STYLE
8894    };
8895
8896    (function () {
8897      var styleBox, cue;
8898
8899      for (var i = 0; i < cues.length; i++) {
8900        cue = cues[i];
8901
8902        // Compute the intial position and styles of the cue div.
8903        styleBox = new CueStyleBox(window, cue, styleOptions);
8904        paddedOverlay.appendChild(styleBox.div);
8905
8906        // Move the cue div to it's correct line position.
8907        moveBoxToLinePosition(window, styleBox, containerBox, boxPositions);
8908
8909        // Remember the computed div so that we don't have to recompute it later
8910        // if we don't have too.
8911        cue.displayState = styleBox.div;
8912
8913        boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox));
8914      }
8915    })();
8916  };
8917
8918  WebVTT$1.Parser = function (window, vttjs, decoder) {
8919    if (!decoder) {
8920      decoder = vttjs;
8921      vttjs = {};
8922    }
8923    if (!vttjs) {
8924      vttjs = {};
8925    }
8926
8927    this.window = window;
8928    this.vttjs = vttjs;
8929    this.state = "INITIAL";
8930    this.buffer = "";
8931    this.decoder = decoder || new TextDecoder("utf8");
8932    this.regionList = [];
8933  };
8934
8935  WebVTT$1.Parser.prototype = {
8936    // If the error is a ParsingError then report it to the consumer if
8937    // possible. If it's not a ParsingError then throw it like normal.
8938    reportOrThrowError: function reportOrThrowError(e) {
8939      if (e instanceof ParsingError) {
8940        this.onparsingerror && this.onparsingerror(e);
8941      } else {
8942        throw e;
8943      }
8944    },
8945    parse: function parse(data) {
8946      var self = this;
8947
8948      // If there is no data then we won't decode it, but will just try to parse
8949      // whatever is in buffer already. This may occur in circumstances, for
8950      // example when flush() is called.
8951      if (data) {
8952        // Try to decode the data that we received.
8953        self.buffer += self.decoder.decode(data, { stream: true });
8954      }
8955
8956      function collectNextLine() {
8957        var buffer = self.buffer;
8958        var pos = 0;
8959        while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') {
8960          ++pos;
8961        }
8962        var line = buffer.substr(0, pos);
8963        // Advance the buffer early in case we fail below.
8964        if (buffer[pos] === '\r') {
8965          ++pos;
8966        }
8967        if (buffer[pos] === '\n') {
8968          ++pos;
8969        }
8970        self.buffer = buffer.substr(pos);
8971        return line;
8972      }
8973
8974      // 3.4 WebVTT region and WebVTT region settings syntax
8975      function parseRegion(input) {
8976        var settings = new Settings();
8977
8978        parseOptions(input, function (k, v) {
8979          switch (k) {
8980            case "id":
8981              settings.set(k, v);
8982              break;
8983            case "width":
8984              settings.percent(k, v);
8985              break;
8986            case "lines":
8987              settings.integer(k, v);
8988              break;
8989            case "regionanchor":
8990            case "viewportanchor":
8991              var xy = v.split(',');
8992              if (xy.length !== 2) {
8993                break;
8994              }
8995              // We have to make sure both x and y parse, so use a temporary
8996              // settings object here.
8997              var anchor = new Settings();
8998              anchor.percent("x", xy[0]);
8999              anchor.percent("y", xy[1]);
9000              if (!anchor.has("x") || !anchor.has("y")) {
9001                break;
9002              }
9003              settings.set(k + "X", anchor.get("x"));
9004              settings.set(k + "Y", anchor.get("y"));
9005              break;
9006            case "scroll":
9007              settings.alt(k, v, ["up"]);
9008              break;
9009          }
9010        }, /=/, /\s/);
9011
9012        // Create the region, using default values for any values that were not
9013        // specified.
9014        if (settings.has("id")) {
9015          var region = new (self.vttjs.VTTRegion || self.window.VTTRegion)();
9016          region.width = settings.get("width", 100);
9017          region.lines = settings.get("lines", 3);
9018          region.regionAnchorX = settings.get("regionanchorX", 0);
9019          region.regionAnchorY = settings.get("regionanchorY", 100);
9020          region.viewportAnchorX = settings.get("viewportanchorX", 0);
9021          region.viewportAnchorY = settings.get("viewportanchorY", 100);
9022          region.scroll = settings.get("scroll", "");
9023          // Register the region.
9024          self.onregion && self.onregion(region);
9025          // Remember the VTTRegion for later in case we parse any VTTCues that
9026          // reference it.
9027          self.regionList.push({
9028            id: settings.get("id"),
9029            region: region
9030          });
9031        }
9032      }
9033
9034      // draft-pantos-http-live-streaming-20
9035      // https://tools.ietf.org/html/draft-pantos-http-live-streaming-20#section-3.5
9036      // 3.5 WebVTT
9037      function parseTimestampMap(input) {
9038        var settings = new Settings();
9039
9040        parseOptions(input, function (k, v) {
9041          switch (k) {
9042            case "MPEGT":
9043              settings.integer(k + 'S', v);
9044              break;
9045            case "LOCA":
9046              settings.set(k + 'L', parseTimeStamp(v));
9047              break;
9048          }
9049        }, /[^\d]:/, /,/);
9050
9051        self.ontimestampmap && self.ontimestampmap({
9052          "MPEGTS": settings.get("MPEGTS"),
9053          "LOCAL": settings.get("LOCAL")
9054        });
9055      }
9056
9057      // 3.2 WebVTT metadata header syntax
9058      function parseHeader(input) {
9059        if (input.match(/X-TIMESTAMP-MAP/)) {
9060          // This line contains HLS X-TIMESTAMP-MAP metadata
9061          parseOptions(input, function (k, v) {
9062            switch (k) {
9063              case "X-TIMESTAMP-MAP":
9064                parseTimestampMap(v);
9065                break;
9066            }
9067          }, /=/);
9068        } else {
9069          parseOptions(input, function (k, v) {
9070            switch (k) {
9071              case "Region":
9072                // 3.3 WebVTT region metadata header syntax
9073                parseRegion(v);
9074                break;
9075            }
9076          }, /:/);
9077        }
9078      }
9079
9080      // 5.1 WebVTT file parsing.
9081      try {
9082        var line;
9083        if (self.state === "INITIAL") {
9084          // We can't start parsing until we have the first line.
9085          if (!/\r\n|\n/.test(self.buffer)) {
9086            return this;
9087          }
9088
9089          line = collectNextLine();
9090
9091          var m = line.match(/^WEBVTT([ \t].*)?$/);
9092          if (!m || !m[0]) {
9093            throw new ParsingError(ParsingError.Errors.BadSignature);
9094          }
9095
9096          self.state = "HEADER";
9097        }
9098
9099        var alreadyCollectedLine = false;
9100        while (self.buffer) {
9101          // We can't parse a line until we have the full line.
9102          if (!/\r\n|\n/.test(self.buffer)) {
9103            return this;
9104          }
9105
9106          if (!alreadyCollectedLine) {
9107            line = collectNextLine();
9108          } else {
9109            alreadyCollectedLine = false;
9110          }
9111
9112          switch (self.state) {
9113            case "HEADER":
9114              // 13-18 - Allow a header (metadata) under the WEBVTT line.
9115              if (/:/.test(line)) {
9116                parseHeader(line);
9117              } else if (!line) {
9118                // An empty line terminates the header and starts the body (cues).
9119                self.state = "ID";
9120              }
9121              continue;
9122            case "NOTE":
9123              // Ignore NOTE blocks.
9124              if (!line) {
9125                self.state = "ID";
9126              }
9127              continue;
9128            case "ID":
9129              // Check for the start of NOTE blocks.
9130              if (/^NOTE($|[ \t])/.test(line)) {
9131                self.state = "NOTE";
9132                break;
9133              }
9134              // 19-29 - Allow any number of line terminators, then initialize new cue values.
9135              if (!line) {
9136                continue;
9137              }
9138              self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, "");
9139              self.state = "CUE";
9140              // 30-39 - Check if self line contains an optional identifier or timing data.
9141              if (line.indexOf("-->") === -1) {
9142                self.cue.id = line;
9143                continue;
9144              }
9145            // Process line as start of a cue.
9146            /*falls through*/
9147            case "CUE":
9148              // 40 - Collect cue timings and settings.
9149              try {
9150                parseCue(line, self.cue, self.regionList);
9151              } catch (e) {
9152                self.reportOrThrowError(e);
9153                // In case of an error ignore rest of the cue.
9154                self.cue = null;
9155                self.state = "BADCUE";
9156                continue;
9157              }
9158              self.state = "CUETEXT";
9159              continue;
9160            case "CUETEXT":
9161              var hasSubstring = line.indexOf("-->") !== -1;
9162              // 34 - If we have an empty line then report the cue.
9163              // 35 - If we have the special substring '-->' then report the cue,
9164              // but do not collect the line as we need to process the current
9165              // one as a new cue.
9166              if (!line || hasSubstring && (alreadyCollectedLine = true)) {
9167                // We are done parsing self cue.
9168                self.oncue && self.oncue(self.cue);
9169                self.cue = null;
9170                self.state = "ID";
9171                continue;
9172              }
9173              if (self.cue.text) {
9174                self.cue.text += "\n";
9175              }
9176              self.cue.text += line;
9177              continue;
9178            case "BADCUE":
9179              // BADCUE
9180              // 54-62 - Collect and discard the remaining cue.
9181              if (!line) {
9182                self.state = "ID";
9183              }
9184              continue;
9185          }
9186        }
9187      } catch (e) {
9188        self.reportOrThrowError(e);
9189
9190        // If we are currently parsing a cue, report what we have.
9191        if (self.state === "CUETEXT" && self.cue && self.oncue) {
9192          self.oncue(self.cue);
9193        }
9194        self.cue = null;
9195        // Enter BADWEBVTT state if header was not parsed correctly otherwise
9196        // another exception occurred so enter BADCUE state.
9197        self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE";
9198      }
9199      return this;
9200    },
9201    flush: function flush() {
9202      var self = this;
9203      try {
9204        // Finish decoding the stream.
9205        self.buffer += self.decoder.decode();
9206        // Synthesize the end of the current cue or region.
9207        if (self.cue || self.state === "HEADER") {
9208          self.buffer += "\n\n";
9209          self.parse();
9210        }
9211        // If we've flushed, parsed, and we're still on the INITIAL state then
9212        // that means we don't have enough of the stream to parse the first
9213        // line.
9214        if (self.state === "INITIAL") {
9215          throw new ParsingError(ParsingError.Errors.BadSignature);
9216        }
9217      } catch (e) {
9218        self.reportOrThrowError(e);
9219      }
9220      self.onflush && self.onflush();
9221      return this;
9222    }
9223  };
9224
9225  var vtt = WebVTT$1;
9226
9227  var vtt$1 = /*#__PURE__*/Object.freeze({
9228    default: vtt,
9229    __moduleExports: vtt
9230  });
9231
9232  /**
9233   * Copyright 2013 vtt.js Contributors
9234   *
9235   * Licensed under the Apache License, Version 2.0 (the "License");
9236   * you may not use this file except in compliance with the License.
9237   * You may obtain a copy of the License at
9238   *
9239   *   http://www.apache.org/licenses/LICENSE-2.0
9240   *
9241   * Unless required by applicable law or agreed to in writing, software
9242   * distributed under the License is distributed on an "AS IS" BASIS,
9243   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9244   * See the License for the specific language governing permissions and
9245   * limitations under the License.
9246   */
9247
9248  var autoKeyword = "auto";
9249  var directionSetting = {
9250    "": 1,
9251    "lr": 1,
9252    "rl": 1
9253  };
9254  var alignSetting = {
9255    "start": 1,
9256    "middle": 1,
9257    "end": 1,
9258    "left": 1,
9259    "right": 1
9260  };
9261
9262  function findDirectionSetting(value) {
9263    if (typeof value !== "string") {
9264      return false;
9265    }
9266    var dir = directionSetting[value.toLowerCase()];
9267    return dir ? value.toLowerCase() : false;
9268  }
9269
9270  function findAlignSetting(value) {
9271    if (typeof value !== "string") {
9272      return false;
9273    }
9274    var align = alignSetting[value.toLowerCase()];
9275    return align ? value.toLowerCase() : false;
9276  }
9277
9278  function VTTCue(startTime, endTime, text) {
9279    /**
9280     * Shim implementation specific properties. These properties are not in
9281     * the spec.
9282     */
9283
9284    // Lets us know when the VTTCue's data has changed in such a way that we need
9285    // to recompute its display state. This lets us compute its display state
9286    // lazily.
9287    this.hasBeenReset = false;
9288
9289    /**
9290     * VTTCue and TextTrackCue properties
9291     * http://dev.w3.org/html5/webvtt/#vttcue-interface
9292     */
9293
9294    var _id = "";
9295    var _pauseOnExit = false;
9296    var _startTime = startTime;
9297    var _endTime = endTime;
9298    var _text = text;
9299    var _region = null;
9300    var _vertical = "";
9301    var _snapToLines = true;
9302    var _line = "auto";
9303    var _lineAlign = "start";
9304    var _position = 50;
9305    var _positionAlign = "middle";
9306    var _size = 50;
9307    var _align = "middle";
9308
9309    Object.defineProperties(this, {
9310      "id": {
9311        enumerable: true,
9312        get: function get() {
9313          return _id;
9314        },
9315        set: function set(value) {
9316          _id = "" + value;
9317        }
9318      },
9319
9320      "pauseOnExit": {
9321        enumerable: true,
9322        get: function get() {
9323          return _pauseOnExit;
9324        },
9325        set: function set(value) {
9326          _pauseOnExit = !!value;
9327        }
9328      },
9329
9330      "startTime": {
9331        enumerable: true,
9332        get: function get() {
9333          return _startTime;
9334        },
9335        set: function set(value) {
9336          if (typeof value !== "number") {
9337            throw new TypeError("Start time must be set to a number.");
9338          }
9339          _startTime = value;
9340          this.hasBeenReset = true;
9341        }
9342      },
9343
9344      "endTime": {
9345        enumerable: true,
9346        get: function get() {
9347          return _endTime;
9348        },
9349        set: function set(value) {
9350          if (typeof value !== "number") {
9351            throw new TypeError("End time must be set to a number.");
9352          }
9353          _endTime = value;
9354          this.hasBeenReset = true;
9355        }
9356      },
9357
9358      "text": {
9359        enumerable: true,
9360        get: function get() {
9361          return _text;
9362        },
9363        set: function set(value) {
9364          _text = "" + value;
9365          this.hasBeenReset = true;
9366        }
9367      },
9368
9369      "region": {
9370        enumerable: true,
9371        get: function get() {
9372          return _region;
9373        },
9374        set: function set(value) {
9375          _region = value;
9376          this.hasBeenReset = true;
9377        }
9378      },
9379
9380      "vertical": {
9381        enumerable: true,
9382        get: function get() {
9383          return _vertical;
9384        },
9385        set: function set(value) {
9386          var setting = findDirectionSetting(value);
9387          // Have to check for false because the setting an be an empty string.
9388          if (setting === false) {
9389            throw new SyntaxError("An invalid or illegal string was specified.");
9390          }
9391          _vertical = setting;
9392          this.hasBeenReset = true;
9393        }
9394      },
9395
9396      "snapToLines": {
9397        enumerable: true,
9398        get: function get() {
9399          return _snapToLines;
9400        },
9401        set: function set(value) {
9402          _snapToLines = !!value;
9403          this.hasBeenReset = true;
9404        }
9405      },
9406
9407      "line": {
9408        enumerable: true,
9409        get: function get() {
9410          return _line;
9411        },
9412        set: function set(value) {
vendor: 4,519 bytes, lines 9413-9580
9413          if (typeof value !== "number" && value !== autoKeyword) {
9414            throw new SyntaxError("An invalid number or illegal string was specified.");
9415          }
9416          _line = value;
9417          this.hasBeenReset = true;
9418        }
9419      },
9420
9421      "lineAlign": {
9422        enumerable: true,
9423        get: function get() {
9424          return _lineAlign;
9425        },
9426        set: function set(value) {
9427          var setting = findAlignSetting(value);
9428          if (!setting) {
9429            throw new SyntaxError("An invalid or illegal string was specified.");
9430          }
9431          _lineAlign = setting;
9432          this.hasBeenReset = true;
9433        }
9434      },
9435
9436      "position": {
9437        enumerable: true,
9438        get: function get() {
9439          return _position;
9440        },
9441        set: function set(value) {
9442          if (value < 0 || value > 100) {
9443            throw new Error("Position must be between 0 and 100.");
9444          }
9445          _position = value;
9446          this.hasBeenReset = true;
9447        }
9448      },
9449
9450      "positionAlign": {
9451        enumerable: true,
9452        get: function get() {
9453          return _positionAlign;
9454        },
9455        set: function set(value) {
9456          var setting = findAlignSetting(value);
9457          if (!setting) {
9458            throw new SyntaxError("An invalid or illegal string was specified.");
9459          }
9460          _positionAlign = setting;
9461          this.hasBeenReset = true;
9462        }
9463      },
9464
9465      "size": {
9466        enumerable: true,
9467        get: function get() {
9468          return _size;
9469        },
9470        set: function set(value) {
9471          if (value < 0 || value > 100) {
9472            throw new Error("Size must be between 0 and 100.");
9473          }
9474          _size = value;
9475          this.hasBeenReset = true;
9476        }
9477      },
9478
9479      "align": {
9480        enumerable: true,
9481        get: function get() {
9482          return _align;
9483        },
9484        set: function set(value) {
9485          var setting = findAlignSetting(value);
9486          if (!setting) {
9487            throw new SyntaxError("An invalid or illegal string was specified.");
9488          }
9489          _align = setting;
9490          this.hasBeenReset = true;
9491        }
9492      }
9493    });
9494
9495    /**
9496     * Other <track> spec defined properties
9497     */
9498
9499    // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state
9500    this.displayState = undefined;
9501  }
9502
9503  /**
9504   * VTTCue methods
9505   */
9506
9507  VTTCue.prototype.getCueAsHTML = function () {
9508    // Assume WebVTT.convertCueToDOMTree is on the global.
9509    return WebVTT.convertCueToDOMTree(window, this.text);
9510  };
9511
9512  var vttcue = VTTCue;
9513
9514  var vttcue$1 = /*#__PURE__*/Object.freeze({
9515    default: vttcue,
9516    __moduleExports: vttcue
9517  });
9518
9519  /**
9520   * Copyright 2013 vtt.js Contributors
9521   *
9522   * Licensed under the Apache License, Version 2.0 (the "License");
9523   * you may not use this file except in compliance with the License.
9524   * You may obtain a copy of the License at
9525   *
9526   *   http://www.apache.org/licenses/LICENSE-2.0
9527   *
9528   * Unless required by applicable law or agreed to in writing, software
9529   * distributed under the License is distributed on an "AS IS" BASIS,
9530   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9531   * See the License for the specific language governing permissions and
9532   * limitations under the License.
9533   */
9534
9535  var scrollSetting = {
9536    "": true,
9537    "up": true
9538  };
9539
9540  function findScrollSetting(value) {
9541    if (typeof value !== "string") {
9542      return false;
9543    }
9544    var scroll = scrollSetting[value.toLowerCase()];
9545    return scroll ? value.toLowerCase() : false;
9546  }
9547
9548  function isValidPercentValue(value) {
9549    return typeof value === "number" && value >= 0 && value <= 100;
9550  }
9551
9552  // VTTRegion shim http://dev.w3.org/html5/webvtt/#vttregion-interface
9553  function VTTRegion() {
9554    var _width = 100;
9555    var _lines = 3;
9556    var _regionAnchorX = 0;
9557    var _regionAnchorY = 100;
9558    var _viewportAnchorX = 0;
9559    var _viewportAnchorY = 100;
9560    var _scroll = "";
9561
9562    Object.defineProperties(this, {
9563      "width": {
9564        enumerable: true,
9565        get: function get() {
9566          return _width;
9567        },
9568        set: function set(value) {
9569          if (!isValidPercentValue(value)) {
9570            throw new Error("Width must be between 0 and 100.");
9571          }
9572          _width = value;
9573        }
9574      },
9575      "lines": {
9576        enumerable: true,
9577        get: function get() {
9578          return _lines;
9579        },
9580        set: function set(value) {
9581          if (typeof value !== "number") {
9582            throw new TypeError("Lines must be set to a number.");
9583          }
9584          _lines = value;
9585        }
9586      },
9587      "regionAnchorY": {
9588        enumerable: true,
9589        get: function get() {
9590          return _regionAnchorY;
9591        },
9592        set: function set(value) {
9593          if (!isValidPercentValue(value)) {
9594            throw new Error("RegionAnchorX must be between 0 and 100.");
9595          }
9596          _regionAnchorY = value;
9597        }
9598      },
9599      "regionAnchorX": {
9600        enumerable: true,
9601        get: function get() {
9602          return _regionAnchorX;
9603        },
9604        set: function set(value) {
9605          if (!isValidPercentValue(value)) {
9606            throw new Error("RegionAnchorY must be between 0 and 100.");
9607          }
9608          _regionAnchorX = value;
9609        }
9610      },
9611      "viewportAnchorY": {
9612        enumerable: true,
9613        get: function get() {
9614          return _viewportAnchorY;
9615        },
9616        set: function set(value) {
9617          if (!isValidPercentValue(value)) {
9618            throw new Error("ViewportAnchorY must be between 0 and 100.");
9619          }
9620          _viewportAnchorY = value;
9621        }
9622      },
9623      "viewportAnchorX": {
9624        enumerable: true,
9625        get: function get() {
9626          return _viewportAnchorX;
9627        },
9628        set: function set(value) {
9629          if (!isValidPercentValue(value)) {
9630            throw new Error("ViewportAnchorX must be between 0 and 100.");
9631          }
9632          _viewportAnchorX = value;
9633        }
9634      },
9635      "scroll": {
9636        enumerable: true,
9637        get: function get() {
9638          return _scroll;
9639        },
9640        set: function set(value) {
9641          var setting = findScrollSetting(value);
9642          // Have to check for false as an empty string is a legal value.
9643          if (setting === false) {
9644            throw new SyntaxError("An invalid or illegal string was specified.");
9645          }
9646          _scroll = setting;
9647        }
9648      }
9649    });
9650  }
9651
9652  var vttregion = VTTRegion;
9653
9654  var vttregion$1 = /*#__PURE__*/Object.freeze({
9655    default: vttregion,
9656    __moduleExports: vttregion
9657  });
9658
9659  var require$$0 = ( vtt$1 && vtt ) || vtt$1;
9660
9661  var require$$1 = ( vttcue$1 && vttcue ) || vttcue$1;
9662
9663  var require$$2 = ( vttregion$1 && vttregion ) || vttregion$1;
9664
9665  var browserIndex = createCommonjsModule(function (module) {
9666    /**
9667     * Copyright 2013 vtt.js Contributors
9668     *
9669     * Licensed under the Apache License, Version 2.0 (the "License");
9670     * you may not use this file except in compliance with the License.
9671     * You may obtain a copy of the License at
9672     *
9673     *   http://www.apache.org/licenses/LICENSE-2.0
9674     *
9675     * Unless required by applicable law or agreed to in writing, software
9676     * distributed under the License is distributed on an "AS IS" BASIS,
9677     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9678     * See the License for the specific language governing permissions and
9679     * limitations under the License.
9680     */
9681
9682    // Default exports for Node. Export the extended versions of VTTCue and
9683    // VTTRegion in Node since we likely want the capability to convert back and
9684    // forth between JSON. If we don't then it's not that big of a deal since we're
9685    // off browser.
9686
9687
9688    var vttjs = module.exports = {
9689      WebVTT: require$$0,
9690      VTTCue: require$$1,
9691      VTTRegion: require$$2
9692    };
9693
9694    window_1.vttjs = vttjs;
9695    window_1.WebVTT = vttjs.WebVTT;
9696
9697    var cueShim = vttjs.VTTCue;
9698    var regionShim = vttjs.VTTRegion;
9699    var nativeVTTCue = window_1.VTTCue;
9700    var nativeVTTRegion = window_1.VTTRegion;
9701
9702    vttjs.shim = function () {
9703      window_1.VTTCue = cueShim;
9704      window_1.VTTRegion = regionShim;
9705    };
9706
9707    vttjs.restore = function () {
9708      window_1.VTTCue = nativeVTTCue;
9709      window_1.VTTRegion = nativeVTTRegion;
9710    };
9711
9712    if (!window_1.VTTCue) {
9713      vttjs.shim();
9714    }
9715  });
9716  var browserIndex_1 = browserIndex.WebVTT;
9717  var browserIndex_2 = browserIndex.VTTCue;
9718  var browserIndex_3 = browserIndex.VTTRegion;
9719
9720  /**
9721   * @file tech.js
9722   */
9723
9724  /**
9725   * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string
9726   * that just contains the src url alone.
9727   * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};`
9728     * `var SourceString = 'http://example.com/some-video.mp4';`
9729   *
9730   * @typedef {Object|string} Tech~SourceObject
9731   *
9732   * @property {string} src
9733   *           The url to the source
9734   *
9735   * @property {string} type
9736   *           The mime type of the source
9737   */
9738
9739  /**
9740   * A function used by {@link Tech} to create a new {@link TextTrack}.
9741   *
9742   * @private
9743   *
9744   * @param {Tech} self
9745   *        An instance of the Tech class.
9746   *
9747   * @param {string} kind
9748   *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
9749   *
9750   * @param {string} [label]
9751   *        Label to identify the text track
9752   *
9753   * @param {string} [language]
9754   *        Two letter language abbreviation
9755   *
9756   * @param {Object} [options={}]
9757   *        An object with additional text track options
9758   *
9759   * @return {TextTrack}
9760   *          The text track that was created.
9761   */
9762  function createTrackHelper(self, kind, label, language) {
9763    var options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
9764
9765    var tracks = self.textTracks();
9766
9767    options.kind = kind;
9768
9769    if (label) {
9770      options.label = label;
9771    }
9772    if (language) {
9773      options.language = language;
9774    }
9775    options.tech = self;
9776
9777    var track = new ALL.text.TrackClass(options);
9778
9779    tracks.addTrack(track);
9780
9781    return track;
9782  }
9783
9784  /**
9785   * This is the base class for media playback technology controllers, such as
9786   * {@link Flash} and {@link HTML5}
9787   *
9788   * @extends Component
9789   */
9790
9791  var Tech = function (_Component) {
9792    inherits(Tech, _Component);
9793
9794    /**
9795     * Create an instance of this Tech.
9796     *
9797     * @param {Object} [options]
9798     *        The key/value store of player options.
9799     *
9800     * @param {Component~ReadyCallback} ready
9801     *        Callback function to call when the `HTML5` Tech is ready.
9802     */
9803    function Tech() {
9804      var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
9805      var ready = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {};
9806      classCallCheck(this, Tech);
9807
9808      // we don't want the tech to report user activity automatically.
vendor: 304,903 bytes, lines 9809-20500
9809      // This is done manually in addControlsListeners
9810      options.reportTouchActivity = false;
9811
9812      // keep track of whether the current source has played at all to
9813      // implement a very limited played()
9814      var _this = possibleConstructorReturn(this, _Component.call(this, null, options, ready));
9815
9816      _this.hasStarted_ = false;
9817      _this.on('playing', function () {
9818        this.hasStarted_ = true;
9819      });
9820      _this.on('loadstart', function () {
9821        this.hasStarted_ = false;
9822      });
9823
9824      ALL.names.forEach(function (name) {
9825        var props = ALL[name];
9826
9827        if (options && options[props.getterName]) {
9828          _this[props.privateName] = options[props.getterName];
9829        }
9830      });
9831
9832      // Manually track progress in cases where the browser/flash player doesn't report it.
9833      if (!_this.featuresProgressEvents) {
9834        _this.manualProgressOn();
9835      }
9836
9837      // Manually track timeupdates in cases where the browser/flash player doesn't report it.
9838      if (!_this.featuresTimeupdateEvents) {
9839        _this.manualTimeUpdatesOn();
9840      }
9841
9842      ['Text', 'Audio', 'Video'].forEach(function (track) {
9843        if (options['native' + track + 'Tracks'] === false) {
9844          _this['featuresNative' + track + 'Tracks'] = false;
9845        }
9846      });
9847
9848      if (options.nativeCaptions === false || options.nativeTextTracks === false) {
9849        _this.featuresNativeTextTracks = false;
9850      } else if (options.nativeCaptions === true || options.nativeTextTracks === true) {
9851        _this.featuresNativeTextTracks = true;
9852      }
9853
9854      if (!_this.featuresNativeTextTracks) {
9855        _this.emulateTextTracks();
9856      }
9857
9858      _this.autoRemoteTextTracks_ = new ALL.text.ListClass();
9859
9860      _this.initTrackListeners();
9861
9862      // Turn on component tap events only if not using native controls
9863      if (!options.nativeControlsForTouch) {
9864        _this.emitTapEvents();
9865      }
9866
9867      if (_this.constructor) {
9868        _this.name_ = _this.constructor.name || 'Unknown Tech';
9869      }
9870      return _this;
9871    }
9872
9873    /**
9874     * A special function to trigger source set in a way that will allow player
9875     * to re-trigger if the player or tech are not ready yet.
9876     *
9877     * @fires Tech#sourceset
9878     * @param {string} src The source string at the time of the source changing.
9879     */
9880
9881
9882    Tech.prototype.triggerSourceset = function triggerSourceset(src) {
9883      var _this2 = this;
9884
9885      if (!this.isReady_) {
9886        // on initial ready we have to trigger source set
9887        // 1ms after ready so that player can watch for it.
9888        this.one('ready', function () {
9889          return _this2.setTimeout(function () {
9890            return _this2.triggerSourceset(src);
9891          }, 1);
9892        });
9893      }
9894
9895      /**
9896       * Fired when the source is set on the tech causing the media element
9897       * to reload.
9898       *
9899       * @see {@link Player#event:sourceset}
9900       * @event Tech#sourceset
9901       * @type {EventTarget~Event}
9902       */
9903      this.trigger({
9904        src: src,
9905        type: 'sourceset'
9906      });
9907    };
9908
9909    /* Fallbacks for unsupported event types
9910    ================================================================================ */
9911
9912    /**
9913     * Polyfill the `progress` event for browsers that don't support it natively.
9914     *
9915     * @see {@link Tech#trackProgress}
9916     */
9917
9918
9919    Tech.prototype.manualProgressOn = function manualProgressOn() {
9920      this.on('durationchange', this.onDurationChange);
9921
9922      this.manualProgress = true;
9923
9924      // Trigger progress watching when a source begins loading
9925      this.one('ready', this.trackProgress);
9926    };
9927
9928    /**
9929     * Turn off the polyfill for `progress` events that was created in
9930     * {@link Tech#manualProgressOn}
9931     */
9932
9933
9934    Tech.prototype.manualProgressOff = function manualProgressOff() {
9935      this.manualProgress = false;
9936      this.stopTrackingProgress();
9937
9938      this.off('durationchange', this.onDurationChange);
9939    };
9940
9941    /**
9942     * This is used to trigger a `progress` event when the buffered percent changes. It
9943     * sets an interval function that will be called every 500 milliseconds to check if the
9944     * buffer end percent has changed.
9945     *
9946     * > This function is called by {@link Tech#manualProgressOn}
9947     *
9948     * @param {EventTarget~Event} event
9949     *        The `ready` event that caused this to run.
9950     *
9951     * @listens Tech#ready
9952     * @fires Tech#progress
9953     */
9954
9955
9956    Tech.prototype.trackProgress = function trackProgress(event) {
9957      this.stopTrackingProgress();
9958      this.progressInterval = this.setInterval(bind(this, function () {
9959        // Don't trigger unless buffered amount is greater than last time
9960
9961        var numBufferedPercent = this.bufferedPercent();
9962
9963        if (this.bufferedPercent_ !== numBufferedPercent) {
9964          /**
9965           * See {@link Player#progress}
9966           *
9967           * @event Tech#progress
9968           * @type {EventTarget~Event}
9969           */
9970          this.trigger('progress');
9971        }
9972
9973        this.bufferedPercent_ = numBufferedPercent;
9974
9975        if (numBufferedPercent === 1) {
9976          this.stopTrackingProgress();
9977        }
9978      }), 500);
9979    };
9980
9981    /**
9982     * Update our internal duration on a `durationchange` event by calling
9983     * {@link Tech#duration}.
9984     *
9985     * @param {EventTarget~Event} event
9986     *        The `durationchange` event that caused this to run.
9987     *
9988     * @listens Tech#durationchange
9989     */
9990
9991
9992    Tech.prototype.onDurationChange = function onDurationChange(event) {
9993      this.duration_ = this.duration();
9994    };
9995
9996    /**
9997     * Get and create a `TimeRange` object for buffering.
9998     *
9999     * @return {TimeRange}
10000     *         The time range object that was created.
10001     */
10002
10003
10004    Tech.prototype.buffered = function buffered() {
10005      return createTimeRanges(0, 0);
10006    };
10007
10008    /**
10009     * Get the percentage of the current video that is currently buffered.
10010     *
10011     * @return {number}
10012     *         A number from 0 to 1 that represents the decimal percentage of the
10013     *         video that is buffered.
10014     *
10015     */
10016
10017
10018    Tech.prototype.bufferedPercent = function bufferedPercent$$1() {
10019      return bufferedPercent(this.buffered(), this.duration_);
10020    };
10021
10022    /**
10023     * Turn off the polyfill for `progress` events that was created in
10024     * {@link Tech#manualProgressOn}
10025     * Stop manually tracking progress events by clearing the interval that was set in
10026     * {@link Tech#trackProgress}.
10027     */
10028
10029
10030    Tech.prototype.stopTrackingProgress = function stopTrackingProgress() {
10031      this.clearInterval(this.progressInterval);
10032    };
10033
10034    /**
10035     * Polyfill the `timeupdate` event for browsers that don't support it.
10036     *
10037     * @see {@link Tech#trackCurrentTime}
10038     */
10039
10040
10041    Tech.prototype.manualTimeUpdatesOn = function manualTimeUpdatesOn() {
10042      this.manualTimeUpdates = true;
10043
10044      this.on('play', this.trackCurrentTime);
10045      this.on('pause', this.stopTrackingCurrentTime);
10046    };
10047
10048    /**
10049     * Turn off the polyfill for `timeupdate` events that was created in
10050     * {@link Tech#manualTimeUpdatesOn}
10051     */
10052
10053
10054    Tech.prototype.manualTimeUpdatesOff = function manualTimeUpdatesOff() {
10055      this.manualTimeUpdates = false;
10056      this.stopTrackingCurrentTime();
10057      this.off('play', this.trackCurrentTime);
10058      this.off('pause', this.stopTrackingCurrentTime);
10059    };
10060
10061    /**
10062     * Sets up an interval function to track current time and trigger `timeupdate` every
10063     * 250 milliseconds.
10064     *
10065     * @listens Tech#play
10066     * @triggers Tech#timeupdate
10067     */
10068
10069
10070    Tech.prototype.trackCurrentTime = function trackCurrentTime() {
10071      if (this.currentTimeInterval) {
10072        this.stopTrackingCurrentTime();
10073      }
10074      this.currentTimeInterval = this.setInterval(function () {
10075        /**
10076         * Triggered at an interval of 250ms to indicated that time is passing in the video.
10077         *
10078         * @event Tech#timeupdate
10079         * @type {EventTarget~Event}
10080         */
10081        this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
10082
10083        // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
10084      }, 250);
10085    };
10086
10087    /**
10088     * Stop the interval function created in {@link Tech#trackCurrentTime} so that the
10089     * `timeupdate` event is no longer triggered.
10090     *
10091     * @listens {Tech#pause}
10092     */
10093
10094
10095    Tech.prototype.stopTrackingCurrentTime = function stopTrackingCurrentTime() {
10096      this.clearInterval(this.currentTimeInterval);
10097
10098      // #1002 - if the video ends right before the next timeupdate would happen,
10099      // the progress bar won't make it all the way to the end
10100      this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
10101    };
10102
10103    /**
10104     * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList},
10105     * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech.
10106     *
10107     * @fires Component#dispose
10108     */
10109
10110
10111    Tech.prototype.dispose = function dispose() {
10112
10113      // clear out all tracks because we can't reuse them between techs
10114      this.clearTracks(NORMAL.names);
10115
10116      // Turn off any manual progress or timeupdate tracking
10117      if (this.manualProgress) {
10118        this.manualProgressOff();
10119      }
10120
10121      if (this.manualTimeUpdates) {
10122        this.manualTimeUpdatesOff();
10123      }
10124
10125      _Component.prototype.dispose.call(this);
10126    };
10127
10128    /**
10129     * Clear out a single `TrackList` or an array of `TrackLists` given their names.
10130     *
10131     * > Note: Techs without source handlers should call this between sources for `video`
10132     *         & `audio` tracks. You don't want to use them between tracks!
10133     *
10134     * @param {string[]|string} types
10135     *        TrackList names to clear, valid names are `video`, `audio`, and
10136     *        `text`.
10137     */
10138
10139
10140    Tech.prototype.clearTracks = function clearTracks(types) {
10141      var _this3 = this;
10142
10143      types = [].concat(types);
10144      // clear out all tracks because we can't reuse them between techs
10145      types.forEach(function (type) {
10146        var list = _this3[type + 'Tracks']() || [];
10147        var i = list.length;
10148
10149        while (i--) {
10150          var track = list[i];
10151
10152          if (type === 'text') {
10153            _this3.removeRemoteTextTrack(track);
10154          }
10155          list.removeTrack(track);
10156        }
10157      });
10158    };
10159
10160    /**
10161     * Remove any TextTracks added via addRemoteTextTrack that are
10162     * flagged for automatic garbage collection
10163     */
10164
10165
10166    Tech.prototype.cleanupAutoTextTracks = function cleanupAutoTextTracks() {
10167      var list = this.autoRemoteTextTracks_ || [];
10168      var i = list.length;
10169
10170      while (i--) {
10171        var track = list[i];
10172
10173        this.removeRemoteTextTrack(track);
10174      }
10175    };
10176
10177    /**
10178     * Reset the tech, which will removes all sources and reset the internal readyState.
10179     *
10180     * @abstract
10181     */
10182
10183
10184    Tech.prototype.reset = function reset() {};
10185
10186    /**
10187     * Get or set an error on the Tech.
10188     *
10189     * @param {MediaError} [err]
10190     *        Error to set on the Tech
10191     *
10192     * @return {MediaError|null}
10193     *         The current error object on the tech, or null if there isn't one.
10194     */
10195
10196
10197    Tech.prototype.error = function error(err) {
10198      if (err !== undefined) {
10199        this.error_ = new MediaError(err);
10200        this.trigger('error');
10201      }
10202      return this.error_;
10203    };
10204
10205    /**
10206     * Returns the `TimeRange`s that have been played through for the current source.
10207     *
10208     * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`.
10209     *         It only checks whether the source has played at all or not.
10210     *
10211     * @return {TimeRange}
10212     *         - A single time range if this video has played
10213     *         - An empty set of ranges if not.
10214     */
10215
10216
10217    Tech.prototype.played = function played() {
10218      if (this.hasStarted_) {
10219        return createTimeRanges(0, 0);
10220      }
10221      return createTimeRanges();
10222    };
10223
10224    /**
10225     * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was
10226     * previously called.
10227     *
10228     * @fires Tech#timeupdate
10229     */
10230
10231
10232    Tech.prototype.setCurrentTime = function setCurrentTime() {
10233      // improve the accuracy of manual timeupdates
10234      if (this.manualTimeUpdates) {
10235        /**
10236         * A manual `timeupdate` event.
10237         *
10238         * @event Tech#timeupdate
10239         * @type {EventTarget~Event}
10240         */
10241        this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
10242      }
10243    };
10244
10245    /**
10246     * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and
10247     * {@link TextTrackList} events.
10248     *
10249     * This adds {@link EventTarget~EventListeners} for `addtrack`, and  `removetrack`.
10250     *
10251     * @fires Tech#audiotrackchange
10252     * @fires Tech#videotrackchange
10253     * @fires Tech#texttrackchange
10254     */
10255
10256
10257    Tech.prototype.initTrackListeners = function initTrackListeners() {
10258      var _this4 = this;
10259
10260      /**
10261       * Triggered when tracks are added or removed on the Tech {@link AudioTrackList}
10262       *
10263       * @event Tech#audiotrackchange
10264       * @type {EventTarget~Event}
10265       */
10266
10267      /**
10268       * Triggered when tracks are added or removed on the Tech {@link VideoTrackList}
10269       *
10270       * @event Tech#videotrackchange
10271       * @type {EventTarget~Event}
10272       */
10273
10274      /**
10275       * Triggered when tracks are added or removed on the Tech {@link TextTrackList}
10276       *
10277       * @event Tech#texttrackchange
10278       * @type {EventTarget~Event}
10279       */
10280      NORMAL.names.forEach(function (name) {
10281        var props = NORMAL[name];
10282        var trackListChanges = function trackListChanges() {
10283          _this4.trigger(name + 'trackchange');
10284        };
10285
10286        var tracks = _this4[props.getterName]();
10287
10288        tracks.addEventListener('removetrack', trackListChanges);
10289        tracks.addEventListener('addtrack', trackListChanges);
10290
10291        _this4.on('dispose', function () {
10292          tracks.removeEventListener('removetrack', trackListChanges);
10293          tracks.removeEventListener('addtrack', trackListChanges);
10294        });
10295      });
10296    };
10297
10298    /**
10299     * Emulate TextTracks using vtt.js if necessary
10300     *
10301     * @fires Tech#vttjsloaded
10302     * @fires Tech#vttjserror
10303     */
10304
10305
10306    Tech.prototype.addWebVttScript_ = function addWebVttScript_() {
10307      var _this5 = this;
10308
10309      if (window_1.WebVTT) {
10310        return;
10311      }
10312
10313      // Initially, Tech.el_ is a child of a dummy-div wait until the Component system
10314      // signals that the Tech is ready at which point Tech.el_ is part of the DOM
10315      // before inserting the WebVTT script
10316      if (document_1.body.contains(this.el())) {
10317
10318        // load via require if available and vtt.js script location was not passed in
10319        // as an option. novtt builds will turn the above require call into an empty object
10320        // which will cause this if check to always fail.
10321        if (!this.options_['vtt.js'] && isPlain(browserIndex) && Object.keys(browserIndex).length > 0) {
10322          this.trigger('vttjsloaded');
10323          return;
10324        }
10325
10326        // load vtt.js via the script location option or the cdn of no location was
10327        // passed in
10328        var script = document_1.createElement('script');
10329
10330        script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js';
10331        script.onload = function () {
10332          /**
10333           * Fired when vtt.js is loaded.
10334           *
10335           * @event Tech#vttjsloaded
10336           * @type {EventTarget~Event}
10337           */
10338          _this5.trigger('vttjsloaded');
10339        };
10340        script.onerror = function () {
10341          /**
10342           * Fired when vtt.js was not loaded due to an error
10343           *
10344           * @event Tech#vttjsloaded
10345           * @type {EventTarget~Event}
10346           */
10347          _this5.trigger('vttjserror');
10348        };
10349        this.on('dispose', function () {
10350          script.onload = null;
10351          script.onerror = null;
10352        });
10353        // but have not loaded yet and we set it to true before the inject so that
10354        // we don't overwrite the injected window.WebVTT if it loads right away
10355        window_1.WebVTT = true;
10356        this.el().parentNode.appendChild(script);
10357      } else {
10358        this.ready(this.addWebVttScript_);
10359      }
10360    };
10361
10362    /**
10363     * Emulate texttracks
10364     *
10365     */
10366
10367
10368    Tech.prototype.emulateTextTracks = function emulateTextTracks() {
10369      var _this6 = this;
10370
10371      var tracks = this.textTracks();
10372      var remoteTracks = this.remoteTextTracks();
10373      var handleAddTrack = function handleAddTrack(e) {
10374        return tracks.addTrack(e.track);
10375      };
10376      var handleRemoveTrack = function handleRemoveTrack(e) {
10377        return tracks.removeTrack(e.track);
10378      };
10379
10380      remoteTracks.on('addtrack', handleAddTrack);
10381      remoteTracks.on('removetrack', handleRemoveTrack);
10382
10383      this.addWebVttScript_();
10384
10385      var updateDisplay = function updateDisplay() {
10386        return _this6.trigger('texttrackchange');
10387      };
10388
10389      var textTracksChanges = function textTracksChanges() {
10390        updateDisplay();
10391
10392        for (var i = 0; i < tracks.length; i++) {
10393          var track = tracks[i];
10394
10395          track.removeEventListener('cuechange', updateDisplay);
10396          if (track.mode === 'showing') {
10397            track.addEventListener('cuechange', updateDisplay);
10398          }
10399        }
10400      };
10401
10402      textTracksChanges();
10403      tracks.addEventListener('change', textTracksChanges);
10404      tracks.addEventListener('addtrack', textTracksChanges);
10405      tracks.addEventListener('removetrack', textTracksChanges);
10406
10407      this.on('dispose', function () {
10408        remoteTracks.off('addtrack', handleAddTrack);
10409        remoteTracks.off('removetrack', handleRemoveTrack);
10410        tracks.removeEventListener('change', textTracksChanges);
10411        tracks.removeEventListener('addtrack', textTracksChanges);
10412        tracks.removeEventListener('removetrack', textTracksChanges);
10413
10414        for (var i = 0; i < tracks.length; i++) {
10415          var track = tracks[i];
10416
10417          track.removeEventListener('cuechange', updateDisplay);
10418        }
10419      });
10420    };
10421
10422    /**
10423     * Create and returns a remote {@link TextTrack} object.
10424     *
10425     * @param {string} kind
10426     *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
10427     *
10428     * @param {string} [label]
10429     *        Label to identify the text track
10430     *
10431     * @param {string} [language]
10432     *        Two letter language abbreviation
10433     *
10434     * @return {TextTrack}
10435     *         The TextTrack that gets created.
10436     */
10437
10438
10439    Tech.prototype.addTextTrack = function addTextTrack(kind, label, language) {
10440      if (!kind) {
10441        throw new Error('TextTrack kind is required but was not provided');
10442      }
10443
10444      return createTrackHelper(this, kind, label, language);
10445    };
10446
10447    /**
10448     * Create an emulated TextTrack for use by addRemoteTextTrack
10449     *
10450     * This is intended to be overridden by classes that inherit from
10451     * Tech in order to create native or custom TextTracks.
10452     *
10453     * @param {Object} options
10454     *        The object should contain the options to initialize the TextTrack with.
10455     *
10456     * @param {string} [options.kind]
10457     *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
10458     *
10459     * @param {string} [options.label].
10460     *        Label to identify the text track
10461     *
10462     * @param {string} [options.language]
10463     *        Two letter language abbreviation.
10464     *
10465     * @return {HTMLTrackElement}
10466     *         The track element that gets created.
10467     */
10468
10469
10470    Tech.prototype.createRemoteTextTrack = function createRemoteTextTrack(options) {
10471      var track = mergeOptions(options, {
10472        tech: this
10473      });
10474
10475      return new REMOTE.remoteTextEl.TrackClass(track);
10476    };
10477
10478    /**
10479     * Creates a remote text track object and returns an html track element.
10480     *
10481     * > Note: This can be an emulated {@link HTMLTrackElement} or a native one.
10482     *
10483     * @param {Object} options
10484     *        See {@link Tech#createRemoteTextTrack} for more detailed properties.
10485     *
10486     * @param {boolean} [manualCleanup=true]
10487     *        - When false: the TextTrack will be automatically removed from the video
10488     *          element whenever the source changes
10489     *        - When True: The TextTrack will have to be cleaned up manually
10490     *
10491     * @return {HTMLTrackElement}
10492     *         An Html Track Element.
10493     *
10494     * @deprecated The default functionality for this function will be equivalent
10495     *             to "manualCleanup=false" in the future. The manualCleanup parameter will
10496     *             also be removed.
10497     */
10498
10499
10500    Tech.prototype.addRemoteTextTrack = function addRemoteTextTrack() {
10501      var _this7 = this;
10502
10503      var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
10504      var manualCleanup = arguments[1];
10505
10506      var htmlTrackElement = this.createRemoteTextTrack(options);
10507
10508      if (manualCleanup !== true && manualCleanup !== false) {
10509        // deprecation warning
10510        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');
10511        manualCleanup = true;
10512      }
10513
10514      // store HTMLTrackElement and TextTrack to remote list
10515      this.remoteTextTrackEls().addTrackElement_(htmlTrackElement);
10516      this.remoteTextTracks().addTrack(htmlTrackElement.track);
10517
10518      if (manualCleanup !== true) {
10519        // create the TextTrackList if it doesn't exist
10520        this.ready(function () {
10521          return _this7.autoRemoteTextTracks_.addTrack(htmlTrackElement.track);
10522        });
10523      }
10524
10525      return htmlTrackElement;
10526    };
10527
10528    /**
10529     * Remove a remote text track from the remote `TextTrackList`.
10530     *
10531     * @param {TextTrack} track
10532     *        `TextTrack` to remove from the `TextTrackList`
10533     */
10534
10535
10536    Tech.prototype.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
10537      var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track);
10538
10539      // remove HTMLTrackElement and TextTrack from remote list
10540      this.remoteTextTrackEls().removeTrackElement_(trackElement);
10541      this.remoteTextTracks().removeTrack(track);
10542      this.autoRemoteTextTracks_.removeTrack(track);
10543    };
10544
10545    /**
10546     * Gets available media playback quality metrics as specified by the W3C's Media
10547     * Playback Quality API.
10548     *
10549     * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
10550     *
10551     * @return {Object}
10552     *         An object with supported media playback quality metrics
10553     *
10554     * @abstract
10555     */
10556
10557
10558    Tech.prototype.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
10559      return {};
10560    };
10561
10562    /**
10563     * A method to set a poster from a `Tech`.
10564     *
10565     * @abstract
10566     */
10567
10568
10569    Tech.prototype.setPoster = function setPoster() {};
10570
10571    /**
10572     * A method to check for the presence of the 'playsinline' <video> attribute.
10573     *
10574     * @abstract
10575     */
10576
10577
10578    Tech.prototype.playsinline = function playsinline() {};
10579
10580    /**
10581     * A method to set or unset the 'playsinline' <video> attribute.
10582     *
10583     * @abstract
10584     */
10585
10586
10587    Tech.prototype.setPlaysinline = function setPlaysinline() {};
10588
10589    /**
10590     * Attempt to force override of native audio tracks.
10591     *
10592     * @param {Boolean} override - If set to true native audio will be overridden,
10593     * otherwise native audio will potentially be used.
10594     *
10595     * @abstract
10596     */
10597
10598
10599    Tech.prototype.overrideNativeAudioTracks = function overrideNativeAudioTracks() {};
10600
10601    /**
10602     * Attempt to force override of native video tracks.
10603     *
10604     * @param {Boolean} override - If set to true native video will be overridden,
10605     * otherwise native video will potentially be used.
10606     *
10607     * @abstract
10608     */
10609
10610
10611    Tech.prototype.overrideNativeVideoTracks = function overrideNativeVideoTracks() {};
10612
10613    /*
10614     * Check if the tech can support the given mime-type.
10615     *
10616     * The base tech does not support any type, but source handlers might
10617     * overwrite this.
10618     *
10619     * @param  {string} type
10620     *         The mimetype to check for support
10621     *
10622     * @return {string}
10623     *         'probably', 'maybe', or empty string
10624     *
10625     * @see [Spec]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType}
10626     *
10627     * @abstract
10628     */
10629
10630
10631    Tech.prototype.canPlayType = function canPlayType() {
10632      return '';
10633    };
10634
10635    /**
10636     * Check if the type is supported by this tech.
10637     *
10638     * The base tech does not support any type, but source handlers might
10639     * overwrite this.
10640     *
10641     * @param {string} type
10642     *        The media type to check
10643     * @return {string} Returns the native video element's response
10644     */
10645
10646
10647    Tech.canPlayType = function canPlayType() {
10648      return '';
10649    };
10650
10651    /**
10652     * Check if the tech can support the given source
10653     * @param {Object} srcObj
10654     *        The source object
10655     * @param {Object} options
10656     *        The options passed to the tech
10657     * @return {string} 'probably', 'maybe', or '' (empty string)
10658     */
10659
10660
10661    Tech.canPlaySource = function canPlaySource(srcObj, options) {
10662      return Tech.canPlayType(srcObj.type);
10663    };
10664
10665    /*
10666     * Return whether the argument is a Tech or not.
10667     * Can be passed either a Class like `Html5` or a instance like `player.tech_`
10668     *
10669     * @param {Object} component
10670     *        The item to check
10671     *
10672     * @return {boolean}
10673     *         Whether it is a tech or not
10674     *         - True if it is a tech
10675     *         - False if it is not
10676     */
10677
10678
10679    Tech.isTech = function isTech(component) {
10680      return component.prototype instanceof Tech || component instanceof Tech || component === Tech;
10681    };
10682
10683    /**
10684     * Registers a `Tech` into a shared list for videojs.
10685     *
10686     * @param {string} name
10687     *        Name of the `Tech` to register.
10688     *
10689     * @param {Object} tech
10690     *        The `Tech` class to register.
10691     */
10692
10693
10694    Tech.registerTech = function registerTech(name, tech) {
10695      if (!Tech.techs_) {
10696        Tech.techs_ = {};
10697      }
10698
10699      if (!Tech.isTech(tech)) {
10700        throw new Error('Tech ' + name + ' must be a Tech');
10701      }
10702
10703      if (!Tech.canPlayType) {
10704        throw new Error('Techs must have a static canPlayType method on them');
10705      }
10706      if (!Tech.canPlaySource) {
10707        throw new Error('Techs must have a static canPlaySource method on them');
10708      }
10709
10710      name = toTitleCase(name);
10711
10712      Tech.techs_[name] = tech;
10713      if (name !== 'Tech') {
10714        // camel case the techName for use in techOrder
10715        Tech.defaultTechOrder_.push(name);
10716      }
10717      return tech;
10718    };
10719
10720    /**
10721     * Get a `Tech` from the shared list by name.
10722     *
10723     * @param {string} name
10724     *        `camelCase` or `TitleCase` name of the Tech to get
10725     *
10726     * @return {Tech|undefined}
10727     *         The `Tech` or undefined if there was no tech with the name requested.
10728     */
10729
10730
10731    Tech.getTech = function getTech(name) {
10732      if (!name) {
10733        return;
10734      }
10735
10736      name = toTitleCase(name);
10737
10738      if (Tech.techs_ && Tech.techs_[name]) {
10739        return Tech.techs_[name];
10740      }
10741
10742      if (window_1 && window_1.videojs && window_1.videojs[name]) {
10743        log$1.warn('The ' + name + ' tech was added to the videojs object when it should be registered using videojs.registerTech(name, tech)');
10744        return window_1.videojs[name];
10745      }
10746    };
10747
10748    return Tech;
10749  }(Component);
10750
10751  /**
10752   * Get the {@link VideoTrackList}
10753   *
10754   * @returns {VideoTrackList}
10755   * @method Tech.prototype.videoTracks
10756   */
10757
10758  /**
10759   * Get the {@link AudioTrackList}
10760   *
10761   * @returns {AudioTrackList}
10762   * @method Tech.prototype.audioTracks
10763   */
10764
10765  /**
10766   * Get the {@link TextTrackList}
10767   *
10768   * @returns {TextTrackList}
10769   * @method Tech.prototype.textTracks
10770   */
10771
10772  /**
10773   * Get the remote element {@link TextTrackList}
10774   *
10775   * @returns {TextTrackList}
10776   * @method Tech.prototype.remoteTextTracks
10777   */
10778
10779  /**
10780   * Get the remote element {@link HtmlTrackElementList}
10781   *
10782   * @returns {HtmlTrackElementList}
10783   * @method Tech.prototype.remoteTextTrackEls
10784   */
10785
10786  ALL.names.forEach(function (name) {
10787    var props = ALL[name];
10788
10789    Tech.prototype[props.getterName] = function () {
10790      this[props.privateName] = this[props.privateName] || new props.ListClass();
10791      return this[props.privateName];
10792    };
10793  });
10794
10795  /**
10796   * List of associated text tracks
10797   *
10798   * @type {TextTrackList}
10799   * @private
10800   * @property Tech#textTracks_
10801   */
10802
10803  /**
10804   * List of associated audio tracks.
10805   *
10806   * @type {AudioTrackList}
10807   * @private
10808   * @property Tech#audioTracks_
10809   */
10810
10811  /**
10812   * List of associated video tracks.
10813   *
10814   * @type {VideoTrackList}
10815   * @private
10816   * @property Tech#videoTracks_
10817   */
10818
10819  /**
10820   * Boolean indicating whether the `Tech` supports volume control.
10821   *
10822   * @type {boolean}
10823   * @default
10824   */
10825  Tech.prototype.featuresVolumeControl = true;
10826
10827  /**
10828   * Boolean indicating whether the `Tech` supports muting volume.
10829   *
10830   * @type {bolean}
10831   * @default
10832   */
10833  Tech.prototype.featuresMuteControl = true;
10834
10835  /**
10836   * Boolean indicating whether the `Tech` supports fullscreen resize control.
10837   * Resizing plugins using request fullscreen reloads the plugin
10838   *
10839   * @type {boolean}
10840   * @default
10841   */
10842  Tech.prototype.featuresFullscreenResize = false;
10843
10844  /**
10845   * Boolean indicating whether the `Tech` supports changing the speed at which the video
10846   * plays. Examples:
10847   *   - Set player to play 2x (twice) as fast
10848   *   - Set player to play 0.5x (half) as fast
10849   *
10850   * @type {boolean}
10851   * @default
10852   */
10853  Tech.prototype.featuresPlaybackRate = false;
10854
10855  /**
10856   * Boolean indicating whether the `Tech` supports the `progress` event. This is currently
10857   * not triggered by video-js-swf. This will be used to determine if
10858   * {@link Tech#manualProgressOn} should be called.
10859   *
10860   * @type {boolean}
10861   * @default
10862   */
10863  Tech.prototype.featuresProgressEvents = false;
10864
10865  /**
10866   * Boolean indicating whether the `Tech` supports the `sourceset` event.
10867   *
10868   * A tech should set this to `true` and then use {@link Tech#triggerSourceset}
10869   * to trigger a {@link Tech#event:sourceset} at the earliest time after getting
10870   * a new source.
10871   *
10872   * @type {boolean}
10873   * @default
10874   */
10875  Tech.prototype.featuresSourceset = false;
10876
10877  /**
10878   * Boolean indicating whether the `Tech` supports the `timeupdate` event. This is currently
10879   * not triggered by video-js-swf. This will be used to determine if
10880   * {@link Tech#manualTimeUpdates} should be called.
10881   *
10882   * @type {boolean}
10883   * @default
10884   */
10885  Tech.prototype.featuresTimeupdateEvents = false;
10886
10887  /**
10888   * Boolean indicating whether the `Tech` supports the native `TextTrack`s.
10889   * This will help us integrate with native `TextTrack`s if the browser supports them.
10890   *
10891   * @type {boolean}
10892   * @default
10893   */
10894  Tech.prototype.featuresNativeTextTracks = false;
10895
10896  /**
10897   * A functional mixin for techs that want to use the Source Handler pattern.
10898   * Source handlers are scripts for handling specific formats.
10899   * The source handler pattern is used for adaptive formats (HLS, DASH) that
10900   * manually load video data and feed it into a Source Buffer (Media Source Extensions)
10901   * Example: `Tech.withSourceHandlers.call(MyTech);`
10902   *
10903   * @param {Tech} _Tech
10904   *        The tech to add source handler functions to.
10905   *
10906   * @mixes Tech~SourceHandlerAdditions
10907   */
10908  Tech.withSourceHandlers = function (_Tech) {
10909
10910    /**
10911     * Register a source handler
10912     *
10913     * @param {Function} handler
10914     *        The source handler class
10915     *
10916     * @param {number} [index]
10917     *        Register it at the following index
10918     */
10919    _Tech.registerSourceHandler = function (handler, index) {
10920      var handlers = _Tech.sourceHandlers;
10921
10922      if (!handlers) {
10923        handlers = _Tech.sourceHandlers = [];
10924      }
10925
10926      if (index === undefined) {
10927        // add to the end of the list
10928        index = handlers.length;
10929      }
10930
10931      handlers.splice(index, 0, handler);
10932    };
10933
10934    /**
10935     * Check if the tech can support the given type. Also checks the
10936     * Techs sourceHandlers.
10937     *
10938     * @param {string} type
10939     *         The mimetype to check.
10940     *
10941     * @return {string}
10942     *         'probably', 'maybe', or '' (empty string)
10943     */
10944    _Tech.canPlayType = function (type) {
10945      var handlers = _Tech.sourceHandlers || [];
10946      var can = void 0;
10947
10948      for (var i = 0; i < handlers.length; i++) {
10949        can = handlers[i].canPlayType(type);
10950
10951        if (can) {
10952          return can;
10953        }
10954      }
10955
10956      return '';
10957    };
10958
10959    /**
10960     * Returns the first source handler that supports the source.
10961     *
10962     * TODO: Answer question: should 'probably' be prioritized over 'maybe'
10963     *
10964     * @param {Tech~SourceObject} source
10965     *        The source object
10966     *
10967     * @param {Object} options
10968     *        The options passed to the tech
10969     *
10970     * @return {SourceHandler|null}
10971     *          The first source handler that supports the source or null if
10972     *          no SourceHandler supports the source
10973     */
10974    _Tech.selectSourceHandler = function (source, options) {
10975      var handlers = _Tech.sourceHandlers || [];
10976      var can = void 0;
10977
10978      for (var i = 0; i < handlers.length; i++) {
10979        can = handlers[i].canHandleSource(source, options);
10980
10981        if (can) {
10982          return handlers[i];
10983        }
10984      }
10985
10986      return null;
10987    };
10988
10989    /**
10990     * Check if the tech can support the given source.
10991     *
10992     * @param {Tech~SourceObject} srcObj
10993     *        The source object
10994     *
10995     * @param {Object} options
10996     *        The options passed to the tech
10997     *
10998     * @return {string}
10999     *         'probably', 'maybe', or '' (empty string)
11000     */
11001    _Tech.canPlaySource = function (srcObj, options) {
11002      var sh = _Tech.selectSourceHandler(srcObj, options);
11003
11004      if (sh) {
11005        return sh.canHandleSource(srcObj, options);
11006      }
11007
11008      return '';
11009    };
11010
11011    /**
11012     * When using a source handler, prefer its implementation of
11013     * any function normally provided by the tech.
11014     */
11015    var deferrable = ['seekable', 'seeking', 'duration'];
11016
11017    /**
11018     * A wrapper around {@link Tech#seekable} that will call a `SourceHandler`s seekable
11019     * function if it exists, with a fallback to the Techs seekable function.
11020     *
11021     * @method _Tech.seekable
11022     */
11023
11024    /**
11025     * A wrapper around {@link Tech#duration} that will call a `SourceHandler`s duration
11026     * function if it exists, otherwise it will fallback to the techs duration function.
11027     *
11028     * @method _Tech.duration
11029     */
11030
11031    deferrable.forEach(function (fnName) {
11032      var originalFn = this[fnName];
11033
11034      if (typeof originalFn !== 'function') {
11035        return;
11036      }
11037
11038      this[fnName] = function () {
11039        if (this.sourceHandler_ && this.sourceHandler_[fnName]) {
11040          return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments);
11041        }
11042        return originalFn.apply(this, arguments);
11043      };
11044    }, _Tech.prototype);
11045
11046    /**
11047     * Create a function for setting the source using a source object
11048     * and source handlers.
11049     * Should never be called unless a source handler was found.
11050     *
11051     * @param {Tech~SourceObject} source
11052     *        A source object with src and type keys
11053     */
11054    _Tech.prototype.setSource = function (source) {
11055      var sh = _Tech.selectSourceHandler(source, this.options_);
11056
11057      if (!sh) {
11058        // Fall back to a native source hander when unsupported sources are
11059        // deliberately set
11060        if (_Tech.nativeSourceHandler) {
11061          sh = _Tech.nativeSourceHandler;
11062        } else {
11063          log$1.error('No source handler found for the current source.');
11064        }
11065      }
11066
11067      // Dispose any existing source handler
11068      this.disposeSourceHandler();
11069      this.off('dispose', this.disposeSourceHandler);
11070
11071      if (sh !== _Tech.nativeSourceHandler) {
11072        this.currentSource_ = source;
11073      }
11074
11075      this.sourceHandler_ = sh.handleSource(source, this, this.options_);
11076      this.on('dispose', this.disposeSourceHandler);
11077    };
11078
11079    /**
11080     * Clean up any existing SourceHandlers and listeners when the Tech is disposed.
11081     *
11082     * @listens Tech#dispose
11083     */
11084    _Tech.prototype.disposeSourceHandler = function () {
11085      // if we have a source and get another one
11086      // then we are loading something new
11087      // than clear all of our current tracks
11088      if (this.currentSource_) {
11089        this.clearTracks(['audio', 'video']);
11090        this.currentSource_ = null;
11091      }
11092
11093      // always clean up auto-text tracks
11094      this.cleanupAutoTextTracks();
11095
11096      if (this.sourceHandler_) {
11097
11098        if (this.sourceHandler_.dispose) {
11099          this.sourceHandler_.dispose();
11100        }
11101
11102        this.sourceHandler_ = null;
11103      }
11104    };
11105  };
11106
11107  // The base Tech class needs to be registered as a Component. It is the only
11108  // Tech that can be registered as a Component.
11109  Component.registerComponent('Tech', Tech);
11110  Tech.registerTech('Tech', Tech);
11111
11112  /**
11113   * A list of techs that should be added to techOrder on Players
11114   *
11115   * @private
11116   */
11117  Tech.defaultTechOrder_ = [];
11118
11119  var middlewares = {};
11120  var middlewareInstances = {};
11121
11122  var TERMINATOR = {};
11123
11124  function use(type, middleware) {
11125    middlewares[type] = middlewares[type] || [];
11126    middlewares[type].push(middleware);
11127  }
11128
11129  function setSource(player, src, next) {
11130    player.setTimeout(function () {
11131      return setSourceHelper(src, middlewares[src.type], next, player);
11132    }, 1);
11133  }
11134
11135  function setTech(middleware, tech) {
11136    middleware.forEach(function (mw) {
11137      return mw.setTech && mw.setTech(tech);
11138    });
11139  }
11140
11141  /**
11142   * Calls a getter on the tech first, through each middleware
11143   * from right to left to the player.
11144   */
11145  function get$1(middleware, tech, method) {
11146    return middleware.reduceRight(middlewareIterator(method), tech[method]());
11147  }
11148
11149  /**
11150   * Takes the argument given to the player and calls the setter method on each
11151   * middleware from left to right to the tech.
11152   */
11153  function set$1(middleware, tech, method, arg) {
11154    return tech[method](middleware.reduce(middlewareIterator(method), arg));
11155  }
11156
11157  /**
11158   * Takes the argument given to the player and calls the `call` version of the method
11159   * on each middleware from left to right.
11160   * Then, call the passed in method on the tech and return the result unchanged
11161   * back to the player, through middleware, this time from right to left.
11162   */
11163  function mediate(middleware, tech, method) {
11164    var arg = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
11165
11166    var callMethod = 'call' + toTitleCase(method);
11167    var middlewareValue = middleware.reduce(middlewareIterator(callMethod), arg);
11168    var terminated = middlewareValue === TERMINATOR;
11169    var returnValue = terminated ? null : tech[method](middlewareValue);
11170
11171    executeRight(middleware, method, returnValue, terminated);
11172
11173    return returnValue;
11174  }
11175
11176  var allowedGetters = {
11177    buffered: 1,
11178    currentTime: 1,
11179    duration: 1,
11180    seekable: 1,
11181    played: 1,
11182    paused: 1
11183  };
11184
11185  var allowedSetters = {
11186    setCurrentTime: 1
11187  };
11188
11189  var allowedMediators = {
11190    play: 1,
11191    pause: 1
11192  };
11193
11194  function middlewareIterator(method) {
11195    return function (value, mw) {
11196      // if the previous middleware terminated, pass along the termination
11197      if (value === TERMINATOR) {
11198        return TERMINATOR;
11199      }
11200
11201      if (mw[method]) {
11202        return mw[method](value);
11203      }
11204
11205      return value;
11206    };
11207  }
11208
11209  function executeRight(mws, method, value, terminated) {
11210    for (var i = mws.length - 1; i >= 0; i--) {
11211      var mw = mws[i];
11212
11213      if (mw[method]) {
11214        mw[method](terminated, value);
11215      }
11216    }
11217  }
11218
11219  function clearCacheForPlayer(player) {
11220    middlewareInstances[player.id()] = null;
11221  }
11222
11223  /**
11224   * {
11225   *  [playerId]: [[mwFactory, mwInstance], ...]
11226   * }
11227   */
11228  function getOrCreateFactory(player, mwFactory) {
11229    var mws = middlewareInstances[player.id()];
11230    var mw = null;
11231
11232    if (mws === undefined || mws === null) {
11233      mw = mwFactory(player);
11234      middlewareInstances[player.id()] = [[mwFactory, mw]];
11235      return mw;
11236    }
11237
11238    for (var i = 0; i < mws.length; i++) {
11239      var _mws$i = mws[i],
11240          mwf = _mws$i[0],
11241          mwi = _mws$i[1];
11242
11243
11244      if (mwf !== mwFactory) {
11245        continue;
11246      }
11247
11248      mw = mwi;
11249    }
11250
11251    if (mw === null) {
11252      mw = mwFactory(player);
11253      mws.push([mwFactory, mw]);
11254    }
11255
11256    return mw;
11257  }
11258
11259  function setSourceHelper() {
11260    var src = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
11261    var middleware = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
11262    var next = arguments[2];
11263    var player = arguments[3];
11264    var acc = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : [];
11265    var lastRun = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false;
11266    var mwFactory = middleware[0],
11267        mwrest = middleware.slice(1);
11268
11269    // if mwFactory is a string, then we're at a fork in the road
11270
11271    if (typeof mwFactory === 'string') {
11272      setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun);
11273
11274      // if we have an mwFactory, call it with the player to get the mw,
11275      // then call the mw's setSource method
11276    } else if (mwFactory) {
11277      var mw = getOrCreateFactory(player, mwFactory);
11278
11279      // if setSource isn't present, implicitly select this middleware
11280      if (!mw.setSource) {
11281        acc.push(mw);
11282        return setSourceHelper(src, mwrest, next, player, acc, lastRun);
11283      }
11284
11285      mw.setSource(assign({}, src), function (err, _src) {
11286
11287        // something happened, try the next middleware on the current level
11288        // make sure to use the old src
11289        if (err) {
11290          return setSourceHelper(src, mwrest, next, player, acc, lastRun);
11291        }
11292
11293        // we've succeeded, now we need to go deeper
11294        acc.push(mw);
11295
11296        // if it's the same type, continue down the current chain
11297        // otherwise, we want to go down the new chain
11298        setSourceHelper(_src, src.type === _src.type ? mwrest : middlewares[_src.type], next, player, acc, lastRun);
11299      });
11300    } else if (mwrest.length) {
11301      setSourceHelper(src, mwrest, next, player, acc, lastRun);
11302    } else if (lastRun) {
11303      next(src, acc);
11304    } else {
11305      setSourceHelper(src, middlewares['*'], next, player, acc, true);
11306    }
11307  }
11308
11309  /**
11310   * Mimetypes
11311   *
11312   * @see http://hul.harvard.edu/ois/////systems/wax/wax-public-help/mimetypes.htm
11313   * @typedef Mimetypes~Kind
11314   * @enum
11315   */
11316  var MimetypesKind = {
11317    opus: 'video/ogg',
11318    ogv: 'video/ogg',
11319    mp4: 'video/mp4',
11320    mov: 'video/mp4',
11321    m4v: 'video/mp4',
11322    mkv: 'video/x-matroska',
11323    mp3: 'audio/mpeg',
11324    aac: 'audio/aac',
11325    oga: 'audio/ogg',
11326    m3u8: 'application/x-mpegURL'
11327  };
11328
11329  /**
11330   * Get the mimetype of a given src url if possible
11331   *
11332   * @param {string} src
11333   *        The url to the src
11334   *
11335   * @return {string}
11336   *         return the mimetype if it was known or empty string otherwise
11337   */
11338  var getMimetype = function getMimetype() {
11339    var src = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
11340
11341    var ext = getFileExtension(src);
11342    var mimetype = MimetypesKind[ext.toLowerCase()];
11343
11344    return mimetype || '';
11345  };
11346
11347  /**
11348   * Find the mime type of a given source string if possible. Uses the player
11349   * source cache.
11350   *
11351   * @param {Player} player
11352   *        The player object
11353   *
11354   * @param {string} src
11355   *        The source string
11356   *
11357   * @return {string}
11358   *         The type that was found
11359   */
11360  var findMimetype = function findMimetype(player, src) {
11361    if (!src) {
11362      return '';
11363    }
11364
11365    // 1. check for the type in the `source` cache
11366    if (player.cache_.source.src === src && player.cache_.source.type) {
11367      return player.cache_.source.type;
11368    }
11369
11370    // 2. see if we have this source in our `currentSources` cache
11371    var matchingSources = player.cache_.sources.filter(function (s) {
11372      return s.src === src;
11373    });
11374
11375    if (matchingSources.length) {
11376      return matchingSources[0].type;
11377    }
11378
11379    // 3. look for the src url in source elements and use the type there
11380    var sources = player.$$('source');
11381
11382    for (var i = 0; i < sources.length; i++) {
11383      var s = sources[i];
11384
11385      if (s.type && s.src && s.src === src) {
11386        return s.type;
11387      }
11388    }
11389
11390    // 4. finally fallback to our list of mime types based on src url extension
11391    return getMimetype(src);
11392  };
11393
11394  /**
11395   * @module filter-source
11396   */
11397
11398  /**
11399   * Filter out single bad source objects or multiple source objects in an
11400   * array. Also flattens nested source object arrays into a 1 dimensional
11401   * array of source objects.
11402   *
11403   * @param {Tech~SourceObject|Tech~SourceObject[]} src
11404   *        The src object to filter
11405   *
11406   * @return {Tech~SourceObject[]}
11407   *         An array of sourceobjects containing only valid sources
11408   *
11409   * @private
11410   */
11411  var filterSource = function filterSource(src) {
11412    // traverse array
11413    if (Array.isArray(src)) {
11414      var newsrc = [];
11415
11416      src.forEach(function (srcobj) {
11417        srcobj = filterSource(srcobj);
11418
11419        if (Array.isArray(srcobj)) {
11420          newsrc = newsrc.concat(srcobj);
11421        } else if (isObject(srcobj)) {
11422          newsrc.push(srcobj);
11423        }
11424      });
11425
11426      src = newsrc;
11427    } else if (typeof src === 'string' && src.trim()) {
11428      // convert string into object
11429      src = [fixSource({ src: src })];
11430    } else if (isObject(src) && typeof src.src === 'string' && src.src && src.src.trim()) {
11431      // src is already valid
11432      src = [fixSource(src)];
11433    } else {
11434      // invalid source, turn it into an empty array
11435      src = [];
11436    }
11437
11438    return src;
11439  };
11440
11441  /**
11442   * Checks src mimetype, adding it when possible
11443   *
11444   * @param {Tech~SourceObject} src
11445   *        The src object to check
11446   * @return {Tech~SourceObject}
11447   *        src Object with known type
11448   */
11449  function fixSource(src) {
11450    var mimetype = getMimetype(src.src);
11451
11452    if (!src.type && mimetype) {
11453      src.type = mimetype;
11454    }
11455
11456    return src;
11457  }
11458
11459  /**
11460   * @file loader.js
11461   */
11462
11463  /**
11464   * The `MediaLoader` is the `Component` that decides which playback technology to load
11465   * when a player is initialized.
11466   *
11467   * @extends Component
11468   */
11469
11470  var MediaLoader = function (_Component) {
11471    inherits(MediaLoader, _Component);
11472
11473    /**
11474     * Create an instance of this class.
11475     *
11476     * @param {Player} player
11477     *        The `Player` that this class should attach to.
11478     *
11479     * @param {Object} [options]
11480     *        The key/value store of player options.
11481     *
11482     * @param {Component~ReadyCallback} [ready]
11483     *        The function that is run when this component is ready.
11484     */
11485    function MediaLoader(player, options, ready) {
11486      classCallCheck(this, MediaLoader);
11487
11488      // MediaLoader has no element
11489      var options_ = mergeOptions({ createEl: false }, options);
11490
11491      // If there are no sources when the player is initialized,
11492      // load the first supported playback technology.
11493
11494      var _this = possibleConstructorReturn(this, _Component.call(this, player, options_, ready));
11495
11496      if (!options.playerOptions.sources || options.playerOptions.sources.length === 0) {
11497        for (var i = 0, j = options.playerOptions.techOrder; i < j.length; i++) {
11498          var techName = toTitleCase(j[i]);
11499          var tech = Tech.getTech(techName);
11500
11501          // Support old behavior of techs being registered as components.
11502          // Remove once that deprecated behavior is removed.
11503          if (!techName) {
11504            tech = Component.getComponent(techName);
11505          }
11506
11507          // Check if the browser supports this technology
11508          if (tech && tech.isSupported()) {
11509            player.loadTech_(techName);
11510            break;
11511          }
11512        }
11513      } else {
11514        // Loop through playback technologies (HTML5, Flash) and check for support.
11515        // Then load the best source.
11516        // A few assumptions here:
11517        //   All playback technologies respect preload false.
11518        player.src(options.playerOptions.sources);
11519      }
11520      return _this;
11521    }
11522
11523    return MediaLoader;
11524  }(Component);
11525
11526  Component.registerComponent('MediaLoader', MediaLoader);
11527
11528  /**
11529   * @file clickable-component.js
11530   */
11531
11532  /**
11533   * Clickable Component which is clickable or keyboard actionable,
11534   * but is not a native HTML button.
11535   *
11536   * @extends Component
11537   */
11538
11539  var ClickableComponent = function (_Component) {
11540    inherits(ClickableComponent, _Component);
11541
11542    /**
11543     * Creates an instance of this class.
11544     *
11545     * @param  {Player} player
11546     *         The `Player` that this class should be attached to.
11547     *
11548     * @param  {Object} [options]
11549     *         The key/value store of player options.
11550     */
11551    function ClickableComponent(player, options) {
11552      classCallCheck(this, ClickableComponent);
11553
11554      var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
11555
11556      _this.emitTapEvents();
11557
11558      _this.enable();
11559      return _this;
11560    }
11561
11562    /**
11563     * Create the `Component`s DOM element.
11564     *
11565     * @param {string} [tag=div]
11566     *        The element's node type.
11567     *
11568     * @param {Object} [props={}]
11569     *        An object of properties that should be set on the element.
11570     *
11571     * @param {Object} [attributes={}]
11572     *        An object of attributes that should be set on the element.
11573     *
11574     * @return {Element}
11575     *         The element that gets created.
11576     */
11577
11578
11579    ClickableComponent.prototype.createEl = function createEl$$1() {
11580      var tag = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'div';
11581      var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
11582      var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
11583
11584      props = assign({
11585        innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>',
11586        className: this.buildCSSClass(),
11587        tabIndex: 0
11588      }, props);
11589
11590      if (tag === 'button') {
11591        log$1.error('Creating a ClickableComponent with an HTML element of ' + tag + ' is not supported; use a Button instead.');
11592      }
11593
11594      // Add ARIA attributes for clickable element which is not a native HTML button
11595      attributes = assign({
11596        role: 'button'
11597      }, attributes);
11598
11599      this.tabIndex_ = props.tabIndex;
11600
11601      var el = _Component.prototype.createEl.call(this, tag, props, attributes);
11602
11603      this.createControlTextEl(el);
11604
11605      return el;
11606    };
11607
11608    ClickableComponent.prototype.dispose = function dispose() {
11609      // remove controlTextEl_ on dispose
11610      this.controlTextEl_ = null;
11611
11612      _Component.prototype.dispose.call(this);
11613    };
11614
11615    /**
11616     * Create a control text element on this `Component`
11617     *
11618     * @param {Element} [el]
11619     *        Parent element for the control text.
11620     *
11621     * @return {Element}
11622     *         The control text element that gets created.
11623     */
11624
11625
11626    ClickableComponent.prototype.createControlTextEl = function createControlTextEl(el) {
11627      this.controlTextEl_ = createEl('span', {
11628        className: 'vjs-control-text'
11629      }, {
11630        // let the screen reader user know that the text of the element may change
11631        'aria-live': 'polite'
11632      });
11633
11634      if (el) {
11635        el.appendChild(this.controlTextEl_);
11636      }
11637
11638      this.controlText(this.controlText_, el);
11639
11640      return this.controlTextEl_;
11641    };
11642
11643    /**
11644     * Get or set the localize text to use for the controls on the `Component`.
11645     *
11646     * @param {string} [text]
11647     *        Control text for element.
11648     *
11649     * @param {Element} [el=this.el()]
11650     *        Element to set the title on.
11651     *
11652     * @return {string}
11653     *         - The control text when getting
11654     */
11655
11656
11657    ClickableComponent.prototype.controlText = function controlText(text) {
11658      var el = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.el();
11659
11660      if (text === undefined) {
11661        return this.controlText_ || 'Need Text';
11662      }
11663
11664      var localizedText = this.localize(text);
11665
11666      this.controlText_ = text;
11667      textContent(this.controlTextEl_, localizedText);
11668      if (!this.nonIconControl) {
11669        // Set title attribute if only an icon is shown
11670        el.setAttribute('title', localizedText);
11671      }
11672    };
11673
11674    /**
11675     * Builds the default DOM `className`.
11676     *
11677     * @return {string}
11678     *         The DOM `className` for this object.
11679     */
11680
11681
11682    ClickableComponent.prototype.buildCSSClass = function buildCSSClass() {
11683      return 'vjs-control vjs-button ' + _Component.prototype.buildCSSClass.call(this);
11684    };
11685
11686    /**
11687     * Enable this `Component`s element.
11688     */
11689
11690
11691    ClickableComponent.prototype.enable = function enable() {
11692      if (!this.enabled_) {
11693        this.enabled_ = true;
11694        this.removeClass('vjs-disabled');
11695        this.el_.setAttribute('aria-disabled', 'false');
11696        if (typeof this.tabIndex_ !== 'undefined') {
11697          this.el_.setAttribute('tabIndex', this.tabIndex_);
11698        }
11699        this.on(['tap', 'click'], this.handleClick);
11700        this.on('focus', this.handleFocus);
11701        this.on('blur', this.handleBlur);
11702      }
11703    };
11704
11705    /**
11706     * Disable this `Component`s element.
11707     */
11708
11709
11710    ClickableComponent.prototype.disable = function disable() {
11711      this.enabled_ = false;
11712      this.addClass('vjs-disabled');
11713      this.el_.setAttribute('aria-disabled', 'true');
11714      if (typeof this.tabIndex_ !== 'undefined') {
11715        this.el_.removeAttribute('tabIndex');
11716      }
11717      this.off(['tap', 'click'], this.handleClick);
11718      this.off('focus', this.handleFocus);
11719      this.off('blur', this.handleBlur);
11720    };
11721
11722    /**
11723     * This gets called when a `ClickableComponent` gets:
11724     * - Clicked (via the `click` event, listening starts in the constructor)
11725     * - Tapped (via the `tap` event, listening starts in the constructor)
11726     * - The following things happen in order:
11727     *   1. {@link ClickableComponent#handleFocus} is called via a `focus` event on the
11728     *      `ClickableComponent`.
11729     *   2. {@link ClickableComponent#handleFocus} adds a listener for `keydown` on using
11730     *      {@link ClickableComponent#handleKeyPress}.
11731     *   3. `ClickableComponent` has not had a `blur` event (`blur` means that focus was lost). The user presses
11732     *      the space or enter key.
11733     *   4. {@link ClickableComponent#handleKeyPress} calls this function with the `keydown`
11734     *      event as a parameter.
11735     *
11736     * @param {EventTarget~Event} event
11737     *        The `keydown`, `tap`, or `click` event that caused this function to be
11738     *        called.
11739     *
11740     * @listens tap
11741     * @listens click
11742     * @abstract
11743     */
11744
11745
11746    ClickableComponent.prototype.handleClick = function handleClick(event) {};
11747
11748    /**
11749     * This gets called when a `ClickableComponent` gains focus via a `focus` event.
11750     * Turns on listening for `keydown` events. When they happen it
11751     * calls `this.handleKeyPress`.
11752     *
11753     * @param {EventTarget~Event} event
11754     *        The `focus` event that caused this function to be called.
11755     *
11756     * @listens focus
11757     */
11758
11759
11760    ClickableComponent.prototype.handleFocus = function handleFocus(event) {
11761      on(document_1, 'keydown', bind(this, this.handleKeyPress));
11762    };
11763
11764    /**
11765     * Called when this ClickableComponent has focus and a key gets pressed down. By
11766     * default it will call `this.handleClick` when the key is space or enter.
11767     *
11768     * @param {EventTarget~Event} event
11769     *        The `keydown` event that caused this function to be called.
11770     *
11771     * @listens keydown
11772     */
11773
11774
11775    ClickableComponent.prototype.handleKeyPress = function handleKeyPress(event) {
11776
11777      // Support Space (32) or Enter (13) key operation to fire a click event
11778      if (event.which === 32 || event.which === 13) {
11779        event.preventDefault();
11780        this.trigger('click');
11781      } else if (_Component.prototype.handleKeyPress) {
11782
11783        // Pass keypress handling up for unsupported keys
11784        _Component.prototype.handleKeyPress.call(this, event);
11785      }
11786    };
11787
11788    /**
11789     * Called when a `ClickableComponent` loses focus. Turns off the listener for
11790     * `keydown` events. Which Stops `this.handleKeyPress` from getting called.
11791     *
11792     * @param {EventTarget~Event} event
11793     *        The `blur` event that caused this function to be called.
11794     *
11795     * @listens blur
11796     */
11797
11798
11799    ClickableComponent.prototype.handleBlur = function handleBlur(event) {
11800      off(document_1, 'keydown', bind(this, this.handleKeyPress));
11801    };
11802
11803    return ClickableComponent;
11804  }(Component);
11805
11806  Component.registerComponent('ClickableComponent', ClickableComponent);
11807
11808  /**
11809   * @file poster-image.js
11810   */
11811
11812  /**
11813   * A `ClickableComponent` that handles showing the poster image for the player.
11814   *
11815   * @extends ClickableComponent
11816   */
11817
11818  var PosterImage = function (_ClickableComponent) {
11819    inherits(PosterImage, _ClickableComponent);
11820
11821    /**
11822     * Create an instance of this class.
11823     *
11824     * @param {Player} player
11825     *        The `Player` that this class should attach to.
11826     *
11827     * @param {Object} [options]
11828     *        The key/value store of player options.
11829     */
11830    function PosterImage(player, options) {
11831      classCallCheck(this, PosterImage);
11832
11833      var _this = possibleConstructorReturn(this, _ClickableComponent.call(this, player, options));
11834
11835      _this.update();
11836      player.on('posterchange', bind(_this, _this.update));
11837      return _this;
11838    }
11839
11840    /**
11841     * Clean up and dispose of the `PosterImage`.
11842     */
11843
11844
11845    PosterImage.prototype.dispose = function dispose() {
11846      this.player().off('posterchange', this.update);
11847      _ClickableComponent.prototype.dispose.call(this);
11848    };
11849
11850    /**
11851     * Create the `PosterImage`s DOM element.
11852     *
11853     * @return {Element}
11854     *         The element that gets created.
11855     */
11856
11857
11858    PosterImage.prototype.createEl = function createEl$$1() {
11859      var el = createEl('div', {
11860        className: 'vjs-poster',
11861
11862        // Don't want poster to be tabbable.
11863        tabIndex: -1
11864      });
11865
11866      return el;
11867    };
11868
11869    /**
11870     * An {@link EventTarget~EventListener} for {@link Player#posterchange} events.
11871     *
11872     * @listens Player#posterchange
11873     *
11874     * @param {EventTarget~Event} [event]
11875     *        The `Player#posterchange` event that triggered this function.
11876     */
11877
11878
11879    PosterImage.prototype.update = function update(event) {
11880      var url = this.player().poster();
11881
11882      this.setSrc(url);
11883
11884      // If there's no poster source we should display:none on this component
11885      // so it's not still clickable or right-clickable
11886      if (url) {
11887        this.show();
11888      } else {
11889        this.hide();
11890      }
11891    };
11892
11893    /**
11894     * Set the source of the `PosterImage` depending on the display method.
11895     *
11896     * @param {string} url
11897     *        The URL to the source for the `PosterImage`.
11898     */
11899
11900
11901    PosterImage.prototype.setSrc = function setSrc(url) {
11902      var backgroundImage = '';
11903
11904      // Any falsy value should stay as an empty string, otherwise
11905      // this will throw an extra error
11906      if (url) {
11907        backgroundImage = 'url("' + url + '")';
11908      }
11909
11910      this.el_.style.backgroundImage = backgroundImage;
11911    };
11912
11913    /**
11914     * An {@link EventTarget~EventListener} for clicks on the `PosterImage`. See
11915     * {@link ClickableComponent#handleClick} for instances where this will be triggered.
11916     *
11917     * @listens tap
11918     * @listens click
11919     * @listens keydown
11920     *
11921     * @param {EventTarget~Event} event
11922     +        The `click`, `tap` or `keydown` event that caused this function to be called.
11923     */
11924
11925
11926    PosterImage.prototype.handleClick = function handleClick(event) {
11927      // We don't want a click to trigger playback when controls are disabled
11928      if (!this.player_.controls()) {
11929        return;
11930      }
11931
11932      if (this.player_.paused()) {
11933        silencePromise(this.player_.play());
11934      } else {
11935        this.player_.pause();
11936      }
11937    };
11938
11939    return PosterImage;
11940  }(ClickableComponent);
11941
11942  Component.registerComponent('PosterImage', PosterImage);
11943
11944  /**
11945   * @file text-track-display.js
11946   */
11947
11948  var darkGray = '#222';
11949  var lightGray = '#ccc';
11950  var fontMap = {
11951    monospace: 'monospace',
11952    sansSerif: 'sans-serif',
11953    serif: 'serif',
11954    monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace',
11955    monospaceSerif: '"Courier New", monospace',
11956    proportionalSansSerif: 'sans-serif',
11957    proportionalSerif: 'serif',
11958    casual: '"Comic Sans MS", Impact, fantasy',
11959    script: '"Monotype Corsiva", cursive',
11960    smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif'
11961  };
11962
11963  /**
11964   * Construct an rgba color from a given hex color code.
11965   *
11966   * @param {number} color
11967   *        Hex number for color, like #f0e or #f604e2.
11968   *
11969   * @param {number} opacity
11970   *        Value for opacity, 0.0 - 1.0.
11971   *
11972   * @return {string}
11973   *         The rgba color that was created, like 'rgba(255, 0, 0, 0.3)'.
11974   */
11975  function constructColor(color, opacity) {
11976    var hex = void 0;
11977
11978    if (color.length === 4) {
11979      // color looks like "#f0e"
11980      hex = color[1] + color[1] + color[2] + color[2] + color[3] + color[3];
11981    } else if (color.length === 7) {
11982      // color looks like "#f604e2"
11983      hex = color.slice(1);
11984    } else {
11985      throw new Error('Invalid color code provided, ' + color + '; must be formatted as e.g. #f0e or #f604e2.');
11986    }
11987    return 'rgba(' + parseInt(hex.slice(0, 2), 16) + ',' + parseInt(hex.slice(2, 4), 16) + ',' + parseInt(hex.slice(4, 6), 16) + ',' + opacity + ')';
11988  }
11989
11990  /**
11991   * Try to update the style of a DOM element. Some style changes will throw an error,
11992   * particularly in IE8. Those should be noops.
11993   *
11994   * @param {Element} el
11995   *        The DOM element to be styled.
11996   *
11997   * @param {string} style
11998   *        The CSS property on the element that should be styled.
11999   *
12000   * @param {string} rule
12001   *        The style rule that should be applied to the property.
12002   *
12003   * @private
12004   */
12005  function tryUpdateStyle(el, style, rule) {
12006    try {
12007      el.style[style] = rule;
12008    } catch (e) {
12009
12010      // Satisfies linter.
12011      return;
12012    }
12013  }
12014
12015  /**
12016   * The component for displaying text track cues.
12017   *
12018   * @extends Component
12019   */
12020
12021  var TextTrackDisplay = function (_Component) {
12022    inherits(TextTrackDisplay, _Component);
12023
12024    /**
12025     * Creates an instance of this class.
12026     *
12027     * @param {Player} player
12028     *        The `Player` that this class should be attached to.
12029     *
12030     * @param {Object} [options]
12031     *        The key/value store of player options.
12032     *
12033     * @param {Component~ReadyCallback} [ready]
12034     *        The function to call when `TextTrackDisplay` is ready.
12035     */
12036    function TextTrackDisplay(player, options, ready) {
12037      classCallCheck(this, TextTrackDisplay);
12038
12039      var _this = possibleConstructorReturn(this, _Component.call(this, player, options, ready));
12040
12041      player.on('loadstart', bind(_this, _this.toggleDisplay));
12042      player.on('texttrackchange', bind(_this, _this.updateDisplay));
12043      player.on('loadstart', bind(_this, _this.preselectTrack));
12044
12045      // This used to be called during player init, but was causing an error
12046      // if a track should show by default and the display hadn't loaded yet.
12047      // Should probably be moved to an external track loader when we support
12048      // tracks that don't need a display.
12049      player.ready(bind(_this, function () {
12050        if (player.tech_ && player.tech_.featuresNativeTextTracks) {
12051          this.hide();
12052          return;
12053        }
12054
12055        player.on('fullscreenchange', bind(this, this.updateDisplay));
12056
12057        var tracks = this.options_.playerOptions.tracks || [];
12058
12059        for (var i = 0; i < tracks.length; i++) {
12060          this.player_.addRemoteTextTrack(tracks[i], true);
12061        }
12062
12063        this.preselectTrack();
12064      }));
12065      return _this;
12066    }
12067
12068    /**
12069    * Preselect a track following this precedence:
12070    * - matches the previously selected {@link TextTrack}'s language and kind
12071    * - matches the previously selected {@link TextTrack}'s language only
12072    * - is the first default captions track
12073    * - is the first default descriptions track
12074    *
12075    * @listens Player#loadstart
12076    */
12077
12078
12079    TextTrackDisplay.prototype.preselectTrack = function preselectTrack() {
12080      var modes = { captions: 1, subtitles: 1 };
12081      var trackList = this.player_.textTracks();
12082      var userPref = this.player_.cache_.selectedLanguage;
12083      var firstDesc = void 0;
12084      var firstCaptions = void 0;
12085      var preferredTrack = void 0;
12086
12087      for (var i = 0; i < trackList.length; i++) {
12088        var track = trackList[i];
12089
12090        if (userPref && userPref.enabled && userPref.language === track.language) {
12091          // Always choose the track that matches both language and kind
12092          if (track.kind === userPref.kind) {
12093            preferredTrack = track;
12094            // or choose the first track that matches language
12095          } else if (!preferredTrack) {
12096            preferredTrack = track;
12097          }
12098
12099          // clear everything if offTextTrackMenuItem was clicked
12100        } else if (userPref && !userPref.enabled) {
12101          preferredTrack = null;
12102          firstDesc = null;
12103          firstCaptions = null;
12104        } else if (track.default) {
12105          if (track.kind === 'descriptions' && !firstDesc) {
12106            firstDesc = track;
12107          } else if (track.kind in modes && !firstCaptions) {
12108            firstCaptions = track;
12109          }
12110        }
12111      }
12112
12113      // The preferredTrack matches the user preference and takes
12114      // precedence over all the other tracks.
12115      // So, display the preferredTrack before the first default track
12116      // and the subtitles/captions track before the descriptions track
12117      if (preferredTrack) {
12118        preferredTrack.mode = 'showing';
12119      } else if (firstCaptions) {
12120        firstCaptions.mode = 'showing';
12121      } else if (firstDesc) {
12122        firstDesc.mode = 'showing';
12123      }
12124    };
12125
12126    /**
12127     * Turn display of {@link TextTrack}'s from the current state into the other state.
12128     * There are only two states:
12129     * - 'shown'
12130     * - 'hidden'
12131     *
12132     * @listens Player#loadstart
12133     */
12134
12135
12136    TextTrackDisplay.prototype.toggleDisplay = function toggleDisplay() {
12137      if (this.player_.tech_ && this.player_.tech_.featuresNativeTextTracks) {
12138        this.hide();
12139      } else {
12140        this.show();
12141      }
12142    };
12143
12144    /**
12145     * Create the {@link Component}'s DOM element.
12146     *
12147     * @return {Element}
12148     *         The element that was created.
12149     */
12150
12151
12152    TextTrackDisplay.prototype.createEl = function createEl() {
12153      return _Component.prototype.createEl.call(this, 'div', {
12154        className: 'vjs-text-track-display'
12155      }, {
12156        'aria-live': 'off',
12157        'aria-atomic': 'true'
12158      });
12159    };
12160
12161    /**
12162     * Clear all displayed {@link TextTrack}s.
12163     */
12164
12165
12166    TextTrackDisplay.prototype.clearDisplay = function clearDisplay() {
12167      if (typeof window_1.WebVTT === 'function') {
12168        window_1.WebVTT.processCues(window_1, [], this.el_);
12169      }
12170    };
12171
12172    /**
12173     * Update the displayed TextTrack when a either a {@link Player#texttrackchange} or
12174     * a {@link Player#fullscreenchange} is fired.
12175     *
12176     * @listens Player#texttrackchange
12177     * @listens Player#fullscreenchange
12178     */
12179
12180
12181    TextTrackDisplay.prototype.updateDisplay = function updateDisplay() {
12182      var tracks = this.player_.textTracks();
12183
12184      this.clearDisplay();
12185
12186      // Track display prioritization model: if multiple tracks are 'showing',
12187      //  display the first 'subtitles' or 'captions' track which is 'showing',
12188      //  otherwise display the first 'descriptions' track which is 'showing'
12189
12190      var descriptionsTrack = null;
12191      var captionsSubtitlesTrack = null;
12192      var i = tracks.length;
12193
12194      while (i--) {
12195        var track = tracks[i];
12196
12197        if (track.mode === 'showing') {
12198          if (track.kind === 'descriptions') {
12199            descriptionsTrack = track;
12200          } else {
12201            captionsSubtitlesTrack = track;
12202          }
12203        }
12204      }
12205
12206      if (captionsSubtitlesTrack) {
12207        if (this.getAttribute('aria-live') !== 'off') {
12208          this.setAttribute('aria-live', 'off');
12209        }
12210        this.updateForTrack(captionsSubtitlesTrack);
12211      } else if (descriptionsTrack) {
12212        if (this.getAttribute('aria-live') !== 'assertive') {
12213          this.setAttribute('aria-live', 'assertive');
12214        }
12215        this.updateForTrack(descriptionsTrack);
12216      }
12217    };
12218
12219    /**
12220     * Add an {@link TextTrack} to to the {@link Tech}s {@link TextTrackList}.
12221     *
12222     * @param {TextTrack} track
12223     *        Text track object to be added to the list.
12224     */
12225
12226
12227    TextTrackDisplay.prototype.updateForTrack = function updateForTrack(track) {
12228      if (typeof window_1.WebVTT !== 'function' || !track.activeCues) {
12229        return;
12230      }
12231
12232      var cues = [];
12233
12234      for (var _i = 0; _i < track.activeCues.length; _i++) {
12235        cues.push(track.activeCues[_i]);
12236      }
12237
12238      window_1.WebVTT.processCues(window_1, cues, this.el_);
12239
12240      if (!this.player_.textTrackSettings) {
12241        return;
12242      }
12243
12244      var overrides = this.player_.textTrackSettings.getValues();
12245
12246      var i = cues.length;
12247
12248      while (i--) {
12249        var cue = cues[i];
12250
12251        if (!cue) {
12252          continue;
12253        }
12254
12255        var cueDiv = cue.displayState;
12256
12257        if (overrides.color) {
12258          cueDiv.firstChild.style.color = overrides.color;
12259        }
12260        if (overrides.textOpacity) {
12261          tryUpdateStyle(cueDiv.firstChild, 'color', constructColor(overrides.color || '#fff', overrides.textOpacity));
12262        }
12263        if (overrides.backgroundColor) {
12264          cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor;
12265        }
12266        if (overrides.backgroundOpacity) {
12267          tryUpdateStyle(cueDiv.firstChild, 'backgroundColor', constructColor(overrides.backgroundColor || '#000', overrides.backgroundOpacity));
12268        }
12269        if (overrides.windowColor) {
12270          if (overrides.windowOpacity) {
12271            tryUpdateStyle(cueDiv, 'backgroundColor', constructColor(overrides.windowColor, overrides.windowOpacity));
12272          } else {
12273            cueDiv.style.backgroundColor = overrides.windowColor;
12274          }
12275        }
12276        if (overrides.edgeStyle) {
12277          if (overrides.edgeStyle === 'dropshadow') {
12278            cueDiv.firstChild.style.textShadow = '2px 2px 3px ' + darkGray + ', 2px 2px 4px ' + darkGray + ', 2px 2px 5px ' + darkGray;
12279          } else if (overrides.edgeStyle === 'raised') {
12280            cueDiv.firstChild.style.textShadow = '1px 1px ' + darkGray + ', 2px 2px ' + darkGray + ', 3px 3px ' + darkGray;
12281          } else if (overrides.edgeStyle === 'depressed') {
12282            cueDiv.firstChild.style.textShadow = '1px 1px ' + lightGray + ', 0 1px ' + lightGray + ', -1px -1px ' + darkGray + ', 0 -1px ' + darkGray;
12283          } else if (overrides.edgeStyle === 'uniform') {
12284            cueDiv.firstChild.style.textShadow = '0 0 4px ' + darkGray + ', 0 0 4px ' + darkGray + ', 0 0 4px ' + darkGray + ', 0 0 4px ' + darkGray;
12285          }
12286        }
12287        if (overrides.fontPercent && overrides.fontPercent !== 1) {
12288          var fontSize = window_1.parseFloat(cueDiv.style.fontSize);
12289
12290          cueDiv.style.fontSize = fontSize * overrides.fontPercent + 'px';
12291          cueDiv.style.height = 'auto';
12292          cueDiv.style.top = 'auto';
12293          cueDiv.style.bottom = '2px';
12294        }
12295        if (overrides.fontFamily && overrides.fontFamily !== 'default') {
12296          if (overrides.fontFamily === 'small-caps') {
12297            cueDiv.firstChild.style.fontVariant = 'small-caps';
12298          } else {
12299            cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily];
12300          }
12301        }
12302      }
12303    };
12304
12305    return TextTrackDisplay;
12306  }(Component);
12307
12308  Component.registerComponent('TextTrackDisplay', TextTrackDisplay);
12309
12310  /**
12311   * @file loading-spinner.js
12312   */
12313
12314  /**
12315   * A loading spinner for use during waiting/loading events.
12316   *
12317   * @extends Component
12318   */
12319
12320  var LoadingSpinner = function (_Component) {
12321    inherits(LoadingSpinner, _Component);
12322
12323    function LoadingSpinner() {
12324      classCallCheck(this, LoadingSpinner);
12325      return possibleConstructorReturn(this, _Component.apply(this, arguments));
12326    }
12327
12328    /**
12329     * Create the `LoadingSpinner`s DOM element.
12330     *
12331     * @return {Element}
12332     *         The dom element that gets created.
12333     */
12334    LoadingSpinner.prototype.createEl = function createEl$$1() {
12335      var isAudio = this.player_.isAudio();
12336      var playerType = this.localize(isAudio ? 'Audio Player' : 'Video Player');
12337      var controlText = createEl('span', {
12338        className: 'vjs-control-text',
12339        innerHTML: this.localize('{1} is loading.', [playerType])
12340      });
12341
12342      var el = _Component.prototype.createEl.call(this, 'div', {
12343        className: 'vjs-loading-spinner',
12344        dir: 'ltr'
12345      });
12346
12347      el.appendChild(controlText);
12348
12349      return el;
12350    };
12351
12352    return LoadingSpinner;
12353  }(Component);
12354
12355  Component.registerComponent('LoadingSpinner', LoadingSpinner);
12356
12357  /**
12358   * @file button.js
12359   */
12360
12361  /**
12362   * Base class for all buttons.
12363   *
12364   * @extends ClickableComponent
12365   */
12366
12367  var Button = function (_ClickableComponent) {
12368    inherits(Button, _ClickableComponent);
12369
12370    function Button() {
12371      classCallCheck(this, Button);
12372      return possibleConstructorReturn(this, _ClickableComponent.apply(this, arguments));
12373    }
12374
12375    /**
12376     * Create the `Button`s DOM element.
12377     *
12378     * @param {string} [tag="button"]
12379     *        The element's node type. This argument is IGNORED: no matter what
12380     *        is passed, it will always create a `button` element.
12381     *
12382     * @param {Object} [props={}]
12383     *        An object of properties that should be set on the element.
12384     *
12385     * @param {Object} [attributes={}]
12386     *        An object of attributes that should be set on the element.
12387     *
12388     * @return {Element}
12389     *         The element that gets created.
12390     */
12391    Button.prototype.createEl = function createEl(tag) {
12392      var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
12393      var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
12394
12395      tag = 'button';
12396
12397      props = assign({
12398        innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>',
12399        className: this.buildCSSClass()
12400      }, props);
12401
12402      // Add attributes for button element
12403      attributes = assign({
12404
12405        // Necessary since the default button type is "submit"
12406        type: 'button'
12407      }, attributes);
12408
12409      var el = Component.prototype.createEl.call(this, tag, props, attributes);
12410
12411      this.createControlTextEl(el);
12412
12413      return el;
12414    };
12415
12416    /**
12417     * Add a child `Component` inside of this `Button`.
12418     *
12419     * @param {string|Component} child
12420     *        The name or instance of a child to add.
12421     *
12422     * @param {Object} [options={}]
12423     *        The key/value store of options that will get passed to children of
12424     *        the child.
12425     *
12426     * @return {Component}
12427     *         The `Component` that gets added as a child. When using a string the
12428     *         `Component` will get created by this process.
12429     *
12430     * @deprecated since version 5
12431     */
12432
12433
12434    Button.prototype.addChild = function addChild(child) {
12435      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
12436
12437      var className = this.constructor.name;
12438
12439      log$1.warn('Adding an actionable (user controllable) child to a Button (' + className + ') is not supported; use a ClickableComponent instead.');
12440
12441      // Avoid the error message generated by ClickableComponent's addChild method
12442      return Component.prototype.addChild.call(this, child, options);
12443    };
12444
12445    /**
12446     * Enable the `Button` element so that it can be activated or clicked. Use this with
12447     * {@link Button#disable}.
12448     */
12449
12450
12451    Button.prototype.enable = function enable() {
12452      _ClickableComponent.prototype.enable.call(this);
12453      this.el_.removeAttribute('disabled');
12454    };
12455
12456    /**
12457     * Disable the `Button` element so that it cannot be activated or clicked. Use this with
12458     * {@link Button#enable}.
12459     */
12460
12461
12462    Button.prototype.disable = function disable() {
12463      _ClickableComponent.prototype.disable.call(this);
12464      this.el_.setAttribute('disabled', 'disabled');
12465    };
12466
12467    /**
12468     * This gets called when a `Button` has focus and `keydown` is triggered via a key
12469     * press.
12470     *
12471     * @param {EventTarget~Event} event
12472     *        The event that caused this function to get called.
12473     *
12474     * @listens keydown
12475     */
12476
12477
12478    Button.prototype.handleKeyPress = function handleKeyPress(event) {
12479
12480      // Ignore Space (32) or Enter (13) key operation, which is handled by the browser for a button.
12481      if (event.which === 32 || event.which === 13) {
12482        return;
12483      }
12484
12485      // Pass keypress handling up for unsupported keys
12486      _ClickableComponent.prototype.handleKeyPress.call(this, event);
12487    };
12488
12489    return Button;
12490  }(ClickableComponent);
12491
12492  Component.registerComponent('Button', Button);
12493
12494  /**
12495   * @file big-play-button.js
12496   */
12497
12498  /**
12499   * The initial play button that shows before the video has played. The hiding of the
12500   * `BigPlayButton` get done via CSS and `Player` states.
12501   *
12502   * @extends Button
12503   */
12504
12505  var BigPlayButton = function (_Button) {
12506    inherits(BigPlayButton, _Button);
12507
12508    function BigPlayButton(player, options) {
12509      classCallCheck(this, BigPlayButton);
12510
12511      var _this = possibleConstructorReturn(this, _Button.call(this, player, options));
12512
12513      _this.mouseused_ = false;
12514
12515      _this.on('mousedown', _this.handleMouseDown);
12516      return _this;
12517    }
12518
12519    /**
12520     * Builds the default DOM `className`.
12521     *
12522     * @return {string}
12523     *         The DOM `className` for this object. Always returns 'vjs-big-play-button'.
12524     */
12525
12526
12527    BigPlayButton.prototype.buildCSSClass = function buildCSSClass() {
12528      return 'vjs-big-play-button';
12529    };
12530
12531    /**
12532     * This gets called when a `BigPlayButton` "clicked". See {@link ClickableComponent}
12533     * for more detailed information on what a click can be.
12534     *
12535     * @param {EventTarget~Event} event
12536     *        The `keydown`, `tap`, or `click` event that caused this function to be
12537     *        called.
12538     *
12539     * @listens tap
12540     * @listens click
12541     */
12542
12543
12544    BigPlayButton.prototype.handleClick = function handleClick(event) {
12545      var playPromise = this.player_.play();
12546
12547      // exit early if clicked via the mouse
12548      if (this.mouseused_ && event.clientX && event.clientY) {
12549        silencePromise(playPromise);
12550        return;
12551      }
12552
12553      var cb = this.player_.getChild('controlBar');
12554      var playToggle = cb && cb.getChild('playToggle');
12555
12556      if (!playToggle) {
12557        this.player_.focus();
12558        return;
12559      }
12560
12561      var playFocus = function playFocus() {
12562        return playToggle.focus();
12563      };
12564
12565      if (isPromise(playPromise)) {
12566        playPromise.then(playFocus, function () {});
12567      } else {
12568        this.setTimeout(playFocus, 1);
12569      }
12570    };
12571
12572    BigPlayButton.prototype.handleKeyPress = function handleKeyPress(event) {
12573      this.mouseused_ = false;
12574
12575      _Button.prototype.handleKeyPress.call(this, event);
12576    };
12577
12578    BigPlayButton.prototype.handleMouseDown = function handleMouseDown(event) {
12579      this.mouseused_ = true;
12580    };
12581
12582    return BigPlayButton;
12583  }(Button);
12584
12585  /**
12586   * The text that should display over the `BigPlayButton`s controls. Added to for localization.
12587   *
12588   * @type {string}
12589   * @private
12590   */
12591
12592
12593  BigPlayButton.prototype.controlText_ = 'Play Video';
12594
12595  Component.registerComponent('BigPlayButton', BigPlayButton);
12596
12597  /**
12598   * @file close-button.js
12599   */
12600
12601  /**
12602   * The `CloseButton` is a `{@link Button}` that fires a `close` event when
12603   * it gets clicked.
12604   *
12605   * @extends Button
12606   */
12607
12608  var CloseButton = function (_Button) {
12609    inherits(CloseButton, _Button);
12610
12611    /**
12612     * Creates an instance of the this class.
12613     *
12614     * @param  {Player} player
12615     *         The `Player` that this class should be attached to.
12616     *
12617     * @param  {Object} [options]
12618     *         The key/value store of player options.
12619     */
12620    function CloseButton(player, options) {
12621      classCallCheck(this, CloseButton);
12622
12623      var _this = possibleConstructorReturn(this, _Button.call(this, player, options));
12624
12625      _this.controlText(options && options.controlText || _this.localize('Close'));
12626      return _this;
12627    }
12628
12629    /**
12630     * Builds the default DOM `className`.
12631     *
12632     * @return {string}
12633     *         The DOM `className` for this object.
12634     */
12635
12636
12637    CloseButton.prototype.buildCSSClass = function buildCSSClass() {
12638      return 'vjs-close-button ' + _Button.prototype.buildCSSClass.call(this);
12639    };
12640
12641    /**
12642     * This gets called when a `CloseButton` gets clicked. See
12643     * {@link ClickableComponent#handleClick} for more information on when this will be
12644     * triggered
12645     *
12646     * @param {EventTarget~Event} event
12647     *        The `keydown`, `tap`, or `click` event that caused this function to be
12648     *        called.
12649     *
12650     * @listens tap
12651     * @listens click
12652     * @fires CloseButton#close
12653     */
12654
12655
12656    CloseButton.prototype.handleClick = function handleClick(event) {
12657
12658      /**
12659       * Triggered when the a `CloseButton` is clicked.
12660       *
12661       * @event CloseButton#close
12662       * @type {EventTarget~Event}
12663       *
12664       * @property {boolean} [bubbles=false]
12665       *           set to false so that the close event does not
12666       *           bubble up to parents if there is no listener
12667       */
12668      this.trigger({ type: 'close', bubbles: false });
12669    };
12670
12671    return CloseButton;
12672  }(Button);
12673
12674  Component.registerComponent('CloseButton', CloseButton);
12675
12676  /**
12677   * @file play-toggle.js
12678   */
12679
12680  /**
12681   * Button to toggle between play and pause.
12682   *
12683   * @extends Button
12684   */
12685
12686  var PlayToggle = function (_Button) {
12687    inherits(PlayToggle, _Button);
12688
12689    /**
12690     * Creates an instance of this class.
12691     *
12692     * @param {Player} player
12693     *        The `Player` that this class should be attached to.
12694     *
12695     * @param {Object} [options]
12696     *        The key/value store of player options.
12697     */
12698    function PlayToggle(player, options) {
12699      classCallCheck(this, PlayToggle);
12700
12701      var _this = possibleConstructorReturn(this, _Button.call(this, player, options));
12702
12703      _this.on(player, 'play', _this.handlePlay);
12704      _this.on(player, 'pause', _this.handlePause);
12705      _this.on(player, 'ended', _this.handleEnded);
12706      return _this;
12707    }
12708
12709    /**
12710     * Builds the default DOM `className`.
12711     *
12712     * @return {string}
12713     *         The DOM `className` for this object.
12714     */
12715
12716
12717    PlayToggle.prototype.buildCSSClass = function buildCSSClass() {
12718      return 'vjs-play-control ' + _Button.prototype.buildCSSClass.call(this);
12719    };
12720
12721    /**
12722     * This gets called when an `PlayToggle` is "clicked". See
12723     * {@link ClickableComponent} for more detailed information on what a click can be.
12724     *
12725     * @param {EventTarget~Event} [event]
12726     *        The `keydown`, `tap`, or `click` event that caused this function to be
12727     *        called.
12728     *
12729     * @listens tap
12730     * @listens click
12731     */
12732
12733
12734    PlayToggle.prototype.handleClick = function handleClick(event) {
12735      if (this.player_.paused()) {
12736        this.player_.play();
12737      } else {
12738        this.player_.pause();
12739      }
12740    };
12741
12742    /**
12743     * This gets called once after the video has ended and the user seeks so that
12744     * we can change the replay button back to a play button.
12745     *
12746     * @param {EventTarget~Event} [event]
12747     *        The event that caused this function to run.
12748     *
12749     * @listens Player#seeked
12750     */
12751
12752
12753    PlayToggle.prototype.handleSeeked = function handleSeeked(event) {
12754      this.removeClass('vjs-ended');
12755
12756      if (this.player_.paused()) {
12757        this.handlePause(event);
12758      } else {
12759        this.handlePlay(event);
12760      }
12761    };
12762
12763    /**
12764     * Add the vjs-playing class to the element so it can change appearance.
12765     *
12766     * @param {EventTarget~Event} [event]
12767     *        The event that caused this function to run.
12768     *
12769     * @listens Player#play
12770     */
12771
12772
12773    PlayToggle.prototype.handlePlay = function handlePlay(event) {
12774      this.removeClass('vjs-ended');
12775      this.removeClass('vjs-paused');
12776      this.addClass('vjs-playing');
12777      // change the button text to "Pause"
12778      this.controlText('Pause');
12779    };
12780
12781    /**
12782     * Add the vjs-paused class to the element so it can change appearance.
12783     *
12784     * @param {EventTarget~Event} [event]
12785     *        The event that caused this function to run.
12786     *
12787     * @listens Player#pause
12788     */
12789
12790
12791    PlayToggle.prototype.handlePause = function handlePause(event) {
12792      this.removeClass('vjs-playing');
12793      this.addClass('vjs-paused');
12794      // change the button text to "Play"
12795      this.controlText('Play');
12796    };
12797
12798    /**
12799     * Add the vjs-ended class to the element so it can change appearance
12800     *
12801     * @param {EventTarget~Event} [event]
12802     *        The event that caused this function to run.
12803     *
12804     * @listens Player#ended
12805     */
12806
12807
12808    PlayToggle.prototype.handleEnded = function handleEnded(event) {
12809      this.removeClass('vjs-playing');
12810      this.addClass('vjs-ended');
12811      // change the button text to "Replay"
12812      this.controlText('Replay');
12813
12814      // on the next seek remove the replay button
12815      this.one(this.player_, 'seeked', this.handleSeeked);
12816    };
12817
12818    return PlayToggle;
12819  }(Button);
12820
12821  /**
12822   * The text that should display over the `PlayToggle`s controls. Added for localization.
12823   *
12824   * @type {string}
12825   * @private
12826   */
12827
12828
12829  PlayToggle.prototype.controlText_ = 'Play';
12830
12831  Component.registerComponent('PlayToggle', PlayToggle);
12832
12833  /**
12834   * @file format-time.js
12835   * @module format-time
12836   */
12837
12838  /**
12839  * Format seconds as a time string, H:MM:SS or M:SS. Supplying a guide (in seconds)
12840  * will force a number of leading zeros to cover the length of the guide.
12841  *
12842  * @param {number} seconds
12843  *        Number of seconds to be turned into a string
12844  *
12845  * @param {number} guide
12846  *        Number (in seconds) to model the string after
12847  *
12848  * @return {string}
12849  *         Time formatted as H:MM:SS or M:SS
12850  */
12851  var defaultImplementation = function defaultImplementation(seconds, guide) {
12852    seconds = seconds < 0 ? 0 : seconds;
12853    var s = Math.floor(seconds % 60);
12854    var m = Math.floor(seconds / 60 % 60);
12855    var h = Math.floor(seconds / 3600);
12856    var gm = Math.floor(guide / 60 % 60);
12857    var gh = Math.floor(guide / 3600);
12858
12859    // handle invalid times
12860    if (isNaN(seconds) || seconds === Infinity) {
12861      // '-' is false for all relational operators (e.g. <, >=) so this setting
12862      // will add the minimum number of fields specified by the guide
12863      h = m = s = '-';
12864    }
12865
12866    // Check if we need to show hours
12867    h = h > 0 || gh > 0 ? h + ':' : '';
12868
12869    // If hours are showing, we may need to add a leading zero.
12870    // Always show at least one digit of minutes.
12871    m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':';
12872
12873    // Check if leading zero is need for seconds
12874    s = s < 10 ? '0' + s : s;
12875
12876    return h + m + s;
12877  };
12878
12879  var implementation = defaultImplementation;
12880
12881  /**
12882   * Replaces the default formatTime implementation with a custom implementation.
12883   *
12884   * @param {Function} customImplementation
12885   *        A function which will be used in place of the default formatTime implementation.
12886   *        Will receive the current time in seconds and the guide (in seconds) as arguments.
12887   */
12888  function setFormatTime(customImplementation) {
12889    implementation = customImplementation;
12890  }
12891
12892  /**
12893   * Resets formatTime to the default implementation.
12894   */
12895  function resetFormatTime() {
12896    implementation = defaultImplementation;
12897  }
12898
12899  function formatTime (seconds) {
12900    var guide = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : seconds;
12901
12902    return implementation(seconds, guide);
12903  }
12904
12905  /**
12906   * @file time-display.js
12907   */
12908
12909  /**
12910   * Displays the time left in the video
12911   *
12912   * @extends Component
12913   */
12914
12915  var TimeDisplay = function (_Component) {
12916    inherits(TimeDisplay, _Component);
12917
12918    /**
12919     * Creates an instance of this class.
12920     *
12921     * @param {Player} player
12922     *        The `Player` that this class should be attached to.
12923     *
12924     * @param {Object} [options]
12925     *        The key/value store of player options.
12926     */
12927    function TimeDisplay(player, options) {
12928      classCallCheck(this, TimeDisplay);
12929
12930      var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
12931
12932      _this.throttledUpdateContent = throttle(bind(_this, _this.updateContent), 25);
12933      _this.on(player, 'timeupdate', _this.throttledUpdateContent);
12934      return _this;
12935    }
12936
12937    /**
12938     * Create the `Component`'s DOM element
12939     *
12940     * @return {Element}
12941     *         The element that was created.
12942     */
12943
12944
12945    TimeDisplay.prototype.createEl = function createEl$$1(plainName) {
12946      var className = this.buildCSSClass();
12947      var el = _Component.prototype.createEl.call(this, 'div', {
12948        className: className + ' vjs-time-control vjs-control',
12949        innerHTML: '<span class="vjs-control-text">' + this.localize(this.labelText_) + '\xA0</span>'
12950      });
12951
12952      this.contentEl_ = createEl('span', {
12953        className: className + '-display'
12954      }, {
12955        // tell screen readers not to automatically read the time as it changes
12956        'aria-live': 'off'
12957      });
12958
12959      this.updateTextNode_();
12960      el.appendChild(this.contentEl_);
12961      return el;
12962    };
12963
12964    TimeDisplay.prototype.dispose = function dispose() {
12965      this.contentEl_ = null;
12966      this.textNode_ = null;
12967
12968      _Component.prototype.dispose.call(this);
12969    };
12970
12971    /**
12972     * Updates the "remaining time" text node with new content using the
12973     * contents of the `formattedTime_` property.
12974     *
12975     * @private
12976     */
12977
12978
12979    TimeDisplay.prototype.updateTextNode_ = function updateTextNode_() {
12980      if (!this.contentEl_) {
12981        return;
12982      }
12983
12984      while (this.contentEl_.firstChild) {
12985        this.contentEl_.removeChild(this.contentEl_.firstChild);
12986      }
12987
12988      this.textNode_ = document_1.createTextNode(this.formattedTime_ || this.formatTime_(0));
12989      this.contentEl_.appendChild(this.textNode_);
12990    };
12991
12992    /**
12993     * Generates a formatted time for this component to use in display.
12994     *
12995     * @param  {number} time
12996     *         A numeric time, in seconds.
12997     *
12998     * @return {string}
12999     *         A formatted time
13000     *
13001     * @private
13002     */
13003
13004
13005    TimeDisplay.prototype.formatTime_ = function formatTime_(time) {
13006      return formatTime(time);
13007    };
13008
13009    /**
13010     * Updates the time display text node if it has what was passed in changed
13011     * the formatted time.
13012     *
13013     * @param {number} time
13014     *        The time to update to
13015     *
13016     * @private
13017     */
13018
13019
13020    TimeDisplay.prototype.updateFormattedTime_ = function updateFormattedTime_(time) {
13021      var formattedTime = this.formatTime_(time);
13022
13023      if (formattedTime === this.formattedTime_) {
13024        return;
13025      }
13026
13027      this.formattedTime_ = formattedTime;
13028      this.requestAnimationFrame(this.updateTextNode_);
13029    };
13030
13031    /**
13032     * To be filled out in the child class, should update the displayed time
13033     * in accordance with the fact that the current time has changed.
13034     *
13035     * @param {EventTarget~Event} [event]
13036     *        The `timeupdate`  event that caused this to run.
13037     *
13038     * @listens Player#timeupdate
13039     */
13040
13041
13042    TimeDisplay.prototype.updateContent = function updateContent(event) {};
13043
13044    return TimeDisplay;
13045  }(Component);
13046
13047  /**
13048   * The text that is added to the `TimeDisplay` for screen reader users.
13049   *
13050   * @type {string}
13051   * @private
13052   */
13053
13054
13055  TimeDisplay.prototype.labelText_ = 'Time';
13056
13057  /**
13058   * The text that should display over the `TimeDisplay`s controls. Added to for localization.
13059   *
13060   * @type {string}
13061   * @private
13062   *
13063   * @deprecated in v7; controlText_ is not used in non-active display Components
13064   */
13065  TimeDisplay.prototype.controlText_ = 'Time';
13066
13067  Component.registerComponent('TimeDisplay', TimeDisplay);
13068
13069  /**
13070   * @file current-time-display.js
13071   */
13072
13073  /**
13074   * Displays the current time
13075   *
13076   * @extends Component
13077   */
13078
13079  var CurrentTimeDisplay = function (_TimeDisplay) {
13080    inherits(CurrentTimeDisplay, _TimeDisplay);
13081
13082    /**
13083     * Creates an instance of this class.
13084     *
13085     * @param {Player} player
13086     *        The `Player` that this class should be attached to.
13087     *
13088     * @param {Object} [options]
13089     *        The key/value store of player options.
13090     */
13091    function CurrentTimeDisplay(player, options) {
13092      classCallCheck(this, CurrentTimeDisplay);
13093
13094      var _this = possibleConstructorReturn(this, _TimeDisplay.call(this, player, options));
13095
13096      _this.on(player, 'ended', _this.handleEnded);
13097      return _this;
13098    }
13099
13100    /**
13101     * Builds the default DOM `className`.
13102     *
13103     * @return {string}
13104     *         The DOM `className` for this object.
13105     */
13106
13107
13108    CurrentTimeDisplay.prototype.buildCSSClass = function buildCSSClass() {
13109      return 'vjs-current-time';
13110    };
13111
13112    /**
13113     * Update current time display
13114     *
13115     * @param {EventTarget~Event} [event]
13116     *        The `timeupdate` event that caused this function to run.
13117     *
13118     * @listens Player#timeupdate
13119     */
13120
13121
13122    CurrentTimeDisplay.prototype.updateContent = function updateContent(event) {
13123      // Allows for smooth scrubbing, when player can't keep up.
13124      var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
13125
13126      this.updateFormattedTime_(time);
13127    };
13128
13129    /**
13130     * When the player fires ended there should be no time left. Sadly
13131     * this is not always the case, lets make it seem like that is the case
13132     * for users.
13133     *
13134     * @param {EventTarget~Event} [event]
13135     *        The `ended` event that caused this to run.
13136     *
13137     * @listens Player#ended
13138     */
13139
13140
13141    CurrentTimeDisplay.prototype.handleEnded = function handleEnded(event) {
13142      if (!this.player_.duration()) {
13143        return;
13144      }
13145      this.updateFormattedTime_(this.player_.duration());
13146    };
13147
13148    return CurrentTimeDisplay;
13149  }(TimeDisplay);
13150
13151  /**
13152   * The text that is added to the `CurrentTimeDisplay` for screen reader users.
13153   *
13154   * @type {string}
13155   * @private
13156   */
13157
13158
13159  CurrentTimeDisplay.prototype.labelText_ = 'Current Time';
13160
13161  /**
13162   * The text that should display over the `CurrentTimeDisplay`s controls. Added to for localization.
13163   *
13164   * @type {string}
13165   * @private
13166   *
13167   * @deprecated in v7; controlText_ is not used in non-active display Components
13168   */
13169  CurrentTimeDisplay.prototype.controlText_ = 'Current Time';
13170
13171  Component.registerComponent('CurrentTimeDisplay', CurrentTimeDisplay);
13172
13173  /**
13174   * @file duration-display.js
13175   */
13176
13177  /**
13178   * Displays the duration
13179   *
13180   * @extends Component
13181   */
13182
13183  var DurationDisplay = function (_TimeDisplay) {
13184    inherits(DurationDisplay, _TimeDisplay);
13185
13186    /**
13187     * Creates an instance of this class.
13188     *
13189     * @param {Player} player
13190     *        The `Player` that this class should be attached to.
13191     *
13192     * @param {Object} [options]
13193     *        The key/value store of player options.
13194     */
13195    function DurationDisplay(player, options) {
13196      classCallCheck(this, DurationDisplay);
13197
13198      // we do not want to/need to throttle duration changes,
13199      // as they should always display the changed duration as
13200      // it has changed
13201      var _this = possibleConstructorReturn(this, _TimeDisplay.call(this, player, options));
13202
13203      _this.on(player, 'durationchange', _this.updateContent);
13204
13205      // Also listen for timeupdate (in the parent) and loadedmetadata because removing those
13206      // listeners could have broken dependent applications/libraries. These
13207      // can likely be removed for 7.0.
13208      _this.on(player, 'loadedmetadata', _this.throttledUpdateContent);
13209      return _this;
13210    }
13211
13212    /**
13213     * Builds the default DOM `className`.
13214     *
13215     * @return {string}
13216     *         The DOM `className` for this object.
13217     */
13218
13219
13220    DurationDisplay.prototype.buildCSSClass = function buildCSSClass() {
13221      return 'vjs-duration';
13222    };
13223
13224    /**
13225     * Update duration time display.
13226     *
13227     * @param {EventTarget~Event} [event]
13228     *        The `durationchange`, `timeupdate`, or `loadedmetadata` event that caused
13229     *        this function to be called.
13230     *
13231     * @listens Player#durationchange
13232     * @listens Player#timeupdate
13233     * @listens Player#loadedmetadata
13234     */
13235
13236
13237    DurationDisplay.prototype.updateContent = function updateContent(event) {
13238      var duration = this.player_.duration();
13239
13240      if (duration && this.duration_ !== duration) {
13241        this.duration_ = duration;
13242        this.updateFormattedTime_(duration);
13243      }
13244    };
13245
13246    return DurationDisplay;
13247  }(TimeDisplay);
13248
13249  /**
13250   * The text that is added to the `DurationDisplay` for screen reader users.
13251   *
13252   * @type {string}
13253   * @private
13254   */
13255
13256
13257  DurationDisplay.prototype.labelText_ = 'Duration';
13258
13259  /**
13260   * The text that should display over the `DurationDisplay`s controls. Added to for localization.
13261   *
13262   * @type {string}
13263   * @private
13264   *
13265   * @deprecated in v7; controlText_ is not used in non-active display Components
13266   */
13267  DurationDisplay.prototype.controlText_ = 'Duration';
13268
13269  Component.registerComponent('DurationDisplay', DurationDisplay);
13270
13271  /**
13272   * @file time-divider.js
13273   */
13274
13275  /**
13276   * The separator between the current time and duration.
13277   * Can be hidden if it's not needed in the design.
13278   *
13279   * @extends Component
13280   */
13281
13282  var TimeDivider = function (_Component) {
13283    inherits(TimeDivider, _Component);
13284
13285    function TimeDivider() {
13286      classCallCheck(this, TimeDivider);
13287      return possibleConstructorReturn(this, _Component.apply(this, arguments));
13288    }
13289
13290    /**
13291     * Create the component's DOM element
13292     *
13293     * @return {Element}
13294     *         The element that was created.
13295     */
13296    TimeDivider.prototype.createEl = function createEl() {
13297      return _Component.prototype.createEl.call(this, 'div', {
13298        className: 'vjs-time-control vjs-time-divider',
13299        innerHTML: '<div><span>/</span></div>'
13300      });
13301    };
13302
13303    return TimeDivider;
13304  }(Component);
13305
13306  Component.registerComponent('TimeDivider', TimeDivider);
13307
13308  /**
13309   * @file remaining-time-display.js
13310   */
13311  /**
13312   * Displays the time left in the video
13313   *
13314   * @extends Component
13315   */
13316
13317  var RemainingTimeDisplay = function (_TimeDisplay) {
13318    inherits(RemainingTimeDisplay, _TimeDisplay);
13319
13320    /**
13321     * Creates an instance of this class.
13322     *
13323     * @param {Player} player
13324     *        The `Player` that this class should be attached to.
13325     *
13326     * @param {Object} [options]
13327     *        The key/value store of player options.
13328     */
13329    function RemainingTimeDisplay(player, options) {
13330      classCallCheck(this, RemainingTimeDisplay);
13331
13332      var _this = possibleConstructorReturn(this, _TimeDisplay.call(this, player, options));
13333
13334      _this.on(player, 'durationchange', _this.throttledUpdateContent);
13335      _this.on(player, 'ended', _this.handleEnded);
13336      return _this;
13337    }
13338
13339    /**
13340     * Builds the default DOM `className`.
13341     *
13342     * @return {string}
13343     *         The DOM `className` for this object.
13344     */
13345
13346
13347    RemainingTimeDisplay.prototype.buildCSSClass = function buildCSSClass() {
13348      return 'vjs-remaining-time';
13349    };
13350
13351    /**
13352     * The remaining time display prefixes numbers with a "minus" character.
13353     *
13354     * @param  {number} time
13355     *         A numeric time, in seconds.
13356     *
13357     * @return {string}
13358     *         A formatted time
13359     *
13360     * @private
13361     */
13362
13363
13364    RemainingTimeDisplay.prototype.formatTime_ = function formatTime_(time) {
13365      // TODO: The "-" should be decorative, and not announced by a screen reader
13366      return '-' + _TimeDisplay.prototype.formatTime_.call(this, time);
13367    };
13368
13369    /**
13370     * Update remaining time display.
13371     *
13372     * @param {EventTarget~Event} [event]
13373     *        The `timeupdate` or `durationchange` event that caused this to run.
13374     *
13375     * @listens Player#timeupdate
13376     * @listens Player#durationchange
13377     */
13378
13379
13380    RemainingTimeDisplay.prototype.updateContent = function updateContent(event) {
13381      if (!this.player_.duration()) {
13382        return;
13383      }
13384
13385      // @deprecated We should only use remainingTimeDisplay
13386      // as of video.js 7
13387      if (this.player_.remainingTimeDisplay) {
13388        this.updateFormattedTime_(this.player_.remainingTimeDisplay());
13389      } else {
13390        this.updateFormattedTime_(this.player_.remainingTime());
13391      }
13392    };
13393
13394    /**
13395     * When the player fires ended there should be no time left. Sadly
13396     * this is not always the case, lets make it seem like that is the case
13397     * for users.
13398     *
13399     * @param {EventTarget~Event} [event]
13400     *        The `ended` event that caused this to run.
13401     *
13402     * @listens Player#ended
13403     */
13404
13405
13406    RemainingTimeDisplay.prototype.handleEnded = function handleEnded(event) {
13407      if (!this.player_.duration()) {
13408        return;
13409      }
13410      this.updateFormattedTime_(0);
13411    };
13412
13413    return RemainingTimeDisplay;
13414  }(TimeDisplay);
13415
13416  /**
13417   * The text that is added to the `RemainingTimeDisplay` for screen reader users.
13418   *
13419   * @type {string}
13420   * @private
13421   */
13422
13423
13424  RemainingTimeDisplay.prototype.labelText_ = 'Remaining Time';
13425
13426  /**
13427   * The text that should display over the `RemainingTimeDisplay`s controls. Added to for localization.
13428   *
13429   * @type {string}
13430   * @private
13431   *
13432   * @deprecated in v7; controlText_ is not used in non-active display Components
13433   */
13434  RemainingTimeDisplay.prototype.controlText_ = 'Remaining Time';
13435
13436  Component.registerComponent('RemainingTimeDisplay', RemainingTimeDisplay);
13437
13438  /**
13439   * @file live-display.js
13440   */
13441
13442  // TODO - Future make it click to snap to live
13443
13444  /**
13445   * Displays the live indicator when duration is Infinity.
13446   *
13447   * @extends Component
13448   */
13449
13450  var LiveDisplay = function (_Component) {
13451    inherits(LiveDisplay, _Component);
13452
13453    /**
13454     * Creates an instance of this class.
13455     *
13456     * @param {Player} player
13457     *        The `Player` that this class should be attached to.
13458     *
13459     * @param {Object} [options]
13460     *        The key/value store of player options.
13461     */
13462    function LiveDisplay(player, options) {
13463      classCallCheck(this, LiveDisplay);
13464
13465      var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
13466
13467      _this.updateShowing();
13468      _this.on(_this.player(), 'durationchange', _this.updateShowing);
13469      return _this;
13470    }
13471
13472    /**
13473     * Create the `Component`'s DOM element
13474     *
13475     * @return {Element}
13476     *         The element that was created.
13477     */
13478
13479
13480    LiveDisplay.prototype.createEl = function createEl$$1() {
13481      var el = _Component.prototype.createEl.call(this, 'div', {
13482        className: 'vjs-live-control vjs-control'
13483      });
13484
13485      this.contentEl_ = createEl('div', {
13486        className: 'vjs-live-display',
13487        innerHTML: '<span class="vjs-control-text">' + this.localize('Stream Type') + '\xA0</span>' + this.localize('LIVE')
13488      }, {
13489        'aria-live': 'off'
13490      });
13491
13492      el.appendChild(this.contentEl_);
13493      return el;
13494    };
13495
13496    LiveDisplay.prototype.dispose = function dispose() {
13497      this.contentEl_ = null;
13498
13499      _Component.prototype.dispose.call(this);
13500    };
13501
13502    /**
13503     * Check the duration to see if the LiveDisplay should be showing or not. Then show/hide
13504     * it accordingly
13505     *
13506     * @param {EventTarget~Event} [event]
13507     *        The {@link Player#durationchange} event that caused this function to run.
13508     *
13509     * @listens Player#durationchange
13510     */
13511
13512
13513    LiveDisplay.prototype.updateShowing = function updateShowing(event) {
13514      if (this.player().duration() === Infinity) {
13515        this.show();
13516      } else {
13517        this.hide();
13518      }
13519    };
13520
13521    return LiveDisplay;
13522  }(Component);
13523
13524  Component.registerComponent('LiveDisplay', LiveDisplay);
13525
13526  /**
13527   * @file slider.js
13528   */
13529
13530  /**
13531   * The base functionality for a slider. Can be vertical or horizontal.
13532   * For instance the volume bar or the seek bar on a video is a slider.
13533   *
13534   * @extends Component
13535   */
13536
13537  var Slider = function (_Component) {
13538    inherits(Slider, _Component);
13539
13540    /**
13541     * Create an instance of this class
13542     *
13543     * @param {Player} player
13544     *        The `Player` that this class should be attached to.
13545     *
13546     * @param {Object} [options]
13547     *        The key/value store of player options.
13548     */
13549    function Slider(player, options) {
13550      classCallCheck(this, Slider);
13551
13552      // Set property names to bar to match with the child Slider class is looking for
13553      var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
13554
13555      _this.bar = _this.getChild(_this.options_.barName);
13556
13557      // Set a horizontal or vertical class on the slider depending on the slider type
13558      _this.vertical(!!_this.options_.vertical);
13559
13560      _this.enable();
13561      return _this;
13562    }
13563
13564    /**
13565     * Are controls are currently enabled for this slider or not.
13566     *
13567     * @return {boolean}
13568     *         true if controls are enabled, false otherwise
13569     */
13570
13571
13572    Slider.prototype.enabled = function enabled() {
13573      return this.enabled_;
13574    };
13575
13576    /**
13577     * Enable controls for this slider if they are disabled
13578     */
13579
13580
13581    Slider.prototype.enable = function enable() {
13582      if (this.enabled()) {
13583        return;
13584      }
13585
13586      this.on('mousedown', this.handleMouseDown);
13587      this.on('touchstart', this.handleMouseDown);
13588      this.on('focus', this.handleFocus);
13589      this.on('blur', this.handleBlur);
13590      this.on('click', this.handleClick);
13591
13592      this.on(this.player_, 'controlsvisible', this.update);
13593
13594      if (this.playerEvent) {
13595        this.on(this.player_, this.playerEvent, this.update);
13596      }
13597
13598      this.removeClass('disabled');
13599      this.setAttribute('tabindex', 0);
13600
13601      this.enabled_ = true;
13602    };
13603
13604    /**
13605     * Disable controls for this slider if they are enabled
13606     */
13607
13608
13609    Slider.prototype.disable = function disable() {
13610      if (!this.enabled()) {
13611        return;
13612      }
13613      var doc = this.bar.el_.ownerDocument;
13614
13615      this.off('mousedown', this.handleMouseDown);
13616      this.off('touchstart', this.handleMouseDown);
13617      this.off('focus', this.handleFocus);
13618      this.off('blur', this.handleBlur);
13619      this.off('click', this.handleClick);
13620      this.off(this.player_, 'controlsvisible', this.update);
13621      this.off(doc, 'mousemove', this.handleMouseMove);
13622      this.off(doc, 'mouseup', this.handleMouseUp);
13623      this.off(doc, 'touchmove', this.handleMouseMove);
13624      this.off(doc, 'touchend', this.handleMouseUp);
13625      this.removeAttribute('tabindex');
13626
13627      this.addClass('disabled');
13628
13629      if (this.playerEvent) {
13630        this.off(this.player_, this.playerEvent, this.update);
13631      }
13632      this.enabled_ = false;
13633    };
13634
13635    /**
13636     * Create the `Slider`s DOM element.
13637     *
13638     * @param {string} type
13639     *        Type of element to create.
13640     *
13641     * @param {Object} [props={}]
13642     *        List of properties in Object form.
13643     *
13644     * @param {Object} [attributes={}]
13645     *        list of attributes in Object form.
13646     *
13647     * @return {Element}
13648     *         The element that gets created.
13649     */
13650
13651
13652    Slider.prototype.createEl = function createEl$$1(type) {
13653      var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
13654      var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
13655
13656      // Add the slider element class to all sub classes
13657      props.className = props.className + ' vjs-slider';
13658      props = assign({
13659        tabIndex: 0
13660      }, props);
13661
13662      attributes = assign({
13663        'role': 'slider',
13664        'aria-valuenow': 0,
13665        'aria-valuemin': 0,
13666        'aria-valuemax': 100,
13667        'tabIndex': 0
13668      }, attributes);
13669
13670      return _Component.prototype.createEl.call(this, type, props, attributes);
13671    };
13672
13673    /**
13674     * Handle `mousedown` or `touchstart` events on the `Slider`.
13675     *
13676     * @param {EventTarget~Event} event
13677     *        `mousedown` or `touchstart` event that triggered this function
13678     *
13679     * @listens mousedown
13680     * @listens touchstart
13681     * @fires Slider#slideractive
13682     */
13683
13684
13685    Slider.prototype.handleMouseDown = function handleMouseDown(event) {
13686      var doc = this.bar.el_.ownerDocument;
13687
13688      if (event.type === 'mousedown') {
13689        event.preventDefault();
13690      }
13691      // Do not call preventDefault() on touchstart in Chrome
13692      // to avoid console warnings. Use a 'touch-action: none' style
13693      // instead to prevent unintented scrolling.
13694      // https://developers.google.com/web/updates/2017/01/scrolling-intervention
13695      if (event.type === 'touchstart' && !IS_CHROME) {
13696        event.preventDefault();
13697      }
13698      blockTextSelection();
13699
13700      this.addClass('vjs-sliding');
13701      /**
13702       * Triggered when the slider is in an active state
13703       *
13704       * @event Slider#slideractive
13705       * @type {EventTarget~Event}
13706       */
13707      this.trigger('slideractive');
13708
13709      this.on(doc, 'mousemove', this.handleMouseMove);
13710      this.on(doc, 'mouseup', this.handleMouseUp);
13711      this.on(doc, 'touchmove', this.handleMouseMove);
13712      this.on(doc, 'touchend', this.handleMouseUp);
13713
13714      this.handleMouseMove(event);
13715    };
13716
13717    /**
13718     * Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`.
13719     * The `mousemove` and `touchmove` events will only only trigger this function during
13720     * `mousedown` and `touchstart`. This is due to {@link Slider#handleMouseDown} and
13721     * {@link Slider#handleMouseUp}.
13722     *
13723     * @param {EventTarget~Event} event
13724     *        `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered
13725     *        this function
13726     *
13727     * @listens mousemove
13728     * @listens touchmove
13729     */
13730
13731
13732    Slider.prototype.handleMouseMove = function handleMouseMove(event) {};
13733
13734    /**
13735     * Handle `mouseup` or `touchend` events on the `Slider`.
13736     *
13737     * @param {EventTarget~Event} event
13738     *        `mouseup` or `touchend` event that triggered this function.
13739     *
13740     * @listens touchend
13741     * @listens mouseup
13742     * @fires Slider#sliderinactive
13743     */
13744
13745
13746    Slider.prototype.handleMouseUp = function handleMouseUp() {
13747      var doc = this.bar.el_.ownerDocument;
13748
13749      unblockTextSelection();
13750
13751      this.removeClass('vjs-sliding');
13752      /**
13753       * Triggered when the slider is no longer in an active state.
13754       *
13755       * @event Slider#sliderinactive
13756       * @type {EventTarget~Event}
13757       */
13758      this.trigger('sliderinactive');
13759
13760      this.off(doc, 'mousemove', this.handleMouseMove);
13761      this.off(doc, 'mouseup', this.handleMouseUp);
13762      this.off(doc, 'touchmove', this.handleMouseMove);
13763      this.off(doc, 'touchend', this.handleMouseUp);
13764
13765      this.update();
13766    };
13767
13768    /**
13769     * Update the progress bar of the `Slider`.
13770     *
13771     * @returns {number}
13772     *          The percentage of progress the progress bar represents as a
13773     *          number from 0 to 1.
13774     */
13775
13776
13777    Slider.prototype.update = function update() {
13778
13779      // In VolumeBar init we have a setTimeout for update that pops and update
13780      // to the end of the execution stack. The player is destroyed before then
13781      // update will cause an error
13782      if (!this.el_) {
13783        return;
13784      }
13785
13786      // If scrubbing, we could use a cached value to make the handle keep up
13787      // with the user's mouse. On HTML5 browsers scrubbing is really smooth, but
13788      // some flash players are slow, so we might want to utilize this later.
13789      // var progress =  (this.player_.scrubbing()) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration();
13790      var progress = this.getPercent();
13791      var bar = this.bar;
13792
13793      // If there's no bar...
13794      if (!bar) {
13795        return;
13796      }
13797
13798      // Protect against no duration and other division issues
13799      if (typeof progress !== 'number' || progress !== progress || progress < 0 || progress === Infinity) {
13800        progress = 0;
13801      }
13802
13803      // Convert to a percentage for setting
13804      var percentage = (progress * 100).toFixed(2) + '%';
13805      var style = bar.el().style;
13806
13807      // Set the new bar width or height
13808      if (this.vertical()) {
13809        style.height = percentage;
13810      } else {
13811        style.width = percentage;
13812      }
13813
13814      return progress;
13815    };
13816
13817    /**
13818     * Calculate distance for slider
13819     *
13820     * @param {EventTarget~Event} event
13821     *        The event that caused this function to run.
13822     *
13823     * @return {number}
13824     *         The current position of the Slider.
13825     *         - position.x for vertical `Slider`s
13826     *         - position.y for horizontal `Slider`s
13827     */
13828
13829
13830    Slider.prototype.calculateDistance = function calculateDistance(event) {
13831      var position = getPointerPosition(this.el_, event);
13832
13833      if (this.vertical()) {
13834        return position.y;
13835      }
13836      return position.x;
13837    };
13838
13839    /**
13840     * Handle a `focus` event on this `Slider`.
13841     *
13842     * @param {EventTarget~Event} event
13843     *        The `focus` event that caused this function to run.
13844     *
13845     * @listens focus
13846     */
13847
13848
13849    Slider.prototype.handleFocus = function handleFocus() {
13850      this.on(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress);
13851    };
13852
13853    /**
13854     * Handle a `keydown` event on the `Slider`. Watches for left, rigth, up, and down
13855     * arrow keys. This function will only be called when the slider has focus. See
13856     * {@link Slider#handleFocus} and {@link Slider#handleBlur}.
13857     *
13858     * @param {EventTarget~Event} event
13859     *        the `keydown` event that caused this function to run.
13860     *
13861     * @listens keydown
13862     */
13863
13864
13865    Slider.prototype.handleKeyPress = function handleKeyPress(event) {
13866      // Left and Down Arrows
13867      if (event.which === 37 || event.which === 40) {
13868        event.preventDefault();
13869        this.stepBack();
13870
13871        // Up and Right Arrows
13872      } else if (event.which === 38 || event.which === 39) {
13873        event.preventDefault();
13874        this.stepForward();
13875      }
13876    };
13877
13878    /**
13879     * Handle a `blur` event on this `Slider`.
13880     *
13881     * @param {EventTarget~Event} event
13882     *        The `blur` event that caused this function to run.
13883     *
13884     * @listens blur
13885     */
13886
13887    Slider.prototype.handleBlur = function handleBlur() {
13888      this.off(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress);
13889    };
13890
13891    /**
13892     * Listener for click events on slider, used to prevent clicks
13893     *   from bubbling up to parent elements like button menus.
13894     *
13895     * @param {Object} event
13896     *        Event that caused this object to run
13897     */
13898
13899
13900    Slider.prototype.handleClick = function handleClick(event) {
13901      event.stopImmediatePropagation();
13902      event.preventDefault();
13903    };
13904
13905    /**
13906     * Get/set if slider is horizontal for vertical
13907     *
13908     * @param {boolean} [bool]
13909     *        - true if slider is vertical,
13910     *        - false is horizontal
13911     *
13912     * @return {boolean}
13913     *         - true if slider is vertical, and getting
13914     *         - false if the slider is horizontal, and getting
13915     */
13916
13917
13918    Slider.prototype.vertical = function vertical(bool) {
13919      if (bool === undefined) {
13920        return this.vertical_ || false;
13921      }
13922
13923      this.vertical_ = !!bool;
13924
13925      if (this.vertical_) {
13926        this.addClass('vjs-slider-vertical');
13927      } else {
13928        this.addClass('vjs-slider-horizontal');
13929      }
13930    };
13931
13932    return Slider;
13933  }(Component);
13934
13935  Component.registerComponent('Slider', Slider);
13936
13937  /**
13938   * @file load-progress-bar.js
13939   */
13940
13941  /**
13942   * Shows loading progress
13943   *
13944   * @extends Component
13945   */
13946
13947  var LoadProgressBar = function (_Component) {
13948    inherits(LoadProgressBar, _Component);
13949
13950    /**
13951     * Creates an instance of this class.
13952     *
13953     * @param {Player} player
13954     *        The `Player` that this class should be attached to.
13955     *
13956     * @param {Object} [options]
13957     *        The key/value store of player options.
13958     */
13959    function LoadProgressBar(player, options) {
13960      classCallCheck(this, LoadProgressBar);
13961
13962      var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
13963
13964      _this.partEls_ = [];
13965      _this.on(player, 'progress', _this.update);
13966      return _this;
13967    }
13968
13969    /**
13970     * Create the `Component`'s DOM element
13971     *
13972     * @return {Element}
13973     *         The element that was created.
13974     */
13975
13976
13977    LoadProgressBar.prototype.createEl = function createEl$$1() {
13978      return _Component.prototype.createEl.call(this, 'div', {
13979        className: 'vjs-load-progress',
13980        innerHTML: '<span class="vjs-control-text"><span>' + this.localize('Loaded') + '</span>: 0%</span>'
13981      });
13982    };
13983
13984    LoadProgressBar.prototype.dispose = function dispose() {
13985      this.partEls_ = null;
13986
13987      _Component.prototype.dispose.call(this);
13988    };
13989
13990    /**
13991     * Update progress bar
13992     *
13993     * @param {EventTarget~Event} [event]
13994     *        The `progress` event that caused this function to run.
13995     *
13996     * @listens Player#progress
13997     */
13998
13999
14000    LoadProgressBar.prototype.update = function update(event) {
14001      var buffered = this.player_.buffered();
14002      var duration = this.player_.duration();
14003      var bufferedEnd = this.player_.bufferedEnd();
14004      var children = this.partEls_;
14005
14006      // get the percent width of a time compared to the total end
14007      var percentify = function percentify(time, end) {
14008        // no NaN
14009        var percent = time / end || 0;
14010
14011        return (percent >= 1 ? 1 : percent) * 100 + '%';
14012      };
14013
14014      // update the width of the progress bar
14015      this.el_.style.width = percentify(bufferedEnd, duration);
14016
14017      // add child elements to represent the individual buffered time ranges
14018      for (var i = 0; i < buffered.length; i++) {
14019        var start = buffered.start(i);
14020        var end = buffered.end(i);
14021        var part = children[i];
14022
14023        if (!part) {
14024          part = this.el_.appendChild(createEl());
14025          children[i] = part;
14026        }
14027
14028        // set the percent based on the width of the progress bar (bufferedEnd)
14029        part.style.left = percentify(start, bufferedEnd);
14030        part.style.width = percentify(end - start, bufferedEnd);
14031      }
14032
14033      // remove unused buffered range elements
14034      for (var _i = children.length; _i > buffered.length; _i--) {
14035        this.el_.removeChild(children[_i - 1]);
14036      }
14037      children.length = buffered.length;
14038    };
14039
14040    return LoadProgressBar;
14041  }(Component);
14042
14043  Component.registerComponent('LoadProgressBar', LoadProgressBar);
14044
14045  /**
14046   * @file time-tooltip.js
14047   */
14048
14049  /**
14050   * Time tooltips display a time above the progress bar.
14051   *
14052   * @extends Component
14053   */
14054
14055  var TimeTooltip = function (_Component) {
14056    inherits(TimeTooltip, _Component);
14057
14058    function TimeTooltip() {
14059      classCallCheck(this, TimeTooltip);
14060      return possibleConstructorReturn(this, _Component.apply(this, arguments));
14061    }
14062
14063    /**
14064     * Create the time tooltip DOM element
14065     *
14066     * @return {Element}
14067     *         The element that was created.
14068     */
14069    TimeTooltip.prototype.createEl = function createEl$$1() {
14070      return _Component.prototype.createEl.call(this, 'div', {
14071        className: 'vjs-time-tooltip'
14072      });
14073    };
14074
14075    /**
14076     * Updates the position of the time tooltip relative to the `SeekBar`.
14077     *
14078     * @param {Object} seekBarRect
14079     *        The `ClientRect` for the {@link SeekBar} element.
14080     *
14081     * @param {number} seekBarPoint
14082     *        A number from 0 to 1, representing a horizontal reference point
14083     *        from the left edge of the {@link SeekBar}
14084     */
14085
14086
14087    TimeTooltip.prototype.update = function update(seekBarRect, seekBarPoint, content) {
14088      var tooltipRect = getBoundingClientRect(this.el_);
14089      var playerRect = getBoundingClientRect(this.player_.el());
14090      var seekBarPointPx = seekBarRect.width * seekBarPoint;
14091
14092      // do nothing if either rect isn't available
14093      // for example, if the player isn't in the DOM for testing
14094      if (!playerRect || !tooltipRect) {
14095        return;
14096      }
14097
14098      // This is the space left of the `seekBarPoint` available within the bounds
14099      // of the player. We calculate any gap between the left edge of the player
14100      // and the left edge of the `SeekBar` and add the number of pixels in the
14101      // `SeekBar` before hitting the `seekBarPoint`
14102      var spaceLeftOfPoint = seekBarRect.left - playerRect.left + seekBarPointPx;
14103
14104      // This is the space right of the `seekBarPoint` available within the bounds
14105      // of the player. We calculate the number of pixels from the `seekBarPoint`
14106      // to the right edge of the `SeekBar` and add to that any gap between the
14107      // right edge of the `SeekBar` and the player.
14108      var spaceRightOfPoint = seekBarRect.width - seekBarPointPx + (playerRect.right - seekBarRect.right);
14109
14110      // This is the number of pixels by which the tooltip will need to be pulled
14111      // further to the right to center it over the `seekBarPoint`.
14112      var pullTooltipBy = tooltipRect.width / 2;
14113
14114      // Adjust the `pullTooltipBy` distance to the left or right depending on
14115      // the results of the space calculations above.
14116      if (spaceLeftOfPoint < pullTooltipBy) {
14117        pullTooltipBy += pullTooltipBy - spaceLeftOfPoint;
14118      } else if (spaceRightOfPoint < pullTooltipBy) {
14119        pullTooltipBy = spaceRightOfPoint;
14120      }
14121
14122      // Due to the imprecision of decimal/ratio based calculations and varying
14123      // rounding behaviors, there are cases where the spacing adjustment is off
14124      // by a pixel or two. This adds insurance to these calculations.
14125      if (pullTooltipBy < 0) {
14126        pullTooltipBy = 0;
14127      } else if (pullTooltipBy > tooltipRect.width) {
14128        pullTooltipBy = tooltipRect.width;
14129      }
14130
14131      this.el_.style.right = '-' + pullTooltipBy + 'px';
14132      textContent(this.el_, content);
14133    };
14134
14135    return TimeTooltip;
14136  }(Component);
14137
14138  Component.registerComponent('TimeTooltip', TimeTooltip);
14139
14140  /**
14141   * @file play-progress-bar.js
14142   */
14143
14144  /**
14145   * Used by {@link SeekBar} to display media playback progress as part of the
14146   * {@link ProgressControl}.
14147   *
14148   * @extends Component
14149   */
14150
14151  var PlayProgressBar = function (_Component) {
14152    inherits(PlayProgressBar, _Component);
14153
14154    function PlayProgressBar() {
14155      classCallCheck(this, PlayProgressBar);
14156      return possibleConstructorReturn(this, _Component.apply(this, arguments));
14157    }
14158
14159    /**
14160     * Create the the DOM element for this class.
14161     *
14162     * @return {Element}
14163     *         The element that was created.
14164     */
14165    PlayProgressBar.prototype.createEl = function createEl() {
14166      return _Component.prototype.createEl.call(this, 'div', {
14167        className: 'vjs-play-progress vjs-slider-bar',
14168        innerHTML: '<span class="vjs-control-text"><span>' + this.localize('Progress') + '</span>: 0%</span>'
14169      });
14170    };
14171
14172    /**
14173     * Enqueues updates to its own DOM as well as the DOM of its
14174     * {@link TimeTooltip} child.
14175     *
14176     * @param {Object} seekBarRect
14177     *        The `ClientRect` for the {@link SeekBar} element.
14178     *
14179     * @param {number} seekBarPoint
14180     *        A number from 0 to 1, representing a horizontal reference point
14181     *        from the left edge of the {@link SeekBar}
14182     */
14183
14184
14185    PlayProgressBar.prototype.update = function update(seekBarRect, seekBarPoint) {
14186      var _this2 = this;
14187
14188      // If there is an existing rAF ID, cancel it so we don't over-queue.
14189      if (this.rafId_) {
14190        this.cancelAnimationFrame(this.rafId_);
14191      }
14192
14193      this.rafId_ = this.requestAnimationFrame(function () {
14194        var time = _this2.player_.scrubbing() ? _this2.player_.getCache().currentTime : _this2.player_.currentTime();
14195
14196        var content = formatTime(time, _this2.player_.duration());
14197        var timeTooltip = _this2.getChild('timeTooltip');
14198
14199        if (timeTooltip) {
14200          timeTooltip.update(seekBarRect, seekBarPoint, content);
14201        }
14202      });
14203    };
14204
14205    return PlayProgressBar;
14206  }(Component);
14207
14208  /**
14209   * Default options for {@link PlayProgressBar}.
14210   *
14211   * @type {Object}
14212   * @private
14213   */
14214
14215
14216  PlayProgressBar.prototype.options_ = {
14217    children: []
14218  };
14219
14220  // Time tooltips should not be added to a player on mobile devices
14221  if (!IS_IOS && !IS_ANDROID) {
14222    PlayProgressBar.prototype.options_.children.push('timeTooltip');
14223  }
14224
14225  Component.registerComponent('PlayProgressBar', PlayProgressBar);
14226
14227  /**
14228   * @file mouse-time-display.js
14229   */
14230
14231  /**
14232   * The {@link MouseTimeDisplay} component tracks mouse movement over the
14233   * {@link ProgressControl}. It displays an indicator and a {@link TimeTooltip}
14234   * indicating the time which is represented by a given point in the
14235   * {@link ProgressControl}.
14236   *
14237   * @extends Component
14238   */
14239
14240  var MouseTimeDisplay = function (_Component) {
14241    inherits(MouseTimeDisplay, _Component);
14242
14243    /**
14244     * Creates an instance of this class.
14245     *
14246     * @param {Player} player
14247     *        The {@link Player} that this class should be attached to.
14248     *
14249     * @param {Object} [options]
14250     *        The key/value store of player options.
14251     */
14252    function MouseTimeDisplay(player, options) {
14253      classCallCheck(this, MouseTimeDisplay);
14254
14255      var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
14256
14257      _this.update = throttle(bind(_this, _this.update), 25);
14258      return _this;
14259    }
14260
14261    /**
14262     * Create the DOM element for this class.
14263     *
14264     * @return {Element}
14265     *         The element that was created.
14266     */
14267
14268
14269    MouseTimeDisplay.prototype.createEl = function createEl() {
14270      return _Component.prototype.createEl.call(this, 'div', {
14271        className: 'vjs-mouse-display'
14272      });
14273    };
14274
14275    /**
14276     * Enqueues updates to its own DOM as well as the DOM of its
14277     * {@link TimeTooltip} child.
14278     *
14279     * @param {Object} seekBarRect
14280     *        The `ClientRect` for the {@link SeekBar} element.
14281     *
14282     * @param {number} seekBarPoint
14283     *        A number from 0 to 1, representing a horizontal reference point
14284     *        from the left edge of the {@link SeekBar}
14285     */
14286
14287
14288    MouseTimeDisplay.prototype.update = function update(seekBarRect, seekBarPoint) {
14289      var _this2 = this;
14290
14291      // If there is an existing rAF ID, cancel it so we don't over-queue.
14292      if (this.rafId_) {
14293        this.cancelAnimationFrame(this.rafId_);
14294      }
14295
14296      this.rafId_ = this.requestAnimationFrame(function () {
14297        var duration = _this2.player_.duration();
14298        var content = formatTime(seekBarPoint * duration, duration);
14299
14300        _this2.el_.style.left = seekBarRect.width * seekBarPoint + 'px';
14301        _this2.getChild('timeTooltip').update(seekBarRect, seekBarPoint, content);
14302      });
14303    };
14304
14305    return MouseTimeDisplay;
14306  }(Component);
14307
14308  /**
14309   * Default options for `MouseTimeDisplay`
14310   *
14311   * @type {Object}
14312   * @private
14313   */
14314
14315
14316  MouseTimeDisplay.prototype.options_ = {
14317    children: ['timeTooltip']
14318  };
14319
14320  Component.registerComponent('MouseTimeDisplay', MouseTimeDisplay);
14321
14322  /**
14323   * @file seek-bar.js
14324   */
14325
14326  // The number of seconds the `step*` functions move the timeline.
14327  var STEP_SECONDS = 5;
14328
14329  // The interval at which the bar should update as it progresses.
14330  var UPDATE_REFRESH_INTERVAL = 30;
14331
14332  /**
14333   * Seek bar and container for the progress bars. Uses {@link PlayProgressBar}
14334   * as its `bar`.
14335   *
14336   * @extends Slider
14337   */
14338
14339  var SeekBar = function (_Slider) {
14340    inherits(SeekBar, _Slider);
14341
14342    /**
14343     * Creates an instance of this class.
14344     *
14345     * @param {Player} player
14346     *        The `Player` that this class should be attached to.
14347     *
14348     * @param {Object} [options]
14349     *        The key/value store of player options.
14350     */
14351    function SeekBar(player, options) {
14352      classCallCheck(this, SeekBar);
14353
14354      var _this = possibleConstructorReturn(this, _Slider.call(this, player, options));
14355
14356      _this.setEventHandlers_();
14357      return _this;
14358    }
14359
14360    /**
14361     * Sets the event handlers
14362     *
14363     * @private
14364     */
14365
14366
14367    SeekBar.prototype.setEventHandlers_ = function setEventHandlers_() {
14368      var _this2 = this;
14369
14370      this.update = throttle(bind(this, this.update), UPDATE_REFRESH_INTERVAL);
14371
14372      this.on(this.player_, 'timeupdate', this.update);
14373      this.on(this.player_, 'ended', this.handleEnded);
14374
14375      // when playing, let's ensure we smoothly update the play progress bar
14376      // via an interval
14377      this.updateInterval = null;
14378
14379      this.on(this.player_, ['playing'], function () {
14380        _this2.clearInterval(_this2.updateInterval);
14381
14382        _this2.updateInterval = _this2.setInterval(function () {
14383          _this2.requestAnimationFrame(function () {
14384            _this2.update();
14385          });
14386        }, UPDATE_REFRESH_INTERVAL);
14387      });
14388
14389      this.on(this.player_, ['ended', 'pause', 'waiting'], function () {
14390        _this2.clearInterval(_this2.updateInterval);
14391      });
14392
14393      this.on(this.player_, ['timeupdate', 'ended'], this.update);
14394    };
14395
14396    /**
14397     * Create the `Component`'s DOM element
14398     *
14399     * @return {Element}
14400     *         The element that was created.
14401     */
14402
14403
14404    SeekBar.prototype.createEl = function createEl$$1() {
14405      return _Slider.prototype.createEl.call(this, 'div', {
14406        className: 'vjs-progress-holder'
14407      }, {
14408        'aria-label': this.localize('Progress Bar')
14409      });
14410    };
14411
14412    /**
14413     * This function updates the play progress bar and accessibility
14414     * attributes to whatever is passed in.
14415     *
14416     * @param {number} currentTime
14417     *        The currentTime value that should be used for accessibility
14418     *
14419     * @param {number} percent
14420     *        The percentage as a decimal that the bar should be filled from 0-1.
14421     *
14422     * @private
14423     */
14424
14425
14426    SeekBar.prototype.update_ = function update_(currentTime, percent) {
14427      var duration = this.player_.duration();
14428
14429      // machine readable value of progress bar (percentage complete)
14430      this.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2));
14431
14432      // human readable value of progress bar (time complete)
14433      this.el_.setAttribute('aria-valuetext', this.localize('progress bar timing: currentTime={1} duration={2}', [formatTime(currentTime, duration), formatTime(duration, duration)], '{1} of {2}'));
14434
14435      // Update the `PlayProgressBar`.
14436      this.bar.update(getBoundingClientRect(this.el_), percent);
14437    };
14438
14439    /**
14440     * Update the seek bar's UI.
14441     *
14442     * @param {EventTarget~Event} [event]
14443     *        The `timeupdate` or `ended` event that caused this to run.
14444     *
14445     * @listens Player#timeupdate
14446     *
14447     * @returns {number}
14448     *          The current percent at a number from 0-1
14449     */
14450
14451
14452    SeekBar.prototype.update = function update(event) {
14453      var percent = _Slider.prototype.update.call(this);
14454
14455      this.update_(this.getCurrentTime_(), percent);
14456      return percent;
14457    };
14458
14459    /**
14460     * Get the value of current time but allows for smooth scrubbing,
14461     * when player can't keep up.
14462     *
14463     * @return {number}
14464     *         The current time value to display
14465     *
14466     * @private
14467     */
14468
14469
14470    SeekBar.prototype.getCurrentTime_ = function getCurrentTime_() {
14471      return this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
14472    };
14473
14474    /**
14475     * We want the seek bar to be full on ended
14476     * no matter what the actual internal values are. so we force it.
14477     *
14478     * @param {EventTarget~Event} [event]
14479     *        The `timeupdate` or `ended` event that caused this to run.
14480     *
14481     * @listens Player#ended
14482     */
14483
14484
14485    SeekBar.prototype.handleEnded = function handleEnded(event) {
14486      this.update_(this.player_.duration(), 1);
14487    };
14488
14489    /**
14490     * Get the percentage of media played so far.
14491     *
14492     * @return {number}
14493     *         The percentage of media played so far (0 to 1).
14494     */
14495
14496
14497    SeekBar.prototype.getPercent = function getPercent() {
14498      var percent = this.getCurrentTime_() / this.player_.duration();
14499
14500      return percent >= 1 ? 1 : percent || 0;
14501    };
14502
14503    /**
14504     * Handle mouse down on seek bar
14505     *
14506     * @param {EventTarget~Event} event
14507     *        The `mousedown` event that caused this to run.
14508     *
14509     * @listens mousedown
14510     */
14511
14512
14513    SeekBar.prototype.handleMouseDown = function handleMouseDown(event) {
14514      if (!isSingleLeftClick(event)) {
14515        return;
14516      }
14517
14518      // Stop event propagation to prevent double fire in progress-control.js
14519      event.stopPropagation();
14520      this.player_.scrubbing(true);
14521
14522      this.videoWasPlaying = !this.player_.paused();
14523      this.player_.pause();
14524
14525      _Slider.prototype.handleMouseDown.call(this, event);
14526    };
14527
14528    /**
14529     * Handle mouse move on seek bar
14530     *
14531     * @param {EventTarget~Event} event
14532     *        The `mousemove` event that caused this to run.
14533     *
14534     * @listens mousemove
14535     */
14536
14537
14538    SeekBar.prototype.handleMouseMove = function handleMouseMove(event) {
14539      if (!isSingleLeftClick(event)) {
14540        return;
14541      }
14542
14543      var newTime = this.calculateDistance(event) * this.player_.duration();
14544
14545      // Don't let video end while scrubbing.
14546      if (newTime === this.player_.duration()) {
14547        newTime = newTime - 0.1;
14548      }
14549
14550      // Set new time (tell player to seek to new time)
14551      this.player_.currentTime(newTime);
14552    };
14553
14554    SeekBar.prototype.enable = function enable() {
14555      _Slider.prototype.enable.call(this);
14556      var mouseTimeDisplay = this.getChild('mouseTimeDisplay');
14557
14558      if (!mouseTimeDisplay) {
14559        return;
14560      }
14561
14562      mouseTimeDisplay.show();
14563    };
14564
14565    SeekBar.prototype.disable = function disable() {
14566      _Slider.prototype.disable.call(this);
14567      var mouseTimeDisplay = this.getChild('mouseTimeDisplay');
14568
14569      if (!mouseTimeDisplay) {
14570        return;
14571      }
14572
14573      mouseTimeDisplay.hide();
14574    };
14575
14576    /**
14577     * Handle mouse up on seek bar
14578     *
14579     * @param {EventTarget~Event} event
14580     *        The `mouseup` event that caused this to run.
14581     *
14582     * @listens mouseup
14583     */
14584
14585
14586    SeekBar.prototype.handleMouseUp = function handleMouseUp(event) {
14587      _Slider.prototype.handleMouseUp.call(this, event);
14588
14589      // Stop event propagation to prevent double fire in progress-control.js
14590      if (event) {
14591        event.stopPropagation();
14592      }
14593      this.player_.scrubbing(false);
14594
14595      /**
14596       * Trigger timeupdate because we're done seeking and the time has changed.
14597       * This is particularly useful for if the player is paused to time the time displays.
14598       *
14599       * @event Tech#timeupdate
14600       * @type {EventTarget~Event}
14601       */
14602      this.player_.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
14603      if (this.videoWasPlaying) {
14604        silencePromise(this.player_.play());
14605      }
14606    };
14607
14608    /**
14609     * Move more quickly fast forward for keyboard-only users
14610     */
14611
14612
14613    SeekBar.prototype.stepForward = function stepForward() {
14614      this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS);
14615    };
14616
14617    /**
14618     * Move more quickly rewind for keyboard-only users
14619     */
14620
14621
14622    SeekBar.prototype.stepBack = function stepBack() {
14623      this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS);
14624    };
14625
14626    /**
14627     * Toggles the playback state of the player
14628     * This gets called when enter or space is used on the seekbar
14629     *
14630     * @param {EventTarget~Event} event
14631     *        The `keydown` event that caused this function to be called
14632     *
14633     */
14634
14635
14636    SeekBar.prototype.handleAction = function handleAction(event) {
14637      if (this.player_.paused()) {
14638        this.player_.play();
14639      } else {
14640        this.player_.pause();
14641      }
14642    };
14643
14644    /**
14645     * Called when this SeekBar has focus and a key gets pressed down. By
14646     * default it will call `this.handleAction` when the key is space or enter.
14647     *
14648     * @param {EventTarget~Event} event
14649     *        The `keydown` event that caused this function to be called.
14650     *
14651     * @listens keydown
14652     */
14653
14654
14655    SeekBar.prototype.handleKeyPress = function handleKeyPress(event) {
14656
14657      // Support Space (32) or Enter (13) key operation to fire a click event
14658      if (event.which === 32 || event.which === 13) {
14659        event.preventDefault();
14660        this.handleAction(event);
14661      } else if (_Slider.prototype.handleKeyPress) {
14662
14663        // Pass keypress handling up for unsupported keys
14664        _Slider.prototype.handleKeyPress.call(this, event);
14665      }
14666    };
14667
14668    return SeekBar;
14669  }(Slider);
14670
14671  /**
14672   * Default options for the `SeekBar`
14673   *
14674   * @type {Object}
14675   * @private
14676   */
14677
14678
14679  SeekBar.prototype.options_ = {
14680    children: ['loadProgressBar', 'playProgressBar'],
14681    barName: 'playProgressBar'
14682  };
14683
14684  // MouseTimeDisplay tooltips should not be added to a player on mobile devices
14685  if (!IS_IOS && !IS_ANDROID) {
14686    SeekBar.prototype.options_.children.splice(1, 0, 'mouseTimeDisplay');
14687  }
14688
14689  /**
14690   * Call the update event for this Slider when this event happens on the player.
14691   *
14692   * @type {string}
14693   */
14694  SeekBar.prototype.playerEvent = 'timeupdate';
14695
14696  Component.registerComponent('SeekBar', SeekBar);
14697
14698  /**
14699   * @file progress-control.js
14700   */
14701
14702  /**
14703   * The Progress Control component contains the seek bar, load progress,
14704   * and play progress.
14705   *
14706   * @extends Component
14707   */
14708
14709  var ProgressControl = function (_Component) {
14710    inherits(ProgressControl, _Component);
14711
14712    /**
14713     * Creates an instance of this class.
14714     *
14715     * @param {Player} player
14716     *        The `Player` that this class should be attached to.
14717     *
14718     * @param {Object} [options]
14719     *        The key/value store of player options.
14720     */
14721    function ProgressControl(player, options) {
14722      classCallCheck(this, ProgressControl);
14723
14724      var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
14725
14726      _this.handleMouseMove = throttle(bind(_this, _this.handleMouseMove), 25);
14727      _this.throttledHandleMouseSeek = throttle(bind(_this, _this.handleMouseSeek), 25);
14728
14729      _this.enable();
14730      return _this;
14731    }
14732
14733    /**
14734     * Create the `Component`'s DOM element
14735     *
14736     * @return {Element}
14737     *         The element that was created.
14738     */
14739
14740
14741    ProgressControl.prototype.createEl = function createEl$$1() {
14742      return _Component.prototype.createEl.call(this, 'div', {
14743        className: 'vjs-progress-control vjs-control'
14744      });
14745    };
14746
14747    /**
14748     * When the mouse moves over the `ProgressControl`, the pointer position
14749     * gets passed down to the `MouseTimeDisplay` component.
14750     *
14751     * @param {EventTarget~Event} event
14752     *        The `mousemove` event that caused this function to run.
14753     *
14754     * @listen mousemove
14755     */
14756
14757
14758    ProgressControl.prototype.handleMouseMove = function handleMouseMove(event) {
14759      var seekBar = this.getChild('seekBar');
14760
14761      if (seekBar) {
14762        var mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay');
14763        var seekBarEl = seekBar.el();
14764        var seekBarRect = getBoundingClientRect(seekBarEl);
14765        var seekBarPoint = getPointerPosition(seekBarEl, event).x;
14766
14767        // The default skin has a gap on either side of the `SeekBar`. This means
14768        // that it's possible to trigger this behavior outside the boundaries of
14769        // the `SeekBar`. This ensures we stay within it at all times.
14770        if (seekBarPoint > 1) {
14771          seekBarPoint = 1;
14772        } else if (seekBarPoint < 0) {
14773          seekBarPoint = 0;
14774        }
14775
14776        if (mouseTimeDisplay) {
14777          mouseTimeDisplay.update(seekBarRect, seekBarPoint);
14778        }
14779      }
14780    };
14781
14782    /**
14783     * A throttled version of the {@link ProgressControl#handleMouseSeek} listener.
14784     *
14785     * @method ProgressControl#throttledHandleMouseSeek
14786     * @param {EventTarget~Event} event
14787     *        The `mousemove` event that caused this function to run.
14788     *
14789     * @listen mousemove
14790     * @listen touchmove
14791     */
14792
14793    /**
14794     * Handle `mousemove` or `touchmove` events on the `ProgressControl`.
14795     *
14796     * @param {EventTarget~Event} event
14797     *        `mousedown` or `touchstart` event that triggered this function
14798     *
14799     * @listens mousemove
14800     * @listens touchmove
14801     */
14802
14803
14804    ProgressControl.prototype.handleMouseSeek = function handleMouseSeek(event) {
14805      var seekBar = this.getChild('seekBar');
14806
14807      if (seekBar) {
14808        seekBar.handleMouseMove(event);
14809      }
14810    };
14811
14812    /**
14813     * Are controls are currently enabled for this progress control.
14814     *
14815     * @return {boolean}
14816     *         true if controls are enabled, false otherwise
14817     */
14818
14819
14820    ProgressControl.prototype.enabled = function enabled() {
14821      return this.enabled_;
14822    };
14823
14824    /**
14825     * Disable all controls on the progress control and its children
14826     */
14827
14828
14829    ProgressControl.prototype.disable = function disable() {
14830      this.children().forEach(function (child) {
14831        return child.disable && child.disable();
14832      });
14833
14834      if (!this.enabled()) {
14835        return;
14836      }
14837
14838      this.off(['mousedown', 'touchstart'], this.handleMouseDown);
14839      this.off(this.el_, 'mousemove', this.handleMouseMove);
14840      this.handleMouseUp();
14841
14842      this.addClass('disabled');
14843
14844      this.enabled_ = false;
14845    };
14846
14847    /**
14848     * Enable all controls on the progress control and its children
14849     */
14850
14851
14852    ProgressControl.prototype.enable = function enable() {
14853      this.children().forEach(function (child) {
14854        return child.enable && child.enable();
14855      });
14856
14857      if (this.enabled()) {
14858        return;
14859      }
14860
14861      this.on(['mousedown', 'touchstart'], this.handleMouseDown);
14862      this.on(this.el_, 'mousemove', this.handleMouseMove);
14863      this.removeClass('disabled');
14864
14865      this.enabled_ = true;
14866    };
14867
14868    /**
14869     * Handle `mousedown` or `touchstart` events on the `ProgressControl`.
14870     *
14871     * @param {EventTarget~Event} event
14872     *        `mousedown` or `touchstart` event that triggered this function
14873     *
14874     * @listens mousedown
14875     * @listens touchstart
14876     */
14877
14878
14879    ProgressControl.prototype.handleMouseDown = function handleMouseDown(event) {
14880      var doc = this.el_.ownerDocument;
14881      var seekBar = this.getChild('seekBar');
14882
14883      if (seekBar) {
14884        seekBar.handleMouseDown(event);
14885      }
14886
14887      this.on(doc, 'mousemove', this.throttledHandleMouseSeek);
14888      this.on(doc, 'touchmove', this.throttledHandleMouseSeek);
14889      this.on(doc, 'mouseup', this.handleMouseUp);
14890      this.on(doc, 'touchend', this.handleMouseUp);
14891    };
14892
14893    /**
14894     * Handle `mouseup` or `touchend` events on the `ProgressControl`.
14895     *
14896     * @param {EventTarget~Event} event
14897     *        `mouseup` or `touchend` event that triggered this function.
14898     *
14899     * @listens touchend
14900     * @listens mouseup
14901     */
14902
14903
14904    ProgressControl.prototype.handleMouseUp = function handleMouseUp(event) {
14905      var doc = this.el_.ownerDocument;
14906      var seekBar = this.getChild('seekBar');
14907
14908      if (seekBar) {
14909        seekBar.handleMouseUp(event);
14910      }
14911
14912      this.off(doc, 'mousemove', this.throttledHandleMouseSeek);
14913      this.off(doc, 'touchmove', this.throttledHandleMouseSeek);
14914      this.off(doc, 'mouseup', this.handleMouseUp);
14915      this.off(doc, 'touchend', this.handleMouseUp);
14916    };
14917
14918    return ProgressControl;
14919  }(Component);
14920
14921  /**
14922   * Default options for `ProgressControl`
14923   *
14924   * @type {Object}
14925   * @private
14926   */
14927
14928
14929  ProgressControl.prototype.options_ = {
14930    children: ['seekBar']
14931  };
14932
14933  Component.registerComponent('ProgressControl', ProgressControl);
14934
14935  /**
14936   * @file fullscreen-toggle.js
14937   */
14938
14939  /**
14940   * Toggle fullscreen video
14941   *
14942   * @extends Button
14943   */
14944
14945  var FullscreenToggle = function (_Button) {
14946    inherits(FullscreenToggle, _Button);
14947
14948    /**
14949     * Creates an instance of this class.
14950     *
14951     * @param {Player} player
14952     *        The `Player` that this class should be attached to.
14953     *
14954     * @param {Object} [options]
14955     *        The key/value store of player options.
14956     */
14957    function FullscreenToggle(player, options) {
14958      classCallCheck(this, FullscreenToggle);
14959
14960      var _this = possibleConstructorReturn(this, _Button.call(this, player, options));
14961
14962      _this.on(player, 'fullscreenchange', _this.handleFullscreenChange);
14963
14964      if (document_1[FullscreenApi.fullscreenEnabled] === false) {
14965        _this.disable();
14966      }
14967      return _this;
14968    }
14969
14970    /**
14971     * Builds the default DOM `className`.
14972     *
14973     * @return {string}
14974     *         The DOM `className` for this object.
14975     */
14976
14977
14978    FullscreenToggle.prototype.buildCSSClass = function buildCSSClass() {
14979      return 'vjs-fullscreen-control ' + _Button.prototype.buildCSSClass.call(this);
14980    };
14981
14982    /**
14983     * Handles fullscreenchange on the player and change control text accordingly.
14984     *
14985     * @param {EventTarget~Event} [event]
14986     *        The {@link Player#fullscreenchange} event that caused this function to be
14987     *        called.
14988     *
14989     * @listens Player#fullscreenchange
14990     */
14991
14992
14993    FullscreenToggle.prototype.handleFullscreenChange = function handleFullscreenChange(event) {
14994      if (this.player_.isFullscreen()) {
14995        this.controlText('Non-Fullscreen');
14996      } else {
14997        this.controlText('Fullscreen');
14998      }
14999    };
15000
15001    /**
15002     * This gets called when an `FullscreenToggle` is "clicked". See
15003     * {@link ClickableComponent} for more detailed information on what a click can be.
15004     *
15005     * @param {EventTarget~Event} [event]
15006     *        The `keydown`, `tap`, or `click` event that caused this function to be
15007     *        called.
15008     *
15009     * @listens tap
15010     * @listens click
15011     */
15012
15013
15014    FullscreenToggle.prototype.handleClick = function handleClick(event) {
15015      if (!this.player_.isFullscreen()) {
15016        this.player_.requestFullscreen();
15017      } else {
15018        this.player_.exitFullscreen();
15019      }
15020    };
15021
15022    return FullscreenToggle;
15023  }(Button);
15024
15025  /**
15026   * The text that should display over the `FullscreenToggle`s controls. Added for localization.
15027   *
15028   * @type {string}
15029   * @private
15030   */
15031
15032
15033  FullscreenToggle.prototype.controlText_ = 'Fullscreen';
15034
15035  Component.registerComponent('FullscreenToggle', FullscreenToggle);
15036
15037  /**
15038   * Check if volume control is supported and if it isn't hide the
15039   * `Component` that was passed  using the `vjs-hidden` class.
15040   *
15041   * @param {Component} self
15042   *        The component that should be hidden if volume is unsupported
15043   *
15044   * @param {Player} player
15045   *        A reference to the player
15046   *
15047   * @private
15048   */
15049  var checkVolumeSupport = function checkVolumeSupport(self, player) {
15050    // hide volume controls when they're not supported by the current tech
15051    if (player.tech_ && !player.tech_.featuresVolumeControl) {
15052      self.addClass('vjs-hidden');
15053    }
15054
15055    self.on(player, 'loadstart', function () {
15056      if (!player.tech_.featuresVolumeControl) {
15057        self.addClass('vjs-hidden');
15058      } else {
15059        self.removeClass('vjs-hidden');
15060      }
15061    });
15062  };
15063
15064  /**
15065   * @file volume-level.js
15066   */
15067
15068  /**
15069   * Shows volume level
15070   *
15071   * @extends Component
15072   */
15073
15074  var VolumeLevel = function (_Component) {
15075    inherits(VolumeLevel, _Component);
15076
15077    function VolumeLevel() {
15078      classCallCheck(this, VolumeLevel);
15079      return possibleConstructorReturn(this, _Component.apply(this, arguments));
15080    }
15081
15082    /**
15083     * Create the `Component`'s DOM element
15084     *
15085     * @return {Element}
15086     *         The element that was created.
15087     */
15088    VolumeLevel.prototype.createEl = function createEl() {
15089      return _Component.prototype.createEl.call(this, 'div', {
15090        className: 'vjs-volume-level',
15091        innerHTML: '<span class="vjs-control-text"></span>'
15092      });
15093    };
15094
15095    return VolumeLevel;
15096  }(Component);
15097
15098  Component.registerComponent('VolumeLevel', VolumeLevel);
15099
15100  /**
15101   * @file volume-bar.js
15102   */
15103
15104  /**
15105   * The bar that contains the volume level and can be clicked on to adjust the level
15106   *
15107   * @extends Slider
15108   */
15109
15110  var VolumeBar = function (_Slider) {
15111    inherits(VolumeBar, _Slider);
15112
15113    /**
15114     * Creates an instance of this class.
15115     *
15116     * @param {Player} player
15117     *        The `Player` that this class should be attached to.
15118     *
15119     * @param {Object} [options]
15120     *        The key/value store of player options.
15121     */
15122    function VolumeBar(player, options) {
15123      classCallCheck(this, VolumeBar);
15124
15125      var _this = possibleConstructorReturn(this, _Slider.call(this, player, options));
15126
15127      _this.on('slideractive', _this.updateLastVolume_);
15128      _this.on(player, 'volumechange', _this.updateARIAAttributes);
15129      player.ready(function () {
15130        return _this.updateARIAAttributes();
15131      });
15132      return _this;
15133    }
15134
15135    /**
15136     * Create the `Component`'s DOM element
15137     *
15138     * @return {Element}
15139     *         The element that was created.
15140     */
15141
15142
15143    VolumeBar.prototype.createEl = function createEl$$1() {
15144      return _Slider.prototype.createEl.call(this, 'div', {
15145        className: 'vjs-volume-bar vjs-slider-bar'
15146      }, {
15147        'aria-label': this.localize('Volume Level'),
15148        'aria-live': 'polite'
15149      });
15150    };
15151
15152    /**
15153     * Handle mouse down on volume bar
15154     *
15155     * @param {EventTarget~Event} event
15156     *        The `mousedown` event that caused this to run.
15157     *
15158     * @listens mousedown
15159     */
15160
15161
15162    VolumeBar.prototype.handleMouseDown = function handleMouseDown(event) {
15163      if (!isSingleLeftClick(event)) {
15164        return;
15165      }
15166
15167      _Slider.prototype.handleMouseDown.call(this, event);
15168    };
15169
15170    /**
15171     * Handle movement events on the {@link VolumeMenuButton}.
15172     *
15173     * @param {EventTarget~Event} event
15174     *        The event that caused this function to run.
15175     *
15176     * @listens mousemove
15177     */
15178
15179
15180    VolumeBar.prototype.handleMouseMove = function handleMouseMove(event) {
15181      if (!isSingleLeftClick(event)) {
15182        return;
15183      }
15184
15185      this.checkMuted();
15186      this.player_.volume(this.calculateDistance(event));
15187    };
15188
15189    /**
15190     * If the player is muted unmute it.
15191     */
15192
15193
15194    VolumeBar.prototype.checkMuted = function checkMuted() {
15195      if (this.player_.muted()) {
15196        this.player_.muted(false);
15197      }
15198    };
15199
15200    /**
15201     * Get percent of volume level
15202     *
15203     * @return {number}
15204     *         Volume level percent as a decimal number.
15205     */
15206
15207
15208    VolumeBar.prototype.getPercent = function getPercent() {
15209      if (this.player_.muted()) {
15210        return 0;
15211      }
15212      return this.player_.volume();
15213    };
15214
15215    /**
15216     * Increase volume level for keyboard users
15217     */
15218
15219
15220    VolumeBar.prototype.stepForward = function stepForward() {
15221      this.checkMuted();
15222      this.player_.volume(this.player_.volume() + 0.1);
15223    };
15224
15225    /**
15226     * Decrease volume level for keyboard users
15227     */
15228
15229
15230    VolumeBar.prototype.stepBack = function stepBack() {
15231      this.checkMuted();
15232      this.player_.volume(this.player_.volume() - 0.1);
15233    };
15234
15235    /**
15236     * Update ARIA accessibility attributes
15237     *
15238     * @param {EventTarget~Event} [event]
15239     *        The `volumechange` event that caused this function to run.
15240     *
15241     * @listens Player#volumechange
15242     */
15243
15244
15245    VolumeBar.prototype.updateARIAAttributes = function updateARIAAttributes(event) {
15246      var ariaValue = this.player_.muted() ? 0 : this.volumeAsPercentage_();
15247
15248      this.el_.setAttribute('aria-valuenow', ariaValue);
15249      this.el_.setAttribute('aria-valuetext', ariaValue + '%');
15250    };
15251
15252    /**
15253     * Returns the current value of the player volume as a percentage
15254     *
15255     * @private
15256     */
15257
15258
15259    VolumeBar.prototype.volumeAsPercentage_ = function volumeAsPercentage_() {
15260      return Math.round(this.player_.volume() * 100);
15261    };
15262
15263    /**
15264     * When user starts dragging the VolumeBar, store the volume and listen for
15265     * the end of the drag. When the drag ends, if the volume was set to zero,
15266     * set lastVolume to the stored volume.
15267     *
15268     * @listens slideractive
15269     * @private
15270     */
15271
15272
15273    VolumeBar.prototype.updateLastVolume_ = function updateLastVolume_() {
15274      var _this2 = this;
15275
15276      var volumeBeforeDrag = this.player_.volume();
15277
15278      this.one('sliderinactive', function () {
15279        if (_this2.player_.volume() === 0) {
15280          _this2.player_.lastVolume_(volumeBeforeDrag);
15281        }
15282      });
15283    };
15284
15285    return VolumeBar;
15286  }(Slider);
15287
15288  /**
15289   * Default options for the `VolumeBar`
15290   *
15291   * @type {Object}
15292   * @private
15293   */
15294
15295
15296  VolumeBar.prototype.options_ = {
15297    children: ['volumeLevel'],
15298    barName: 'volumeLevel'
15299  };
15300
15301  /**
15302   * Call the update event for this Slider when this event happens on the player.
15303   *
15304   * @type {string}
15305   */
15306  VolumeBar.prototype.playerEvent = 'volumechange';
15307
15308  Component.registerComponent('VolumeBar', VolumeBar);
15309
15310  /**
15311   * @file volume-control.js
15312   */
15313
15314  /**
15315   * The component for controlling the volume level
15316   *
15317   * @extends Component
15318   */
15319
15320  var VolumeControl = function (_Component) {
15321    inherits(VolumeControl, _Component);
15322
15323    /**
15324     * Creates an instance of this class.
15325     *
15326     * @param {Player} player
15327     *        The `Player` that this class should be attached to.
15328     *
15329     * @param {Object} [options={}]
15330     *        The key/value store of player options.
15331     */
15332    function VolumeControl(player) {
15333      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
15334      classCallCheck(this, VolumeControl);
15335
15336      options.vertical = options.vertical || false;
15337
15338      // Pass the vertical option down to the VolumeBar if
15339      // the VolumeBar is turned on.
15340      if (typeof options.volumeBar === 'undefined' || isPlain(options.volumeBar)) {
15341        options.volumeBar = options.volumeBar || {};
15342        options.volumeBar.vertical = options.vertical;
15343      }
15344
15345      // hide this control if volume support is missing
15346      var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
15347
15348      checkVolumeSupport(_this, player);
15349
15350      _this.throttledHandleMouseMove = throttle(bind(_this, _this.handleMouseMove), 25);
15351
15352      _this.on('mousedown', _this.handleMouseDown);
15353      _this.on('touchstart', _this.handleMouseDown);
15354
15355      // while the slider is active (the mouse has been pressed down and
15356      // is dragging) or in focus we do not want to hide the VolumeBar
15357      _this.on(_this.volumeBar, ['focus', 'slideractive'], function () {
15358        _this.volumeBar.addClass('vjs-slider-active');
15359        _this.addClass('vjs-slider-active');
15360        _this.trigger('slideractive');
15361      });
15362
15363      _this.on(_this.volumeBar, ['blur', 'sliderinactive'], function () {
15364        _this.volumeBar.removeClass('vjs-slider-active');
15365        _this.removeClass('vjs-slider-active');
15366        _this.trigger('sliderinactive');
15367      });
15368      return _this;
15369    }
15370
15371    /**
15372     * Create the `Component`'s DOM element
15373     *
15374     * @return {Element}
15375     *         The element that was created.
15376     */
15377
15378
15379    VolumeControl.prototype.createEl = function createEl() {
15380      var orientationClass = 'vjs-volume-horizontal';
15381
15382      if (this.options_.vertical) {
15383        orientationClass = 'vjs-volume-vertical';
15384      }
15385
15386      return _Component.prototype.createEl.call(this, 'div', {
15387        className: 'vjs-volume-control vjs-control ' + orientationClass
15388      });
15389    };
15390
15391    /**
15392     * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
15393     *
15394     * @param {EventTarget~Event} event
15395     *        `mousedown` or `touchstart` event that triggered this function
15396     *
15397     * @listens mousedown
15398     * @listens touchstart
15399     */
15400
15401
15402    VolumeControl.prototype.handleMouseDown = function handleMouseDown(event) {
15403      var doc = this.el_.ownerDocument;
15404
15405      this.on(doc, 'mousemove', this.throttledHandleMouseMove);
15406      this.on(doc, 'touchmove', this.throttledHandleMouseMove);
15407      this.on(doc, 'mouseup', this.handleMouseUp);
15408      this.on(doc, 'touchend', this.handleMouseUp);
15409    };
15410
15411    /**
15412     * Handle `mouseup` or `touchend` events on the `VolumeControl`.
15413     *
15414     * @param {EventTarget~Event} event
15415     *        `mouseup` or `touchend` event that triggered this function.
15416     *
15417     * @listens touchend
15418     * @listens mouseup
15419     */
15420
15421
15422    VolumeControl.prototype.handleMouseUp = function handleMouseUp(event) {
15423      var doc = this.el_.ownerDocument;
15424
15425      this.off(doc, 'mousemove', this.throttledHandleMouseMove);
15426      this.off(doc, 'touchmove', this.throttledHandleMouseMove);
15427      this.off(doc, 'mouseup', this.handleMouseUp);
15428      this.off(doc, 'touchend', this.handleMouseUp);
15429    };
15430
15431    /**
15432     * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
15433     *
15434     * @param {EventTarget~Event} event
15435     *        `mousedown` or `touchstart` event that triggered this function
15436     *
15437     * @listens mousedown
15438     * @listens touchstart
15439     */
15440
15441
15442    VolumeControl.prototype.handleMouseMove = function handleMouseMove(event) {
15443      this.volumeBar.handleMouseMove(event);
15444    };
15445
15446    return VolumeControl;
15447  }(Component);
15448
15449  /**
15450   * Default options for the `VolumeControl`
15451   *
15452   * @type {Object}
15453   * @private
15454   */
15455
15456
15457  VolumeControl.prototype.options_ = {
15458    children: ['volumeBar']
15459  };
15460
15461  Component.registerComponent('VolumeControl', VolumeControl);
15462
15463  /**
15464   * Check if muting volume is supported and if it isn't hide the mute toggle
15465   * button.
15466   *
15467   * @param {Component} self
15468   *        A reference to the mute toggle button
15469   *
15470   * @param {Player} player
15471   *        A reference to the player
15472   *
15473   * @private
15474   */
15475  var checkMuteSupport = function checkMuteSupport(self, player) {
15476    // hide mute toggle button if it's not supported by the current tech
15477    if (player.tech_ && !player.tech_.featuresMuteControl) {
15478      self.addClass('vjs-hidden');
15479    }
15480
15481    self.on(player, 'loadstart', function () {
15482      if (!player.tech_.featuresMuteControl) {
15483        self.addClass('vjs-hidden');
15484      } else {
15485        self.removeClass('vjs-hidden');
15486      }
15487    });
15488  };
15489
15490  /**
15491   * @file mute-toggle.js
15492   */
15493
15494  /**
15495   * A button component for muting the audio.
15496   *
15497   * @extends Button
15498   */
15499
15500  var MuteToggle = function (_Button) {
15501    inherits(MuteToggle, _Button);
15502
15503    /**
15504     * Creates an instance of this class.
15505     *
15506     * @param {Player} player
15507     *        The `Player` that this class should be attached to.
15508     *
15509     * @param {Object} [options]
15510     *        The key/value store of player options.
15511     */
15512    function MuteToggle(player, options) {
15513      classCallCheck(this, MuteToggle);
15514
15515      // hide this control if volume support is missing
15516      var _this = possibleConstructorReturn(this, _Button.call(this, player, options));
15517
15518      checkMuteSupport(_this, player);
15519
15520      _this.on(player, ['loadstart', 'volumechange'], _this.update);
15521      return _this;
15522    }
15523
15524    /**
15525     * Builds the default DOM `className`.
15526     *
15527     * @return {string}
15528     *         The DOM `className` for this object.
15529     */
15530
15531
15532    MuteToggle.prototype.buildCSSClass = function buildCSSClass() {
15533      return 'vjs-mute-control ' + _Button.prototype.buildCSSClass.call(this);
15534    };
15535
15536    /**
15537     * This gets called when an `MuteToggle` is "clicked". See
15538     * {@link ClickableComponent} for more detailed information on what a click can be.
15539     *
15540     * @param {EventTarget~Event} [event]
15541     *        The `keydown`, `tap`, or `click` event that caused this function to be
15542     *        called.
15543     *
15544     * @listens tap
15545     * @listens click
15546     */
15547
15548
15549    MuteToggle.prototype.handleClick = function handleClick(event) {
15550      var vol = this.player_.volume();
15551      var lastVolume = this.player_.lastVolume_();
15552
15553      if (vol === 0) {
15554        var volumeToSet = lastVolume < 0.1 ? 0.1 : lastVolume;
15555
15556        this.player_.volume(volumeToSet);
15557        this.player_.muted(false);
15558      } else {
15559        this.player_.muted(this.player_.muted() ? false : true);
15560      }
15561    };
15562
15563    /**
15564     * Update the `MuteToggle` button based on the state of `volume` and `muted`
15565     * on the player.
15566     *
15567     * @param {EventTarget~Event} [event]
15568     *        The {@link Player#loadstart} event if this function was called
15569     *        through an event.
15570     *
15571     * @listens Player#loadstart
15572     * @listens Player#volumechange
15573     */
15574
15575
15576    MuteToggle.prototype.update = function update(event) {
15577      this.updateIcon_();
15578      this.updateControlText_();
15579    };
15580
15581    /**
15582     * Update the appearance of the `MuteToggle` icon.
15583     *
15584     * Possible states (given `level` variable below):
15585     * - 0: crossed out
15586     * - 1: zero bars of volume
15587     * - 2: one bar of volume
15588     * - 3: two bars of volume
15589     *
15590     * @private
15591     */
15592
15593
15594    MuteToggle.prototype.updateIcon_ = function updateIcon_() {
15595      var vol = this.player_.volume();
15596      var level = 3;
15597
15598      // in iOS when a player is loaded with muted attribute
15599      // and volume is changed with a native mute button
15600      // we want to make sure muted state is updated
15601      if (IS_IOS) {
15602        this.player_.muted(this.player_.tech_.el_.muted);
15603      }
15604
15605      if (vol === 0 || this.player_.muted()) {
15606        level = 0;
15607      } else if (vol < 0.33) {
15608        level = 1;
15609      } else if (vol < 0.67) {
15610        level = 2;
15611      }
15612
15613      // TODO improve muted icon classes
15614      for (var i = 0; i < 4; i++) {
15615        removeClass(this.el_, 'vjs-vol-' + i);
15616      }
15617      addClass(this.el_, 'vjs-vol-' + level);
15618    };
15619
15620    /**
15621     * If `muted` has changed on the player, update the control text
15622     * (`title` attribute on `vjs-mute-control` element and content of
15623     * `vjs-control-text` element).
15624     *
15625     * @private
15626     */
15627
15628
15629    MuteToggle.prototype.updateControlText_ = function updateControlText_() {
15630      var soundOff = this.player_.muted() || this.player_.volume() === 0;
15631      var text = soundOff ? 'Unmute' : 'Mute';
15632
15633      if (this.controlText() !== text) {
15634        this.controlText(text);
15635      }
15636    };
15637
15638    return MuteToggle;
15639  }(Button);
15640
15641  /**
15642   * The text that should display over the `MuteToggle`s controls. Added for localization.
15643   *
15644   * @type {string}
15645   * @private
15646   */
15647
15648
15649  MuteToggle.prototype.controlText_ = 'Mute';
15650
15651  Component.registerComponent('MuteToggle', MuteToggle);
15652
15653  /**
15654   * @file volume-control.js
15655   */
15656
15657  /**
15658   * A Component to contain the MuteToggle and VolumeControl so that
15659   * they can work together.
15660   *
15661   * @extends Component
15662   */
15663
15664  var VolumePanel = function (_Component) {
15665    inherits(VolumePanel, _Component);
15666
15667    /**
15668     * Creates an instance of this class.
15669     *
15670     * @param {Player} player
15671     *        The `Player` that this class should be attached to.
15672     *
15673     * @param {Object} [options={}]
15674     *        The key/value store of player options.
15675     */
15676    function VolumePanel(player) {
15677      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
15678      classCallCheck(this, VolumePanel);
15679
15680      if (typeof options.inline !== 'undefined') {
15681        options.inline = options.inline;
15682      } else {
15683        options.inline = true;
15684      }
15685
15686      // pass the inline option down to the VolumeControl as vertical if
15687      // the VolumeControl is on.
15688      if (typeof options.volumeControl === 'undefined' || isPlain(options.volumeControl)) {
15689        options.volumeControl = options.volumeControl || {};
15690        options.volumeControl.vertical = !options.inline;
15691      }
15692
15693      var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
15694
15695      _this.on(player, ['loadstart'], _this.volumePanelState_);
15696
15697      // while the slider is active (the mouse has been pressed down and
15698      // is dragging) we do not want to hide the VolumeBar
15699      _this.on(_this.volumeControl, ['slideractive'], _this.sliderActive_);
15700
15701      _this.on(_this.volumeControl, ['sliderinactive'], _this.sliderInactive_);
15702      return _this;
15703    }
15704
15705    /**
15706     * Add vjs-slider-active class to the VolumePanel
15707     *
15708     * @listens VolumeControl#slideractive
15709     * @private
15710     */
15711
15712
15713    VolumePanel.prototype.sliderActive_ = function sliderActive_() {
15714      this.addClass('vjs-slider-active');
15715    };
15716
15717    /**
15718     * Removes vjs-slider-active class to the VolumePanel
15719     *
15720     * @listens VolumeControl#sliderinactive
15721     * @private
15722     */
15723
15724
15725    VolumePanel.prototype.sliderInactive_ = function sliderInactive_() {
15726      this.removeClass('vjs-slider-active');
15727    };
15728
15729    /**
15730     * Adds vjs-hidden or vjs-mute-toggle-only to the VolumePanel
15731     * depending on MuteToggle and VolumeControl state
15732     *
15733     * @listens Player#loadstart
15734     * @private
15735     */
15736
15737
15738    VolumePanel.prototype.volumePanelState_ = function volumePanelState_() {
15739      // hide volume panel if neither volume control or mute toggle
15740      // are displayed
15741      if (this.volumeControl.hasClass('vjs-hidden') && this.muteToggle.hasClass('vjs-hidden')) {
15742        this.addClass('vjs-hidden');
15743      }
15744
15745      // if only mute toggle is visible we don't want
15746      // volume panel expanding when hovered or active
15747      if (this.volumeControl.hasClass('vjs-hidden') && !this.muteToggle.hasClass('vjs-hidden')) {
15748        this.addClass('vjs-mute-toggle-only');
15749      }
15750    };
15751
15752    /**
15753     * Create the `Component`'s DOM element
15754     *
15755     * @return {Element}
15756     *         The element that was created.
15757     */
15758
15759
15760    VolumePanel.prototype.createEl = function createEl() {
15761      var orientationClass = 'vjs-volume-panel-horizontal';
15762
15763      if (!this.options_.inline) {
15764        orientationClass = 'vjs-volume-panel-vertical';
15765      }
15766
15767      return _Component.prototype.createEl.call(this, 'div', {
15768        className: 'vjs-volume-panel vjs-control ' + orientationClass
15769      });
15770    };
15771
15772    return VolumePanel;
15773  }(Component);
15774
15775  /**
15776   * Default options for the `VolumeControl`
15777   *
15778   * @type {Object}
15779   * @private
15780   */
15781
15782
15783  VolumePanel.prototype.options_ = {
15784    children: ['muteToggle', 'volumeControl']
15785  };
15786
15787  Component.registerComponent('VolumePanel', VolumePanel);
15788
15789  /**
15790   * @file menu.js
15791   */
15792
15793  /**
15794   * The Menu component is used to build popup menus, including subtitle and
15795   * captions selection menus.
15796   *
15797   * @extends Component
15798   */
15799
15800  var Menu = function (_Component) {
15801    inherits(Menu, _Component);
15802
15803    /**
15804     * Create an instance of this class.
15805     *
15806     * @param {Player} player
15807     *        the player that this component should attach to
15808     *
15809     * @param {Object} [options]
15810     *        Object of option names and values
15811     *
15812     */
15813    function Menu(player, options) {
15814      classCallCheck(this, Menu);
15815
15816      var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
15817
15818      if (options) {
15819        _this.menuButton_ = options.menuButton;
15820      }
15821
15822      _this.focusedChild_ = -1;
15823
15824      _this.on('keydown', _this.handleKeyPress);
15825      return _this;
15826    }
15827
15828    /**
15829     * Add a {@link MenuItem} to the menu.
15830     *
15831     * @param {Object|string} component
15832     *        The name or instance of the `MenuItem` to add.
15833     *
15834     */
15835
15836
15837    Menu.prototype.addItem = function addItem(component) {
15838      this.addChild(component);
15839      component.on('click', bind(this, function (event) {
15840        // Unpress the associated MenuButton, and move focus back to it
15841        if (this.menuButton_) {
15842          this.menuButton_.unpressButton();
15843
15844          // don't focus menu button if item is a caption settings item
15845          // because focus will move elsewhere
15846          if (component.name() !== 'CaptionSettingsMenuItem') {
15847            this.menuButton_.focus();
15848          }
15849        }
15850      }));
15851    };
15852
15853    /**
15854     * Create the `Menu`s DOM element.
15855     *
15856     * @return {Element}
15857     *         the element that was created
15858     */
15859
15860
15861    Menu.prototype.createEl = function createEl$$1() {
15862      var contentElType = this.options_.contentElType || 'ul';
15863
15864      this.contentEl_ = createEl(contentElType, {
15865        className: 'vjs-menu-content'
15866      });
15867
15868      this.contentEl_.setAttribute('role', 'menu');
15869
15870      var el = _Component.prototype.createEl.call(this, 'div', {
15871        append: this.contentEl_,
15872        className: 'vjs-menu'
15873      });
15874
15875      el.appendChild(this.contentEl_);
15876
15877      // Prevent clicks from bubbling up. Needed for Menu Buttons,
15878      // where a click on the parent is significant
15879      on(el, 'click', function (event) {
15880        event.preventDefault();
15881        event.stopImmediatePropagation();
15882      });
15883
15884      return el;
15885    };
15886
15887    Menu.prototype.dispose = function dispose() {
15888      this.contentEl_ = null;
15889
15890      _Component.prototype.dispose.call(this);
15891    };
15892
15893    /**
15894     * Handle a `keydown` event on this menu. This listener is added in the constructor.
15895     *
15896     * @param {EventTarget~Event} event
15897     *        A `keydown` event that happened on the menu.
15898     *
15899     * @listens keydown
15900     */
15901
15902
15903    Menu.prototype.handleKeyPress = function handleKeyPress(event) {
15904      // Left and Down Arrows
15905      if (event.which === 37 || event.which === 40) {
15906        event.preventDefault();
15907        this.stepForward();
15908
15909        // Up and Right Arrows
15910      } else if (event.which === 38 || event.which === 39) {
15911        event.preventDefault();
15912        this.stepBack();
15913      }
15914    };
15915
15916    /**
15917     * Move to next (lower) menu item for keyboard users.
15918     */
15919
15920
15921    Menu.prototype.stepForward = function stepForward() {
15922      var stepChild = 0;
15923
15924      if (this.focusedChild_ !== undefined) {
15925        stepChild = this.focusedChild_ + 1;
15926      }
15927      this.focus(stepChild);
15928    };
15929
15930    /**
15931     * Move to previous (higher) menu item for keyboard users.
15932     */
15933
15934
15935    Menu.prototype.stepBack = function stepBack() {
15936      var stepChild = 0;
15937
15938      if (this.focusedChild_ !== undefined) {
15939        stepChild = this.focusedChild_ - 1;
15940      }
15941      this.focus(stepChild);
15942    };
15943
15944    /**
15945     * Set focus on a {@link MenuItem} in the `Menu`.
15946     *
15947     * @param {Object|string} [item=0]
15948     *        Index of child item set focus on.
15949     */
15950
15951
15952    Menu.prototype.focus = function focus() {
15953      var item = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
15954
15955      var children = this.children().slice();
15956      var haveTitle = children.length && children[0].className && /vjs-menu-title/.test(children[0].className);
15957
15958      if (haveTitle) {
15959        children.shift();
15960      }
15961
15962      if (children.length > 0) {
15963        if (item < 0) {
15964          item = 0;
15965        } else if (item >= children.length) {
15966          item = children.length - 1;
15967        }
15968
15969        this.focusedChild_ = item;
15970
15971        children[item].el_.focus();
15972      }
15973    };
15974
15975    return Menu;
15976  }(Component);
15977
15978  Component.registerComponent('Menu', Menu);
15979
15980  /**
15981   * @file menu-button.js
15982   */
15983
15984  /**
15985   * A `MenuButton` class for any popup {@link Menu}.
15986   *
15987   * @extends Component
15988   */
15989
15990  var MenuButton = function (_Component) {
15991    inherits(MenuButton, _Component);
15992
15993    /**
15994     * Creates an instance of this class.
15995     *
15996     * @param {Player} player
15997     *        The `Player` that this class should be attached to.
15998     *
15999     * @param {Object} [options={}]
16000     *        The key/value store of player options.
16001     */
16002    function MenuButton(player) {
16003      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
16004      classCallCheck(this, MenuButton);
16005
16006      var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
16007
16008      _this.menuButton_ = new Button(player, options);
16009
16010      _this.menuButton_.controlText(_this.controlText_);
16011      _this.menuButton_.el_.setAttribute('aria-haspopup', 'true');
16012
16013      // Add buildCSSClass values to the button, not the wrapper
16014      var buttonClass = Button.prototype.buildCSSClass();
16015
16016      _this.menuButton_.el_.className = _this.buildCSSClass() + ' ' + buttonClass;
16017      _this.menuButton_.removeClass('vjs-control');
16018
16019      _this.addChild(_this.menuButton_);
16020
16021      _this.update();
16022
16023      _this.enabled_ = true;
16024
16025      _this.on(_this.menuButton_, 'tap', _this.handleClick);
16026      _this.on(_this.menuButton_, 'click', _this.handleClick);
16027      _this.on(_this.menuButton_, 'focus', _this.handleFocus);
16028      _this.on(_this.menuButton_, 'blur', _this.handleBlur);
16029
16030      _this.on('keydown', _this.handleSubmenuKeyPress);
16031      return _this;
16032    }
16033
16034    /**
16035     * Update the menu based on the current state of its items.
16036     */
16037
16038
16039    MenuButton.prototype.update = function update() {
16040      var menu = this.createMenu();
16041
16042      if (this.menu) {
16043        this.menu.dispose();
16044        this.removeChild(this.menu);
16045      }
16046
16047      this.menu = menu;
16048      this.addChild(menu);
16049
16050      /**
16051       * Track the state of the menu button
16052       *
16053       * @type {Boolean}
16054       * @private
16055       */
16056      this.buttonPressed_ = false;
16057      this.menuButton_.el_.setAttribute('aria-expanded', 'false');
16058
16059      if (this.items && this.items.length <= this.hideThreshold_) {
16060        this.hide();
16061      } else {
16062        this.show();
16063      }
16064    };
16065
16066    /**
16067     * Create the menu and add all items to it.
16068     *
16069     * @return {Menu}
16070     *         The constructed menu
16071     */
16072
16073
16074    MenuButton.prototype.createMenu = function createMenu() {
16075      var menu = new Menu(this.player_, { menuButton: this });
16076
16077      /**
16078       * Hide the menu if the number of items is less than or equal to this threshold. This defaults
16079       * to 0 and whenever we add items which can be hidden to the menu we'll increment it. We list
16080       * it here because every time we run `createMenu` we need to reset the value.
16081       *
16082       * @protected
16083       * @type {Number}
16084       */
16085      this.hideThreshold_ = 0;
16086
16087      // Add a title list item to the top
16088      if (this.options_.title) {
16089        var title = createEl('li', {
16090          className: 'vjs-menu-title',
16091          innerHTML: toTitleCase(this.options_.title),
16092          tabIndex: -1
16093        });
16094
16095        this.hideThreshold_ += 1;
16096
16097        menu.children_.unshift(title);
16098        prependTo(title, menu.contentEl());
16099      }
16100
16101      this.items = this.createItems();
16102
16103      if (this.items) {
16104        // Add menu items to the menu
16105        for (var i = 0; i < this.items.length; i++) {
16106          menu.addItem(this.items[i]);
16107        }
16108      }
16109
16110      return menu;
16111    };
16112
16113    /**
16114     * Create the list of menu items. Specific to each subclass.
16115     *
16116     * @abstract
16117     */
16118
16119
16120    MenuButton.prototype.createItems = function createItems() {};
16121
16122    /**
16123     * Create the `MenuButtons`s DOM element.
16124     *
16125     * @return {Element}
16126     *         The element that gets created.
16127     */
16128
16129
16130    MenuButton.prototype.createEl = function createEl$$1() {
16131      return _Component.prototype.createEl.call(this, 'div', {
16132        className: this.buildWrapperCSSClass()
16133      }, {});
16134    };
16135
16136    /**
16137     * Allow sub components to stack CSS class names for the wrapper element
16138     *
16139     * @return {string}
16140     *         The constructed wrapper DOM `className`
16141     */
16142
16143
16144    MenuButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() {
16145      var menuButtonClass = 'vjs-menu-button';
16146
16147      // If the inline option is passed, we want to use different styles altogether.
16148      if (this.options_.inline === true) {
16149        menuButtonClass += '-inline';
16150      } else {
16151        menuButtonClass += '-popup';
16152      }
16153
16154      // TODO: Fix the CSS so that this isn't necessary
16155      var buttonClass = Button.prototype.buildCSSClass();
16156
16157      return 'vjs-menu-button ' + menuButtonClass + ' ' + buttonClass + ' ' + _Component.prototype.buildCSSClass.call(this);
16158    };
16159
16160    /**
16161     * Builds the default DOM `className`.
16162     *
16163     * @return {string}
16164     *         The DOM `className` for this object.
16165     */
16166
16167
16168    MenuButton.prototype.buildCSSClass = function buildCSSClass() {
16169      var menuButtonClass = 'vjs-menu-button';
16170
16171      // If the inline option is passed, we want to use different styles altogether.
16172      if (this.options_.inline === true) {
16173        menuButtonClass += '-inline';
16174      } else {
16175        menuButtonClass += '-popup';
16176      }
16177
16178      return 'vjs-menu-button ' + menuButtonClass + ' ' + _Component.prototype.buildCSSClass.call(this);
16179    };
16180
16181    /**
16182     * Get or set the localized control text that will be used for accessibility.
16183     *
16184     * > NOTE: This will come from the internal `menuButton_` element.
16185     *
16186     * @param {string} [text]
16187     *        Control text for element.
16188     *
16189     * @param {Element} [el=this.menuButton_.el()]
16190     *        Element to set the title on.
16191     *
16192     * @return {string}
16193     *         - The control text when getting
16194     */
16195
16196
16197    MenuButton.prototype.controlText = function controlText(text) {
16198      var el = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.menuButton_.el();
16199
16200      return this.menuButton_.controlText(text, el);
16201    };
16202
16203    /**
16204     * Handle a click on a `MenuButton`.
16205     * See {@link ClickableComponent#handleClick} for instances where this is called.
16206     *
16207     * @param {EventTarget~Event} event
16208     *        The `keydown`, `tap`, or `click` event that caused this function to be
16209     *        called.
16210     *
16211     * @listens tap
16212     * @listens click
16213     */
16214
16215
16216    MenuButton.prototype.handleClick = function handleClick(event) {
16217      // When you click the button it adds focus, which will show the menu.
16218      // So we'll remove focus when the mouse leaves the button. Focus is needed
16219      // for tab navigation.
16220
16221      this.one(this.menu.contentEl(), 'mouseleave', bind(this, function (e) {
16222        this.unpressButton();
16223        this.el_.blur();
16224      }));
16225      if (this.buttonPressed_) {
16226        this.unpressButton();
16227      } else {
16228        this.pressButton();
16229      }
16230    };
16231
16232    /**
16233     * Set the focus to the actual button, not to this element
16234     */
16235
16236
16237    MenuButton.prototype.focus = function focus() {
16238      this.menuButton_.focus();
16239    };
16240
16241    /**
16242     * Remove the focus from the actual button, not this element
16243     */
16244
16245
16246    MenuButton.prototype.blur = function blur() {
16247      this.menuButton_.blur();
16248    };
16249
16250    /**
16251     * This gets called when a `MenuButton` gains focus via a `focus` event.
16252     * Turns on listening for `keydown` events. When they happen it
16253     * calls `this.handleKeyPress`.
16254     *
16255     * @param {EventTarget~Event} event
16256     *        The `focus` event that caused this function to be called.
16257     *
16258     * @listens focus
16259     */
16260
16261
16262    MenuButton.prototype.handleFocus = function handleFocus() {
16263      on(document_1, 'keydown', bind(this, this.handleKeyPress));
16264    };
16265
16266    /**
16267     * Called when a `MenuButton` loses focus. Turns off the listener for
16268     * `keydown` events. Which Stops `this.handleKeyPress` from getting called.
16269     *
16270     * @param {EventTarget~Event} event
16271     *        The `blur` event that caused this function to be called.
16272     *
16273     * @listens blur
16274     */
16275
16276
16277    MenuButton.prototype.handleBlur = function handleBlur() {
16278      off(document_1, 'keydown', bind(this, this.handleKeyPress));
16279    };
16280
16281    /**
16282     * Handle tab, escape, down arrow, and up arrow keys for `MenuButton`. See
16283     * {@link ClickableComponent#handleKeyPress} for instances where this is called.
16284     *
16285     * @param {EventTarget~Event} event
16286     *        The `keydown` event that caused this function to be called.
16287     *
16288     * @listens keydown
16289     */
16290
16291
16292    MenuButton.prototype.handleKeyPress = function handleKeyPress(event) {
16293
16294      // Escape (27) key or Tab (9) key unpress the 'button'
16295      if (event.which === 27 || event.which === 9) {
16296        if (this.buttonPressed_) {
16297          this.unpressButton();
16298        }
16299        // Don't preventDefault for Tab key - we still want to lose focus
16300        if (event.which !== 9) {
16301          event.preventDefault();
16302          // Set focus back to the menu button's button
16303          this.menuButton_.el_.focus();
16304        }
16305        // Up (38) key or Down (40) key press the 'button'
16306      } else if (event.which === 38 || event.which === 40) {
16307        if (!this.buttonPressed_) {
16308          this.pressButton();
16309          event.preventDefault();
16310        }
16311      }
16312    };
16313
16314    /**
16315     * Handle a `keydown` event on a sub-menu. The listener for this is added in
16316     * the constructor.
16317     *
16318     * @param {EventTarget~Event} event
16319     *        Key press event
16320     *
16321     * @listens keydown
16322     */
16323
16324
16325    MenuButton.prototype.handleSubmenuKeyPress = function handleSubmenuKeyPress(event) {
16326
16327      // Escape (27) key or Tab (9) key unpress the 'button'
16328      if (event.which === 27 || event.which === 9) {
16329        if (this.buttonPressed_) {
16330          this.unpressButton();
16331        }
16332        // Don't preventDefault for Tab key - we still want to lose focus
16333        if (event.which !== 9) {
16334          event.preventDefault();
16335          // Set focus back to the menu button's button
16336          this.menuButton_.el_.focus();
16337        }
16338      }
16339    };
16340
16341    /**
16342     * Put the current `MenuButton` into a pressed state.
16343     */
16344
16345
16346    MenuButton.prototype.pressButton = function pressButton() {
16347      if (this.enabled_) {
16348        this.buttonPressed_ = true;
16349        this.menu.lockShowing();
16350        this.menuButton_.el_.setAttribute('aria-expanded', 'true');
16351
16352        // set the focus into the submenu, except on iOS where it is resulting in
16353        // undesired scrolling behavior when the player is in an iframe
16354        if (IS_IOS && isInFrame()) {
16355          // Return early so that the menu isn't focused
16356          return;
16357        }
16358
16359        this.menu.focus();
16360      }
16361    };
16362
16363    /**
16364     * Take the current `MenuButton` out of a pressed state.
16365     */
16366
16367
16368    MenuButton.prototype.unpressButton = function unpressButton() {
16369      if (this.enabled_) {
16370        this.buttonPressed_ = false;
16371        this.menu.unlockShowing();
16372        this.menuButton_.el_.setAttribute('aria-expanded', 'false');
16373      }
16374    };
16375
16376    /**
16377     * Disable the `MenuButton`. Don't allow it to be clicked.
16378     */
16379
16380
16381    MenuButton.prototype.disable = function disable() {
16382      this.unpressButton();
16383
16384      this.enabled_ = false;
16385      this.addClass('vjs-disabled');
16386
16387      this.menuButton_.disable();
16388    };
16389
16390    /**
16391     * Enable the `MenuButton`. Allow it to be clicked.
16392     */
16393
16394
16395    MenuButton.prototype.enable = function enable() {
16396      this.enabled_ = true;
16397      this.removeClass('vjs-disabled');
16398
16399      this.menuButton_.enable();
16400    };
16401
16402    return MenuButton;
16403  }(Component);
16404
16405  Component.registerComponent('MenuButton', MenuButton);
16406
16407  /**
16408   * @file track-button.js
16409   */
16410
16411  /**
16412   * The base class for buttons that toggle specific  track types (e.g. subtitles).
16413   *
16414   * @extends MenuButton
16415   */
16416
16417  var TrackButton = function (_MenuButton) {
16418    inherits(TrackButton, _MenuButton);
16419
16420    /**
16421     * Creates an instance of this class.
16422     *
16423     * @param {Player} player
16424     *        The `Player` that this class should be attached to.
16425     *
16426     * @param {Object} [options]
16427     *        The key/value store of player options.
16428     */
16429    function TrackButton(player, options) {
16430      classCallCheck(this, TrackButton);
16431
16432      var tracks = options.tracks;
16433
16434      var _this = possibleConstructorReturn(this, _MenuButton.call(this, player, options));
16435
16436      if (_this.items.length <= 1) {
16437        _this.hide();
16438      }
16439
16440      if (!tracks) {
16441        return possibleConstructorReturn(_this);
16442      }
16443
16444      var updateHandler = bind(_this, _this.update);
16445
16446      tracks.addEventListener('removetrack', updateHandler);
16447      tracks.addEventListener('addtrack', updateHandler);
16448      _this.player_.on('ready', updateHandler);
16449
16450      _this.player_.on('dispose', function () {
16451        tracks.removeEventListener('removetrack', updateHandler);
16452        tracks.removeEventListener('addtrack', updateHandler);
16453      });
16454      return _this;
16455    }
16456
16457    return TrackButton;
16458  }(MenuButton);
16459
16460  Component.registerComponent('TrackButton', TrackButton);
16461
16462  /**
16463   * @file menu-item.js
16464   */
16465
16466  /**
16467   * The component for a menu item. `<li>`
16468   *
16469   * @extends ClickableComponent
16470   */
16471
16472  var MenuItem = function (_ClickableComponent) {
16473    inherits(MenuItem, _ClickableComponent);
16474
16475    /**
16476     * Creates an instance of the this class.
16477     *
16478     * @param {Player} player
16479     *        The `Player` that this class should be attached to.
16480     *
16481     * @param {Object} [options={}]
16482     *        The key/value store of player options.
16483     *
16484     */
16485    function MenuItem(player, options) {
16486      classCallCheck(this, MenuItem);
16487
16488      var _this = possibleConstructorReturn(this, _ClickableComponent.call(this, player, options));
16489
16490      _this.selectable = options.selectable;
16491      _this.isSelected_ = options.selected || false;
16492      _this.multiSelectable = options.multiSelectable;
16493
16494      _this.selected(_this.isSelected_);
16495
16496      if (_this.selectable) {
16497        if (_this.multiSelectable) {
16498          _this.el_.setAttribute('role', 'menuitemcheckbox');
16499        } else {
16500          _this.el_.setAttribute('role', 'menuitemradio');
16501        }
16502      } else {
16503        _this.el_.setAttribute('role', 'menuitem');
16504      }
16505      return _this;
16506    }
16507
16508    /**
16509     * Create the `MenuItem's DOM element
16510     *
16511     * @param {string} [type=li]
16512     *        Element's node type, not actually used, always set to `li`.
16513     *
16514     * @param {Object} [props={}]
16515     *        An object of properties that should be set on the element
16516     *
16517     * @param {Object} [attrs={}]
16518     *        An object of attributes that should be set on the element
16519     *
16520     * @return {Element}
16521     *         The element that gets created.
16522     */
16523
16524
16525    MenuItem.prototype.createEl = function createEl(type, props, attrs) {
16526      // The control is textual, not just an icon
16527      this.nonIconControl = true;
16528
16529      return _ClickableComponent.prototype.createEl.call(this, 'li', assign({
16530        className: 'vjs-menu-item',
16531        innerHTML: '<span class="vjs-menu-item-text">' + this.localize(this.options_.label) + '</span>',
16532        tabIndex: -1
16533      }, props), attrs);
16534    };
16535
16536    /**
16537     * Any click on a `MenuItem` puts it into the selected state.
16538     * See {@link ClickableComponent#handleClick} for instances where this is called.
16539     *
16540     * @param {EventTarget~Event} event
16541     *        The `keydown`, `tap`, or `click` event that caused this function to be
16542     *        called.
16543     *
16544     * @listens tap
16545     * @listens click
16546     */
16547
16548
16549    MenuItem.prototype.handleClick = function handleClick(event) {
16550      this.selected(true);
16551    };
16552
16553    /**
16554     * Set the state for this menu item as selected or not.
16555     *
16556     * @param {boolean} selected
16557     *        if the menu item is selected or not
16558     */
16559
16560
16561    MenuItem.prototype.selected = function selected(_selected) {
16562      if (this.selectable) {
16563        if (_selected) {
16564          this.addClass('vjs-selected');
16565          this.el_.setAttribute('aria-checked', 'true');
16566          // aria-checked isn't fully supported by browsers/screen readers,
16567          // so indicate selected state to screen reader in the control text.
16568          this.controlText(', selected');
16569          this.isSelected_ = true;
16570        } else {
16571          this.removeClass('vjs-selected');
16572          this.el_.setAttribute('aria-checked', 'false');
16573          // Indicate un-selected state to screen reader
16574          this.controlText('');
16575          this.isSelected_ = false;
16576        }
16577      }
16578    };
16579
16580    return MenuItem;
16581  }(ClickableComponent);
16582
16583  Component.registerComponent('MenuItem', MenuItem);
16584
16585  /**
16586   * @file text-track-menu-item.js
16587   */
16588
16589  /**
16590   * The specific menu item type for selecting a language within a text track kind
16591   *
16592   * @extends MenuItem
16593   */
16594
16595  var TextTrackMenuItem = function (_MenuItem) {
16596    inherits(TextTrackMenuItem, _MenuItem);
16597
16598    /**
16599     * Creates an instance of this class.
16600     *
16601     * @param {Player} player
16602     *        The `Player` that this class should be attached to.
16603     *
16604     * @param {Object} [options]
16605     *        The key/value store of player options.
16606     */
16607    function TextTrackMenuItem(player, options) {
16608      classCallCheck(this, TextTrackMenuItem);
16609
16610      var track = options.track;
16611      var tracks = player.textTracks();
16612
16613      // Modify options for parent MenuItem class's init.
16614      options.label = track.label || track.language || 'Unknown';
16615      options.selected = track.mode === 'showing';
16616
16617      var _this = possibleConstructorReturn(this, _MenuItem.call(this, player, options));
16618
16619      _this.track = track;
16620      var changeHandler = function changeHandler() {
16621        for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
16622          args[_key] = arguments[_key];
16623        }
16624
16625        _this.handleTracksChange.apply(_this, args);
16626      };
16627      var selectedLanguageChangeHandler = function selectedLanguageChangeHandler() {
16628        for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
16629          args[_key2] = arguments[_key2];
16630        }
16631
16632        _this.handleSelectedLanguageChange.apply(_this, args);
16633      };
16634
16635      player.on(['loadstart', 'texttrackchange'], changeHandler);
16636      tracks.addEventListener('change', changeHandler);
16637      tracks.addEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
16638      _this.on('dispose', function () {
16639        player.off(['loadstart', 'texttrackchange'], changeHandler);
16640        tracks.removeEventListener('change', changeHandler);
16641        tracks.removeEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
16642      });
16643
16644      // iOS7 doesn't dispatch change events to TextTrackLists when an
16645      // associated track's mode changes. Without something like
16646      // Object.observe() (also not present on iOS7), it's not
16647      // possible to detect changes to the mode attribute and polyfill
16648      // the change event. As a poor substitute, we manually dispatch
16649      // change events whenever the controls modify the mode.
16650      if (tracks.onchange === undefined) {
16651        var event = void 0;
16652
16653        _this.on(['tap', 'click'], function () {
16654          if (_typeof(window_1.Event) !== 'object') {
16655            // Android 2.3 throws an Illegal Constructor error for window.Event
16656            try {
16657              event = new window_1.Event('change');
16658            } catch (err) {
16659              // continue regardless of error
16660            }
16661          }
16662
16663          if (!event) {
16664            event = document_1.createEvent('Event');
16665            event.initEvent('change', true, true);
16666          }
16667
16668          tracks.dispatchEvent(event);
16669        });
16670      }
16671
16672      // set the default state based on current tracks
16673      _this.handleTracksChange();
16674      return _this;
16675    }
16676
16677    /**
16678     * This gets called when an `TextTrackMenuItem` is "clicked". See
16679     * {@link ClickableComponent} for more detailed information on what a click can be.
16680     *
16681     * @param {EventTarget~Event} event
16682     *        The `keydown`, `tap`, or `click` event that caused this function to be
16683     *        called.
16684     *
16685     * @listens tap
16686     * @listens click
16687     */
16688
16689
16690    TextTrackMenuItem.prototype.handleClick = function handleClick(event) {
16691      var kind = this.track.kind;
16692      var kinds = this.track.kinds;
16693      var tracks = this.player_.textTracks();
16694
16695      if (!kinds) {
16696        kinds = [kind];
16697      }
16698
16699      _MenuItem.prototype.handleClick.call(this, event);
16700
16701      if (!tracks) {
16702        return;
16703      }
16704
16705      for (var i = 0; i < tracks.length; i++) {
16706        var track = tracks[i];
16707
16708        if (track === this.track && kinds.indexOf(track.kind) > -1) {
16709          if (track.mode !== 'showing') {
16710            track.mode = 'showing';
16711          }
16712        } else if (track.mode !== 'disabled') {
16713          track.mode = 'disabled';
16714        }
16715      }
16716    };
16717
16718    /**
16719     * Handle text track list change
16720     *
16721     * @param {EventTarget~Event} event
16722     *        The `change` event that caused this function to be called.
16723     *
16724     * @listens TextTrackList#change
16725     */
16726
16727
16728    TextTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) {
16729      var shouldBeSelected = this.track.mode === 'showing';
16730
16731      // Prevent redundant selected() calls because they may cause
16732      // screen readers to read the appended control text unnecessarily
16733      if (shouldBeSelected !== this.isSelected_) {
16734        this.selected(shouldBeSelected);
16735      }
16736    };
16737
16738    TextTrackMenuItem.prototype.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) {
16739      if (this.track.mode === 'showing') {
16740        var selectedLanguage = this.player_.cache_.selectedLanguage;
16741
16742        // Don't replace the kind of track across the same language
16743        if (selectedLanguage && selectedLanguage.enabled && selectedLanguage.language === this.track.language && selectedLanguage.kind !== this.track.kind) {
16744          return;
16745        }
16746
16747        this.player_.cache_.selectedLanguage = {
16748          enabled: true,
16749          language: this.track.language,
16750          kind: this.track.kind
16751        };
16752      }
16753    };
16754
16755    TextTrackMenuItem.prototype.dispose = function dispose() {
16756      // remove reference to track object on dispose
16757      this.track = null;
16758
16759      _MenuItem.prototype.dispose.call(this);
16760    };
16761
16762    return TextTrackMenuItem;
16763  }(MenuItem);
16764
16765  Component.registerComponent('TextTrackMenuItem', TextTrackMenuItem);
16766
16767  /**
16768   * @file off-text-track-menu-item.js
16769   */
16770
16771  /**
16772   * A special menu item for turning of a specific type of text track
16773   *
16774   * @extends TextTrackMenuItem
16775   */
16776
16777  var OffTextTrackMenuItem = function (_TextTrackMenuItem) {
16778    inherits(OffTextTrackMenuItem, _TextTrackMenuItem);
16779
16780    /**
16781     * Creates an instance of this class.
16782     *
16783     * @param {Player} player
16784     *        The `Player` that this class should be attached to.
16785     *
16786     * @param {Object} [options]
16787     *        The key/value store of player options.
16788     */
16789    function OffTextTrackMenuItem(player, options) {
16790      classCallCheck(this, OffTextTrackMenuItem);
16791
16792      // Create pseudo track info
16793      // Requires options['kind']
16794      options.track = {
16795        player: player,
16796        kind: options.kind,
16797        kinds: options.kinds,
16798        default: false,
16799        mode: 'disabled'
16800      };
16801
16802      if (!options.kinds) {
16803        options.kinds = [options.kind];
16804      }
16805
16806      if (options.label) {
16807        options.track.label = options.label;
16808      } else {
16809        options.track.label = options.kinds.join(' and ') + ' off';
16810      }
16811
16812      // MenuItem is selectable
16813      options.selectable = true;
16814      // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
16815      options.multiSelectable = false;
16816
16817      return possibleConstructorReturn(this, _TextTrackMenuItem.call(this, player, options));
16818    }
16819
16820    /**
16821     * Handle text track change
16822     *
16823     * @param {EventTarget~Event} event
16824     *        The event that caused this function to run
16825     */
16826
16827
16828    OffTextTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) {
16829      var tracks = this.player().textTracks();
16830      var shouldBeSelected = true;
16831
16832      for (var i = 0, l = tracks.length; i < l; i++) {
16833        var track = tracks[i];
16834
16835        if (this.options_.kinds.indexOf(track.kind) > -1 && track.mode === 'showing') {
16836          shouldBeSelected = false;
16837          break;
16838        }
16839      }
16840
16841      // Prevent redundant selected() calls because they may cause
16842      // screen readers to read the appended control text unnecessarily
16843      if (shouldBeSelected !== this.isSelected_) {
16844        this.selected(shouldBeSelected);
16845      }
16846    };
16847
16848    OffTextTrackMenuItem.prototype.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) {
16849      var tracks = this.player().textTracks();
16850      var allHidden = true;
16851
16852      for (var i = 0, l = tracks.length; i < l; i++) {
16853        var track = tracks[i];
16854
16855        if (['captions', 'descriptions', 'subtitles'].indexOf(track.kind) > -1 && track.mode === 'showing') {
16856          allHidden = false;
16857          break;
16858        }
16859      }
16860
16861      if (allHidden) {
16862        this.player_.cache_.selectedLanguage = {
16863          enabled: false
16864        };
16865      }
16866    };
16867
16868    return OffTextTrackMenuItem;
16869  }(TextTrackMenuItem);
16870
16871  Component.registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem);
16872
16873  /**
16874   * @file text-track-button.js
16875   */
16876
16877  /**
16878   * The base class for buttons that toggle specific text track types (e.g. subtitles)
16879   *
16880   * @extends MenuButton
16881   */
16882
16883  var TextTrackButton = function (_TrackButton) {
16884    inherits(TextTrackButton, _TrackButton);
16885
16886    /**
16887     * Creates an instance of this class.
16888     *
16889     * @param {Player} player
16890     *        The `Player` that this class should be attached to.
16891     *
16892     * @param {Object} [options={}]
16893     *        The key/value store of player options.
16894     */
16895    function TextTrackButton(player) {
16896      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
16897      classCallCheck(this, TextTrackButton);
16898
16899      options.tracks = player.textTracks();
16900
16901      return possibleConstructorReturn(this, _TrackButton.call(this, player, options));
16902    }
16903
16904    /**
16905     * Create a menu item for each text track
16906     *
16907     * @param {TextTrackMenuItem[]} [items=[]]
16908     *        Existing array of items to use during creation
16909     *
16910     * @return {TextTrackMenuItem[]}
16911     *         Array of menu items that were created
16912     */
16913
16914
16915    TextTrackButton.prototype.createItems = function createItems() {
16916      var items = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
16917      var TrackMenuItem = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : TextTrackMenuItem;
16918
16919
16920      // Label is an override for the [track] off label
16921      // USed to localise captions/subtitles
16922      var label = void 0;
16923
16924      if (this.label_) {
16925        label = this.label_ + ' off';
16926      }
16927      // Add an OFF menu item to turn all tracks off
16928      items.push(new OffTextTrackMenuItem(this.player_, {
16929        kinds: this.kinds_,
16930        kind: this.kind_,
16931        label: label
16932      }));
16933
16934      this.hideThreshold_ += 1;
16935
16936      var tracks = this.player_.textTracks();
16937
16938      if (!Array.isArray(this.kinds_)) {
16939        this.kinds_ = [this.kind_];
16940      }
16941
16942      for (var i = 0; i < tracks.length; i++) {
16943        var track = tracks[i];
16944
16945        // only add tracks that are of an appropriate kind and have a label
16946        if (this.kinds_.indexOf(track.kind) > -1) {
16947
16948          var item = new TrackMenuItem(this.player_, {
16949            track: track,
16950            // MenuItem is selectable
16951            selectable: true,
16952            // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
16953            multiSelectable: false
16954          });
16955
16956          item.addClass('vjs-' + track.kind + '-menu-item');
16957          items.push(item);
16958        }
16959      }
16960
16961      return items;
16962    };
16963
16964    return TextTrackButton;
16965  }(TrackButton);
16966
16967  Component.registerComponent('TextTrackButton', TextTrackButton);
16968
16969  /**
16970   * @file chapters-track-menu-item.js
16971   */
16972
16973  /**
16974   * The chapter track menu item
16975   *
16976   * @extends MenuItem
16977   */
16978
16979  var ChaptersTrackMenuItem = function (_MenuItem) {
16980    inherits(ChaptersTrackMenuItem, _MenuItem);
16981
16982    /**
16983     * Creates an instance of this class.
16984     *
16985     * @param {Player} player
16986     *        The `Player` that this class should be attached to.
16987     *
16988     * @param {Object} [options]
16989     *        The key/value store of player options.
16990     */
16991    function ChaptersTrackMenuItem(player, options) {
16992      classCallCheck(this, ChaptersTrackMenuItem);
16993
16994      var track = options.track;
16995      var cue = options.cue;
16996      var currentTime = player.currentTime();
16997
16998      // Modify options for parent MenuItem class's init.
16999      options.selectable = true;
17000      options.multiSelectable = false;
17001      options.label = cue.text;
17002      options.selected = cue.startTime <= currentTime && currentTime < cue.endTime;
17003
17004      var _this = possibleConstructorReturn(this, _MenuItem.call(this, player, options));
17005
17006      _this.track = track;
17007      _this.cue = cue;
17008      track.addEventListener('cuechange', bind(_this, _this.update));
17009      return _this;
17010    }
17011
17012    /**
17013     * This gets called when an `ChaptersTrackMenuItem` is "clicked". See
17014     * {@link ClickableComponent} for more detailed information on what a click can be.
17015     *
17016     * @param {EventTarget~Event} [event]
17017     *        The `keydown`, `tap`, or `click` event that caused this function to be
17018     *        called.
17019     *
17020     * @listens tap
17021     * @listens click
17022     */
17023
17024
17025    ChaptersTrackMenuItem.prototype.handleClick = function handleClick(event) {
17026      _MenuItem.prototype.handleClick.call(this);
17027      this.player_.currentTime(this.cue.startTime);
17028      this.update(this.cue.startTime);
17029    };
17030
17031    /**
17032     * Update chapter menu item
17033     *
17034     * @param {EventTarget~Event} [event]
17035     *        The `cuechange` event that caused this function to run.
17036     *
17037     * @listens TextTrack#cuechange
17038     */
17039
17040
17041    ChaptersTrackMenuItem.prototype.update = function update(event) {
17042      var cue = this.cue;
17043      var currentTime = this.player_.currentTime();
17044
17045      // vjs.log(currentTime, cue.startTime);
17046      this.selected(cue.startTime <= currentTime && currentTime < cue.endTime);
17047    };
17048
17049    return ChaptersTrackMenuItem;
17050  }(MenuItem);
17051
17052  Component.registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem);
17053
17054  /**
17055   * @file chapters-button.js
17056   */
17057
17058  /**
17059   * The button component for toggling and selecting chapters
17060   * Chapters act much differently than other text tracks
17061   * Cues are navigation vs. other tracks of alternative languages
17062   *
17063   * @extends TextTrackButton
17064   */
17065
17066  var ChaptersButton = function (_TextTrackButton) {
17067    inherits(ChaptersButton, _TextTrackButton);
17068
17069    /**
17070     * Creates an instance of this class.
17071     *
17072     * @param {Player} player
17073     *        The `Player` that this class should be attached to.
17074     *
17075     * @param {Object} [options]
17076     *        The key/value store of player options.
17077     *
17078     * @param {Component~ReadyCallback} [ready]
17079     *        The function to call when this function is ready.
17080     */
17081    function ChaptersButton(player, options, ready) {
17082      classCallCheck(this, ChaptersButton);
17083      return possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready));
17084    }
17085
17086    /**
17087     * Builds the default DOM `className`.
17088     *
17089     * @return {string}
17090     *         The DOM `className` for this object.
17091     */
17092
17093
17094    ChaptersButton.prototype.buildCSSClass = function buildCSSClass() {
17095      return 'vjs-chapters-button ' + _TextTrackButton.prototype.buildCSSClass.call(this);
17096    };
17097
17098    ChaptersButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() {
17099      return 'vjs-chapters-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
17100    };
17101
17102    /**
17103     * Update the menu based on the current state of its items.
17104     *
17105     * @param {EventTarget~Event} [event]
17106     *        An event that triggered this function to run.
17107     *
17108     * @listens TextTrackList#addtrack
17109     * @listens TextTrackList#removetrack
17110     * @listens TextTrackList#change
17111     */
17112
17113
17114    ChaptersButton.prototype.update = function update(event) {
17115      if (!this.track_ || event && (event.type === 'addtrack' || event.type === 'removetrack')) {
17116        this.setTrack(this.findChaptersTrack());
17117      }
17118      _TextTrackButton.prototype.update.call(this);
17119    };
17120
17121    /**
17122     * Set the currently selected track for the chapters button.
17123     *
17124     * @param {TextTrack} track
17125     *        The new track to select. Nothing will change if this is the currently selected
17126     *        track.
17127     */
17128
17129
17130    ChaptersButton.prototype.setTrack = function setTrack(track) {
17131      if (this.track_ === track) {
17132        return;
17133      }
17134
17135      if (!this.updateHandler_) {
17136        this.updateHandler_ = this.update.bind(this);
17137      }
17138
17139      // here this.track_ refers to the old track instance
17140      if (this.track_) {
17141        var remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
17142
17143        if (remoteTextTrackEl) {
17144          remoteTextTrackEl.removeEventListener('load', this.updateHandler_);
17145        }
17146
17147        this.track_ = null;
17148      }
17149
17150      this.track_ = track;
17151
17152      // here this.track_ refers to the new track instance
17153      if (this.track_) {
17154        this.track_.mode = 'hidden';
17155
17156        var _remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
17157
17158        if (_remoteTextTrackEl) {
17159          _remoteTextTrackEl.addEventListener('load', this.updateHandler_);
17160        }
17161      }
17162    };
17163
17164    /**
17165     * Find the track object that is currently in use by this ChaptersButton
17166     *
17167     * @return {TextTrack|undefined}
17168     *         The current track or undefined if none was found.
17169     */
17170
17171
17172    ChaptersButton.prototype.findChaptersTrack = function findChaptersTrack() {
17173      var tracks = this.player_.textTracks() || [];
17174
17175      for (var i = tracks.length - 1; i >= 0; i--) {
17176        // We will always choose the last track as our chaptersTrack
17177        var track = tracks[i];
17178
17179        if (track.kind === this.kind_) {
17180          return track;
17181        }
17182      }
17183    };
17184
17185    /**
17186     * Get the caption for the ChaptersButton based on the track label. This will also
17187     * use the current tracks localized kind as a fallback if a label does not exist.
17188     *
17189     * @return {string}
17190     *         The tracks current label or the localized track kind.
17191     */
17192
17193
17194    ChaptersButton.prototype.getMenuCaption = function getMenuCaption() {
17195      if (this.track_ && this.track_.label) {
17196        return this.track_.label;
17197      }
17198      return this.localize(toTitleCase(this.kind_));
17199    };
17200
17201    /**
17202     * Create menu from chapter track
17203     *
17204     * @return {Menu}
17205     *         New menu for the chapter buttons
17206     */
17207
17208
17209    ChaptersButton.prototype.createMenu = function createMenu() {
17210      this.options_.title = this.getMenuCaption();
17211      return _TextTrackButton.prototype.createMenu.call(this);
17212    };
17213
17214    /**
17215     * Create a menu item for each text track
17216     *
17217     * @return {TextTrackMenuItem[]}
17218     *         Array of menu items
17219     */
17220
17221
17222    ChaptersButton.prototype.createItems = function createItems() {
17223      var items = [];
17224
17225      if (!this.track_) {
17226        return items;
17227      }
17228
17229      var cues = this.track_.cues;
17230
17231      if (!cues) {
17232        return items;
17233      }
17234
17235      for (var i = 0, l = cues.length; i < l; i++) {
17236        var cue = cues[i];
17237        var mi = new ChaptersTrackMenuItem(this.player_, { track: this.track_, cue: cue });
17238
17239        items.push(mi);
17240      }
17241
17242      return items;
17243    };
17244
17245    return ChaptersButton;
17246  }(TextTrackButton);
17247
17248  /**
17249   * `kind` of TextTrack to look for to associate it with this menu.
17250   *
17251   * @type {string}
17252   * @private
17253   */
17254
17255
17256  ChaptersButton.prototype.kind_ = 'chapters';
17257
17258  /**
17259   * The text that should display over the `ChaptersButton`s controls. Added for localization.
17260   *
17261   * @type {string}
17262   * @private
17263   */
17264  ChaptersButton.prototype.controlText_ = 'Chapters';
17265
17266  Component.registerComponent('ChaptersButton', ChaptersButton);
17267
17268  /**
17269   * @file descriptions-button.js
17270   */
17271
17272  /**
17273   * The button component for toggling and selecting descriptions
17274   *
17275   * @extends TextTrackButton
17276   */
17277
17278  var DescriptionsButton = function (_TextTrackButton) {
17279    inherits(DescriptionsButton, _TextTrackButton);
17280
17281    /**
17282     * Creates an instance of this class.
17283     *
17284     * @param {Player} player
17285     *        The `Player` that this class should be attached to.
17286     *
17287     * @param {Object} [options]
17288     *        The key/value store of player options.
17289     *
17290     * @param {Component~ReadyCallback} [ready]
17291     *        The function to call when this component is ready.
17292     */
17293    function DescriptionsButton(player, options, ready) {
17294      classCallCheck(this, DescriptionsButton);
17295
17296      var _this = possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready));
17297
17298      var tracks = player.textTracks();
17299      var changeHandler = bind(_this, _this.handleTracksChange);
17300
17301      tracks.addEventListener('change', changeHandler);
17302      _this.on('dispose', function () {
17303        tracks.removeEventListener('change', changeHandler);
17304      });
17305      return _this;
17306    }
17307
17308    /**
17309     * Handle text track change
17310     *
17311     * @param {EventTarget~Event} event
17312     *        The event that caused this function to run
17313     *
17314     * @listens TextTrackList#change
17315     */
17316
17317
17318    DescriptionsButton.prototype.handleTracksChange = function handleTracksChange(event) {
17319      var tracks = this.player().textTracks();
17320      var disabled = false;
17321
17322      // Check whether a track of a different kind is showing
17323      for (var i = 0, l = tracks.length; i < l; i++) {
17324        var track = tracks[i];
17325
17326        if (track.kind !== this.kind_ && track.mode === 'showing') {
17327          disabled = true;
17328          break;
17329        }
17330      }
17331
17332      // If another track is showing, disable this menu button
17333      if (disabled) {
17334        this.disable();
17335      } else {
17336        this.enable();
17337      }
17338    };
17339
17340    /**
17341     * Builds the default DOM `className`.
17342     *
17343     * @return {string}
17344     *         The DOM `className` for this object.
17345     */
17346
17347
17348    DescriptionsButton.prototype.buildCSSClass = function buildCSSClass() {
17349      return 'vjs-descriptions-button ' + _TextTrackButton.prototype.buildCSSClass.call(this);
17350    };
17351
17352    DescriptionsButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() {
17353      return 'vjs-descriptions-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
17354    };
17355
17356    return DescriptionsButton;
17357  }(TextTrackButton);
17358
17359  /**
17360   * `kind` of TextTrack to look for to associate it with this menu.
17361   *
17362   * @type {string}
17363   * @private
17364   */
17365
17366
17367  DescriptionsButton.prototype.kind_ = 'descriptions';
17368
17369  /**
17370   * The text that should display over the `DescriptionsButton`s controls. Added for localization.
17371   *
17372   * @type {string}
17373   * @private
17374   */
17375  DescriptionsButton.prototype.controlText_ = 'Descriptions';
17376
17377  Component.registerComponent('DescriptionsButton', DescriptionsButton);
17378
17379  /**
17380   * @file subtitles-button.js
17381   */
17382
17383  /**
17384   * The button component for toggling and selecting subtitles
17385   *
17386   * @extends TextTrackButton
17387   */
17388
17389  var SubtitlesButton = function (_TextTrackButton) {
17390    inherits(SubtitlesButton, _TextTrackButton);
17391
17392    /**
17393     * Creates an instance of this class.
17394     *
17395     * @param {Player} player
17396     *        The `Player` that this class should be attached to.
17397     *
17398     * @param {Object} [options]
17399     *        The key/value store of player options.
17400     *
17401     * @param {Component~ReadyCallback} [ready]
17402     *        The function to call when this component is ready.
17403     */
17404    function SubtitlesButton(player, options, ready) {
17405      classCallCheck(this, SubtitlesButton);
17406      return possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready));
17407    }
17408
17409    /**
17410     * Builds the default DOM `className`.
17411     *
17412     * @return {string}
17413     *         The DOM `className` for this object.
17414     */
17415
17416
17417    SubtitlesButton.prototype.buildCSSClass = function buildCSSClass() {
17418      return 'vjs-subtitles-button ' + _TextTrackButton.prototype.buildCSSClass.call(this);
17419    };
17420
17421    SubtitlesButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() {
17422      return 'vjs-subtitles-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
17423    };
17424
17425    return SubtitlesButton;
17426  }(TextTrackButton);
17427
17428  /**
17429   * `kind` of TextTrack to look for to associate it with this menu.
17430   *
17431   * @type {string}
17432   * @private
17433   */
17434
17435
17436  SubtitlesButton.prototype.kind_ = 'subtitles';
17437
17438  /**
17439   * The text that should display over the `SubtitlesButton`s controls. Added for localization.
17440   *
17441   * @type {string}
17442   * @private
17443   */
17444  SubtitlesButton.prototype.controlText_ = 'Subtitles';
17445
17446  Component.registerComponent('SubtitlesButton', SubtitlesButton);
17447
17448  /**
17449   * @file caption-settings-menu-item.js
17450   */
17451
17452  /**
17453   * The menu item for caption track settings menu
17454   *
17455   * @extends TextTrackMenuItem
17456   */
17457
17458  var CaptionSettingsMenuItem = function (_TextTrackMenuItem) {
17459    inherits(CaptionSettingsMenuItem, _TextTrackMenuItem);
17460
17461    /**
17462     * Creates an instance of this class.
17463     *
17464     * @param {Player} player
17465     *        The `Player` that this class should be attached to.
17466     *
17467     * @param {Object} [options]
17468     *        The key/value store of player options.
17469     */
17470    function CaptionSettingsMenuItem(player, options) {
17471      classCallCheck(this, CaptionSettingsMenuItem);
17472
17473      options.track = {
17474        player: player,
17475        kind: options.kind,
17476        label: options.kind + ' settings',
17477        selectable: false,
17478        default: false,
17479        mode: 'disabled'
17480      };
17481
17482      // CaptionSettingsMenuItem has no concept of 'selected'
17483      options.selectable = false;
17484
17485      options.name = 'CaptionSettingsMenuItem';
17486
17487      var _this = possibleConstructorReturn(this, _TextTrackMenuItem.call(this, player, options));
17488
17489      _this.addClass('vjs-texttrack-settings');
17490      _this.controlText(', opens ' + options.kind + ' settings dialog');
17491      return _this;
17492    }
17493
17494    /**
17495     * This gets called when an `CaptionSettingsMenuItem` is "clicked". See
17496     * {@link ClickableComponent} for more detailed information on what a click can be.
17497     *
17498     * @param {EventTarget~Event} [event]
17499     *        The `keydown`, `tap`, or `click` event that caused this function to be
17500     *        called.
17501     *
17502     * @listens tap
17503     * @listens click
17504     */
17505
17506
17507    CaptionSettingsMenuItem.prototype.handleClick = function handleClick(event) {
17508      this.player().getChild('textTrackSettings').open();
17509    };
17510
17511    return CaptionSettingsMenuItem;
17512  }(TextTrackMenuItem);
17513
17514  Component.registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem);
17515
17516  /**
17517   * @file captions-button.js
17518   */
17519
17520  /**
17521   * The button component for toggling and selecting captions
17522   *
17523   * @extends TextTrackButton
17524   */
17525
17526  var CaptionsButton = function (_TextTrackButton) {
17527    inherits(CaptionsButton, _TextTrackButton);
17528
17529    /**
17530     * Creates an instance of this class.
17531     *
17532     * @param {Player} player
17533     *        The `Player` that this class should be attached to.
17534     *
17535     * @param {Object} [options]
17536     *        The key/value store of player options.
17537     *
17538     * @param {Component~ReadyCallback} [ready]
17539     *        The function to call when this component is ready.
17540     */
17541    function CaptionsButton(player, options, ready) {
17542      classCallCheck(this, CaptionsButton);
17543      return possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready));
17544    }
17545
17546    /**
17547     * Builds the default DOM `className`.
17548     *
17549     * @return {string}
17550     *         The DOM `className` for this object.
17551     */
17552
17553
17554    CaptionsButton.prototype.buildCSSClass = function buildCSSClass() {
17555      return 'vjs-captions-button ' + _TextTrackButton.prototype.buildCSSClass.call(this);
17556    };
17557
17558    CaptionsButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() {
17559      return 'vjs-captions-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
17560    };
17561
17562    /**
17563     * Create caption menu items
17564     *
17565     * @return {CaptionSettingsMenuItem[]}
17566     *         The array of current menu items.
17567     */
17568
17569
17570    CaptionsButton.prototype.createItems = function createItems() {
17571      var items = [];
17572
17573      if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
17574        items.push(new CaptionSettingsMenuItem(this.player_, { kind: this.kind_ }));
17575
17576        this.hideThreshold_ += 1;
17577      }
17578
17579      return _TextTrackButton.prototype.createItems.call(this, items);
17580    };
17581
17582    return CaptionsButton;
17583  }(TextTrackButton);
17584
17585  /**
17586   * `kind` of TextTrack to look for to associate it with this menu.
17587   *
17588   * @type {string}
17589   * @private
17590   */
17591
17592
17593  CaptionsButton.prototype.kind_ = 'captions';
17594
17595  /**
17596   * The text that should display over the `CaptionsButton`s controls. Added for localization.
17597   *
17598   * @type {string}
17599   * @private
17600   */
17601  CaptionsButton.prototype.controlText_ = 'Captions';
17602
17603  Component.registerComponent('CaptionsButton', CaptionsButton);
17604
17605  /**
17606   * @file subs-caps-menu-item.js
17607   */
17608
17609  /**
17610   * SubsCapsMenuItem has an [cc] icon to distinguish captions from subtitles
17611   * in the SubsCapsMenu.
17612   *
17613   * @extends TextTrackMenuItem
17614   */
17615
17616  var SubsCapsMenuItem = function (_TextTrackMenuItem) {
17617    inherits(SubsCapsMenuItem, _TextTrackMenuItem);
17618
17619    function SubsCapsMenuItem() {
17620      classCallCheck(this, SubsCapsMenuItem);
17621      return possibleConstructorReturn(this, _TextTrackMenuItem.apply(this, arguments));
17622    }
17623
17624    SubsCapsMenuItem.prototype.createEl = function createEl(type, props, attrs) {
17625      var innerHTML = '<span class="vjs-menu-item-text">' + this.localize(this.options_.label);
17626
17627      if (this.options_.track.kind === 'captions') {
17628        innerHTML += '\n        <span aria-hidden="true" class="vjs-icon-placeholder"></span>\n        <span class="vjs-control-text"> ' + this.localize('Captions') + '</span>\n      ';
17629      }
17630
17631      innerHTML += '</span>';
17632
17633      var el = _TextTrackMenuItem.prototype.createEl.call(this, type, assign({
17634        innerHTML: innerHTML
17635      }, props), attrs);
17636
17637      return el;
17638    };
17639
17640    return SubsCapsMenuItem;
17641  }(TextTrackMenuItem);
17642
17643  Component.registerComponent('SubsCapsMenuItem', SubsCapsMenuItem);
17644
17645  /**
17646   * @file sub-caps-button.js
17647   */
17648  /**
17649   * The button component for toggling and selecting captions and/or subtitles
17650   *
17651   * @extends TextTrackButton
17652   */
17653
17654  var SubsCapsButton = function (_TextTrackButton) {
17655    inherits(SubsCapsButton, _TextTrackButton);
17656
17657    function SubsCapsButton(player) {
17658      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
17659      classCallCheck(this, SubsCapsButton);
17660
17661      // Although North America uses "captions" in most cases for
17662      // "captions and subtitles" other locales use "subtitles"
17663      var _this = possibleConstructorReturn(this, _TextTrackButton.call(this, player, options));
17664
17665      _this.label_ = 'subtitles';
17666      if (['en', 'en-us', 'en-ca', 'fr-ca'].indexOf(_this.player_.language_) > -1) {
17667        _this.label_ = 'captions';
17668      }
17669      _this.menuButton_.controlText(toTitleCase(_this.label_));
17670      return _this;
17671    }
17672
17673    /**
17674     * Builds the default DOM `className`.
17675     *
17676     * @return {string}
17677     *         The DOM `className` for this object.
17678     */
17679
17680
17681    SubsCapsButton.prototype.buildCSSClass = function buildCSSClass() {
17682      return 'vjs-subs-caps-button ' + _TextTrackButton.prototype.buildCSSClass.call(this);
17683    };
17684
17685    SubsCapsButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() {
17686      return 'vjs-subs-caps-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
17687    };
17688
17689    /**
17690     * Create caption/subtitles menu items
17691     *
17692     * @return {CaptionSettingsMenuItem[]}
17693     *         The array of current menu items.
17694     */
17695
17696
17697    SubsCapsButton.prototype.createItems = function createItems() {
17698      var items = [];
17699
17700      if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
17701        items.push(new CaptionSettingsMenuItem(this.player_, { kind: this.label_ }));
17702
17703        this.hideThreshold_ += 1;
17704      }
17705
17706      items = _TextTrackButton.prototype.createItems.call(this, items, SubsCapsMenuItem);
17707      return items;
17708    };
17709
17710    return SubsCapsButton;
17711  }(TextTrackButton);
17712
17713  /**
17714   * `kind`s of TextTrack to look for to associate it with this menu.
17715   *
17716   * @type {array}
17717   * @private
17718   */
17719
17720
17721  SubsCapsButton.prototype.kinds_ = ['captions', 'subtitles'];
17722
17723  /**
17724   * The text that should display over the `SubsCapsButton`s controls.
17725   *
17726   *
17727   * @type {string}
17728   * @private
17729   */
17730  SubsCapsButton.prototype.controlText_ = 'Subtitles';
17731
17732  Component.registerComponent('SubsCapsButton', SubsCapsButton);
17733
17734  /**
17735   * @file audio-track-menu-item.js
17736   */
17737
17738  /**
17739   * An {@link AudioTrack} {@link MenuItem}
17740   *
17741   * @extends MenuItem
17742   */
17743
17744  var AudioTrackMenuItem = function (_MenuItem) {
17745    inherits(AudioTrackMenuItem, _MenuItem);
17746
17747    /**
17748     * Creates an instance of this class.
17749     *
17750     * @param {Player} player
17751     *        The `Player` that this class should be attached to.
17752     *
17753     * @param {Object} [options]
17754     *        The key/value store of player options.
17755     */
17756    function AudioTrackMenuItem(player, options) {
17757      classCallCheck(this, AudioTrackMenuItem);
17758
17759      var track = options.track;
17760      var tracks = player.audioTracks();
17761
17762      // Modify options for parent MenuItem class's init.
17763      options.label = track.label || track.language || 'Unknown';
17764      options.selected = track.enabled;
17765
17766      var _this = possibleConstructorReturn(this, _MenuItem.call(this, player, options));
17767
17768      _this.track = track;
17769
17770      _this.addClass('vjs-' + track.kind + '-menu-item');
17771
17772      var changeHandler = function changeHandler() {
17773        for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
17774          args[_key] = arguments[_key];
17775        }
17776
17777        _this.handleTracksChange.apply(_this, args);
17778      };
17779
17780      tracks.addEventListener('change', changeHandler);
17781      _this.on('dispose', function () {
17782        tracks.removeEventListener('change', changeHandler);
17783      });
17784      return _this;
17785    }
17786
17787    AudioTrackMenuItem.prototype.createEl = function createEl(type, props, attrs) {
17788      var innerHTML = '<span class="vjs-menu-item-text">' + this.localize(this.options_.label);
17789
17790      if (this.options_.track.kind === 'main-desc') {
17791        innerHTML += '\n        <span aria-hidden="true" class="vjs-icon-placeholder"></span>\n        <span class="vjs-control-text"> ' + this.localize('Descriptions') + '</span>\n      ';
17792      }
17793
17794      innerHTML += '</span>';
17795
17796      var el = _MenuItem.prototype.createEl.call(this, type, assign({
17797        innerHTML: innerHTML
17798      }, props), attrs);
17799
17800      return el;
17801    };
17802
17803    /**
17804     * This gets called when an `AudioTrackMenuItem is "clicked". See {@link ClickableComponent}
17805     * for more detailed information on what a click can be.
17806     *
17807     * @param {EventTarget~Event} [event]
17808     *        The `keydown`, `tap`, or `click` event that caused this function to be
17809     *        called.
17810     *
17811     * @listens tap
17812     * @listens click
17813     */
17814
17815
17816    AudioTrackMenuItem.prototype.handleClick = function handleClick(event) {
17817      var tracks = this.player_.audioTracks();
17818
17819      _MenuItem.prototype.handleClick.call(this, event);
17820
17821      for (var i = 0; i < tracks.length; i++) {
17822        var track = tracks[i];
17823
17824        track.enabled = track === this.track;
17825      }
17826    };
17827
17828    /**
17829     * Handle any {@link AudioTrack} change.
17830     *
17831     * @param {EventTarget~Event} [event]
17832     *        The {@link AudioTrackList#change} event that caused this to run.
17833     *
17834     * @listens AudioTrackList#change
17835     */
17836
17837
17838    AudioTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) {
17839      this.selected(this.track.enabled);
17840    };
17841
17842    return AudioTrackMenuItem;
17843  }(MenuItem);
17844
17845  Component.registerComponent('AudioTrackMenuItem', AudioTrackMenuItem);
17846
17847  /**
17848   * @file audio-track-button.js
17849   */
17850
17851  /**
17852   * The base class for buttons that toggle specific {@link AudioTrack} types.
17853   *
17854   * @extends TrackButton
17855   */
17856
17857  var AudioTrackButton = function (_TrackButton) {
17858    inherits(AudioTrackButton, _TrackButton);
17859
17860    /**
17861     * Creates an instance of this class.
17862     *
17863     * @param {Player} player
17864     *        The `Player` that this class should be attached to.
17865     *
17866     * @param {Object} [options={}]
17867     *        The key/value store of player options.
17868     */
17869    function AudioTrackButton(player) {
17870      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
17871      classCallCheck(this, AudioTrackButton);
17872
17873      options.tracks = player.audioTracks();
17874
17875      return possibleConstructorReturn(this, _TrackButton.call(this, player, options));
17876    }
17877
17878    /**
17879     * Builds the default DOM `className`.
17880     *
17881     * @return {string}
17882     *         The DOM `className` for this object.
17883     */
17884
17885
17886    AudioTrackButton.prototype.buildCSSClass = function buildCSSClass() {
17887      return 'vjs-audio-button ' + _TrackButton.prototype.buildCSSClass.call(this);
17888    };
17889
17890    AudioTrackButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() {
17891      return 'vjs-audio-button ' + _TrackButton.prototype.buildWrapperCSSClass.call(this);
17892    };
17893
17894    /**
17895     * Create a menu item for each audio track
17896     *
17897     * @param {AudioTrackMenuItem[]} [items=[]]
17898     *        An array of existing menu items to use.
17899     *
17900     * @return {AudioTrackMenuItem[]}
17901     *         An array of menu items
17902     */
17903
17904
17905    AudioTrackButton.prototype.createItems = function createItems() {
17906      var items = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
17907
17908      // if there's only one audio track, there no point in showing it
17909      this.hideThreshold_ = 1;
17910
17911      var tracks = this.player_.audioTracks();
17912
17913      for (var i = 0; i < tracks.length; i++) {
17914        var track = tracks[i];
17915
17916        items.push(new AudioTrackMenuItem(this.player_, {
17917          track: track,
17918          // MenuItem is selectable
17919          selectable: true,
17920          // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
17921          multiSelectable: false
17922        }));
17923      }
17924
17925      return items;
17926    };
17927
17928    return AudioTrackButton;
17929  }(TrackButton);
17930
17931  /**
17932   * The text that should display over the `AudioTrackButton`s controls. Added for localization.
17933   *
17934   * @type {string}
17935   * @private
17936   */
17937
17938
17939  AudioTrackButton.prototype.controlText_ = 'Audio Track';
17940  Component.registerComponent('AudioTrackButton', AudioTrackButton);
17941
17942  /**
17943   * @file playback-rate-menu-item.js
17944   */
17945
17946  /**
17947   * The specific menu item type for selecting a playback rate.
17948   *
17949   * @extends MenuItem
17950   */
17951
17952  var PlaybackRateMenuItem = function (_MenuItem) {
17953    inherits(PlaybackRateMenuItem, _MenuItem);
17954
17955    /**
17956     * Creates an instance of this class.
17957     *
17958     * @param {Player} player
17959     *        The `Player` that this class should be attached to.
17960     *
17961     * @param {Object} [options]
17962     *        The key/value store of player options.
17963     */
17964    function PlaybackRateMenuItem(player, options) {
17965      classCallCheck(this, PlaybackRateMenuItem);
17966
17967      var label = options.rate;
17968      var rate = parseFloat(label, 10);
17969
17970      // Modify options for parent MenuItem class's init.
17971      options.label = label;
17972      options.selected = rate === 1;
17973      options.selectable = true;
17974      options.multiSelectable = false;
17975
17976      var _this = possibleConstructorReturn(this, _MenuItem.call(this, player, options));
17977
17978      _this.label = label;
17979      _this.rate = rate;
17980
17981      _this.on(player, 'ratechange', _this.update);
17982      return _this;
17983    }
17984
17985    /**
17986     * This gets called when an `PlaybackRateMenuItem` is "clicked". See
17987     * {@link ClickableComponent} for more detailed information on what a click can be.
17988     *
17989     * @param {EventTarget~Event} [event]
17990     *        The `keydown`, `tap`, or `click` event that caused this function to be
17991     *        called.
17992     *
17993     * @listens tap
17994     * @listens click
17995     */
17996
17997
17998    PlaybackRateMenuItem.prototype.handleClick = function handleClick(event) {
17999      _MenuItem.prototype.handleClick.call(this);
18000      this.player().playbackRate(this.rate);
18001    };
18002
18003    /**
18004     * Update the PlaybackRateMenuItem when the playbackrate changes.
18005     *
18006     * @param {EventTarget~Event} [event]
18007     *        The `ratechange` event that caused this function to run.
18008     *
18009     * @listens Player#ratechange
18010     */
18011
18012
18013    PlaybackRateMenuItem.prototype.update = function update(event) {
18014      this.selected(this.player().playbackRate() === this.rate);
18015    };
18016
18017    return PlaybackRateMenuItem;
18018  }(MenuItem);
18019
18020  /**
18021   * The text that should display over the `PlaybackRateMenuItem`s controls. Added for localization.
18022   *
18023   * @type {string}
18024   * @private
18025   */
18026
18027
18028  PlaybackRateMenuItem.prototype.contentElType = 'button';
18029
18030  Component.registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem);
18031
18032  /**
18033   * @file playback-rate-menu-button.js
18034   */
18035
18036  /**
18037   * The component for controlling the playback rate.
18038   *
18039   * @extends MenuButton
18040   */
18041
18042  var PlaybackRateMenuButton = function (_MenuButton) {
18043    inherits(PlaybackRateMenuButton, _MenuButton);
18044
18045    /**
18046     * Creates an instance of this class.
18047     *
18048     * @param {Player} player
18049     *        The `Player` that this class should be attached to.
18050     *
18051     * @param {Object} [options]
18052     *        The key/value store of player options.
18053     */
18054    function PlaybackRateMenuButton(player, options) {
18055      classCallCheck(this, PlaybackRateMenuButton);
18056
18057      var _this = possibleConstructorReturn(this, _MenuButton.call(this, player, options));
18058
18059      _this.updateVisibility();
18060      _this.updateLabel();
18061
18062      _this.on(player, 'loadstart', _this.updateVisibility);
18063      _this.on(player, 'ratechange', _this.updateLabel);
18064      return _this;
18065    }
18066
18067    /**
18068     * Create the `Component`'s DOM element
18069     *
18070     * @return {Element}
18071     *         The element that was created.
18072     */
18073
18074
18075    PlaybackRateMenuButton.prototype.createEl = function createEl$$1() {
18076      var el = _MenuButton.prototype.createEl.call(this);
18077
18078      this.labelEl_ = createEl('div', {
18079        className: 'vjs-playback-rate-value',
18080        innerHTML: '1x'
18081      });
18082
18083      el.appendChild(this.labelEl_);
18084
18085      return el;
18086    };
18087
18088    PlaybackRateMenuButton.prototype.dispose = function dispose() {
18089      this.labelEl_ = null;
18090
18091      _MenuButton.prototype.dispose.call(this);
18092    };
18093
18094    /**
18095     * Builds the default DOM `className`.
18096     *
18097     * @return {string}
18098     *         The DOM `className` for this object.
18099     */
18100
18101
18102    PlaybackRateMenuButton.prototype.buildCSSClass = function buildCSSClass() {
18103      return 'vjs-playback-rate ' + _MenuButton.prototype.buildCSSClass.call(this);
18104    };
18105
18106    PlaybackRateMenuButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() {
18107      return 'vjs-playback-rate ' + _MenuButton.prototype.buildWrapperCSSClass.call(this);
18108    };
18109
18110    /**
18111     * Create the playback rate menu
18112     *
18113     * @return {Menu}
18114     *         Menu object populated with {@link PlaybackRateMenuItem}s
18115     */
18116
18117
18118    PlaybackRateMenuButton.prototype.createMenu = function createMenu() {
18119      var menu = new Menu(this.player());
18120      var rates = this.playbackRates();
18121
18122      if (rates) {
18123        for (var i = rates.length - 1; i >= 0; i--) {
18124          menu.addChild(new PlaybackRateMenuItem(this.player(), { rate: rates[i] + 'x' }));
18125        }
18126      }
18127
18128      return menu;
18129    };
18130
18131    /**
18132     * Updates ARIA accessibility attributes
18133     */
18134
18135
18136    PlaybackRateMenuButton.prototype.updateARIAAttributes = function updateARIAAttributes() {
18137      // Current playback rate
18138      this.el().setAttribute('aria-valuenow', this.player().playbackRate());
18139    };
18140
18141    /**
18142     * This gets called when an `PlaybackRateMenuButton` is "clicked". See
18143     * {@link ClickableComponent} for more detailed information on what a click can be.
18144     *
18145     * @param {EventTarget~Event} [event]
18146     *        The `keydown`, `tap`, or `click` event that caused this function to be
18147     *        called.
18148     *
18149     * @listens tap
18150     * @listens click
18151     */
18152
18153
18154    PlaybackRateMenuButton.prototype.handleClick = function handleClick(event) {
18155      // select next rate option
18156      var currentRate = this.player().playbackRate();
18157      var rates = this.playbackRates();
18158
18159      // this will select first one if the last one currently selected
18160      var newRate = rates[0];
18161
18162      for (var i = 0; i < rates.length; i++) {
18163        if (rates[i] > currentRate) {
18164          newRate = rates[i];
18165          break;
18166        }
18167      }
18168      this.player().playbackRate(newRate);
18169    };
18170
18171    /**
18172     * Get possible playback rates
18173     *
18174     * @return {Array}
18175     *         All possible playback rates
18176     */
18177
18178
18179    PlaybackRateMenuButton.prototype.playbackRates = function playbackRates() {
18180      return this.options_.playbackRates || this.options_.playerOptions && this.options_.playerOptions.playbackRates;
18181    };
18182
18183    /**
18184     * Get whether playback rates is supported by the tech
18185     * and an array of playback rates exists
18186     *
18187     * @return {boolean}
18188     *         Whether changing playback rate is supported
18189     */
18190
18191
18192    PlaybackRateMenuButton.prototype.playbackRateSupported = function playbackRateSupported() {
18193      return this.player().tech_ && this.player().tech_.featuresPlaybackRate && this.playbackRates() && this.playbackRates().length > 0;
18194    };
18195
18196    /**
18197     * Hide playback rate controls when they're no playback rate options to select
18198     *
18199     * @param {EventTarget~Event} [event]
18200     *        The event that caused this function to run.
18201     *
18202     * @listens Player#loadstart
18203     */
18204
18205
18206    PlaybackRateMenuButton.prototype.updateVisibility = function updateVisibility(event) {
18207      if (this.playbackRateSupported()) {
18208        this.removeClass('vjs-hidden');
18209      } else {
18210        this.addClass('vjs-hidden');
18211      }
18212    };
18213
18214    /**
18215     * Update button label when rate changed
18216     *
18217     * @param {EventTarget~Event} [event]
18218     *        The event that caused this function to run.
18219     *
18220     * @listens Player#ratechange
18221     */
18222
18223
18224    PlaybackRateMenuButton.prototype.updateLabel = function updateLabel(event) {
18225      if (this.playbackRateSupported()) {
18226        this.labelEl_.innerHTML = this.player().playbackRate() + 'x';
18227      }
18228    };
18229
18230    return PlaybackRateMenuButton;
18231  }(MenuButton);
18232
18233  /**
18234   * The text that should display over the `FullscreenToggle`s controls. Added for localization.
18235   *
18236   * @type {string}
18237   * @private
18238   */
18239
18240
18241  PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate';
18242
18243  Component.registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton);
18244
18245  /**
18246   * @file spacer.js
18247   */
18248
18249  /**
18250   * Just an empty spacer element that can be used as an append point for plugins, etc.
18251   * Also can be used to create space between elements when necessary.
18252   *
18253   * @extends Component
18254   */
18255
18256  var Spacer = function (_Component) {
18257    inherits(Spacer, _Component);
18258
18259    function Spacer() {
18260      classCallCheck(this, Spacer);
18261      return possibleConstructorReturn(this, _Component.apply(this, arguments));
18262    }
18263
18264    /**
18265     * Builds the default DOM `className`.
18266     *
18267     * @return {string}
18268     *         The DOM `className` for this object.
18269     */
18270    Spacer.prototype.buildCSSClass = function buildCSSClass() {
18271      return 'vjs-spacer ' + _Component.prototype.buildCSSClass.call(this);
18272    };
18273
18274    /**
18275     * Create the `Component`'s DOM element
18276     *
18277     * @return {Element}
18278     *         The element that was created.
18279     */
18280
18281
18282    Spacer.prototype.createEl = function createEl() {
18283      return _Component.prototype.createEl.call(this, 'div', {
18284        className: this.buildCSSClass()
18285      });
18286    };
18287
18288    return Spacer;
18289  }(Component);
18290
18291  Component.registerComponent('Spacer', Spacer);
18292
18293  /**
18294   * @file custom-control-spacer.js
18295   */
18296
18297  /**
18298   * Spacer specifically meant to be used as an insertion point for new plugins, etc.
18299   *
18300   * @extends Spacer
18301   */
18302
18303  var CustomControlSpacer = function (_Spacer) {
18304    inherits(CustomControlSpacer, _Spacer);
18305
18306    function CustomControlSpacer() {
18307      classCallCheck(this, CustomControlSpacer);
18308      return possibleConstructorReturn(this, _Spacer.apply(this, arguments));
18309    }
18310
18311    /**
18312     * Builds the default DOM `className`.
18313     *
18314     * @return {string}
18315     *         The DOM `className` for this object.
18316     */
18317    CustomControlSpacer.prototype.buildCSSClass = function buildCSSClass() {
18318      return 'vjs-custom-control-spacer ' + _Spacer.prototype.buildCSSClass.call(this);
18319    };
18320
18321    /**
18322     * Create the `Component`'s DOM element
18323     *
18324     * @return {Element}
18325     *         The element that was created.
18326     */
18327
18328
18329    CustomControlSpacer.prototype.createEl = function createEl() {
18330      var el = _Spacer.prototype.createEl.call(this, {
18331        className: this.buildCSSClass()
18332      });
18333
18334      // No-flex/table-cell mode requires there be some content
18335      // in the cell to fill the remaining space of the table.
18336      el.innerHTML = '\xA0';
18337      return el;
18338    };
18339
18340    return CustomControlSpacer;
18341  }(Spacer);
18342
18343  Component.registerComponent('CustomControlSpacer', CustomControlSpacer);
18344
18345  /**
18346   * @file control-bar.js
18347   */
18348
18349  /**
18350   * Container of main controls.
18351   *
18352   * @extends Component
18353   */
18354
18355  var ControlBar = function (_Component) {
18356    inherits(ControlBar, _Component);
18357
18358    function ControlBar() {
18359      classCallCheck(this, ControlBar);
18360      return possibleConstructorReturn(this, _Component.apply(this, arguments));
18361    }
18362
18363    /**
18364     * Create the `Component`'s DOM element
18365     *
18366     * @return {Element}
18367     *         The element that was created.
18368     */
18369    ControlBar.prototype.createEl = function createEl() {
18370      return _Component.prototype.createEl.call(this, 'div', {
18371        className: 'vjs-control-bar',
18372        dir: 'ltr'
18373      });
18374    };
18375
18376    return ControlBar;
18377  }(Component);
18378
18379  /**
18380   * Default options for `ControlBar`
18381   *
18382   * @type {Object}
18383   * @private
18384   */
18385
18386
18387  ControlBar.prototype.options_ = {
18388    children: ['playToggle', 'volumePanel', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'descriptionsButton', 'subsCapsButton', 'audioTrackButton', 'fullscreenToggle']
18389  };
18390
18391  Component.registerComponent('ControlBar', ControlBar);
18392
18393  /**
18394   * @file error-display.js
18395   */
18396
18397  /**
18398   * A display that indicates an error has occurred. This means that the video
18399   * is unplayable.
18400   *
18401   * @extends ModalDialog
18402   */
18403
18404  var ErrorDisplay = function (_ModalDialog) {
18405    inherits(ErrorDisplay, _ModalDialog);
18406
18407    /**
18408     * Creates an instance of this class.
18409     *
18410     * @param  {Player} player
18411     *         The `Player` that this class should be attached to.
18412     *
18413     * @param  {Object} [options]
18414     *         The key/value store of player options.
18415     */
18416    function ErrorDisplay(player, options) {
18417      classCallCheck(this, ErrorDisplay);
18418
18419      var _this = possibleConstructorReturn(this, _ModalDialog.call(this, player, options));
18420
18421      _this.on(player, 'error', _this.open);
18422      return _this;
18423    }
18424
18425    /**
18426     * Builds the default DOM `className`.
18427     *
18428     * @return {string}
18429     *         The DOM `className` for this object.
18430     *
18431     * @deprecated Since version 5.
18432     */
18433
18434
18435    ErrorDisplay.prototype.buildCSSClass = function buildCSSClass() {
18436      return 'vjs-error-display ' + _ModalDialog.prototype.buildCSSClass.call(this);
18437    };
18438
18439    /**
18440     * Gets the localized error message based on the `Player`s error.
18441     *
18442     * @return {string}
18443     *         The `Player`s error message localized or an empty string.
18444     */
18445
18446
18447    ErrorDisplay.prototype.content = function content() {
18448      var error = this.player().error();
18449
18450      return error ? this.localize(error.message) : '';
18451    };
18452
18453    return ErrorDisplay;
18454  }(ModalDialog);
18455
18456  /**
18457   * The default options for an `ErrorDisplay`.
18458   *
18459   * @private
18460   */
18461
18462
18463  ErrorDisplay.prototype.options_ = mergeOptions(ModalDialog.prototype.options_, {
18464    pauseOnOpen: false,
18465    fillAlways: true,
18466    temporary: false,
18467    uncloseable: true
18468  });
18469
18470  Component.registerComponent('ErrorDisplay', ErrorDisplay);
18471
18472  /**
18473   * @file text-track-settings.js
18474   */
18475
18476  var LOCAL_STORAGE_KEY = 'vjs-text-track-settings';
18477
18478  var COLOR_BLACK = ['#000', 'Black'];
18479  var COLOR_BLUE = ['#00F', 'Blue'];
18480  var COLOR_CYAN = ['#0FF', 'Cyan'];
18481  var COLOR_GREEN = ['#0F0', 'Green'];
18482  var COLOR_MAGENTA = ['#F0F', 'Magenta'];
18483  var COLOR_RED = ['#F00', 'Red'];
18484  var COLOR_WHITE = ['#FFF', 'White'];
18485  var COLOR_YELLOW = ['#FF0', 'Yellow'];
18486
18487  var OPACITY_OPAQUE = ['1', 'Opaque'];
18488  var OPACITY_SEMI = ['0.5', 'Semi-Transparent'];
18489  var OPACITY_TRANS = ['0', 'Transparent'];
18490
18491  // Configuration for the various <select> elements in the DOM of this component.
18492  //
18493  // Possible keys include:
18494  //
18495  // `default`:
18496  //   The default option index. Only needs to be provided if not zero.
18497  // `parser`:
18498  //   A function which is used to parse the value from the selected option in
18499  //   a customized way.
18500  // `selector`:
18501  //   The selector used to find the associated <select> element.
18502  var selectConfigs = {
18503    backgroundColor: {
18504      selector: '.vjs-bg-color > select',
18505      id: 'captions-background-color-%s',
18506      label: 'Color',
18507      options: [COLOR_BLACK, COLOR_WHITE, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
18508    },
18509
18510    backgroundOpacity: {
18511      selector: '.vjs-bg-opacity > select',
18512      id: 'captions-background-opacity-%s',
18513      label: 'Transparency',
18514      options: [OPACITY_OPAQUE, OPACITY_SEMI, OPACITY_TRANS]
18515    },
18516
18517    color: {
18518      selector: '.vjs-fg-color > select',
18519      id: 'captions-foreground-color-%s',
18520      label: 'Color',
18521      options: [COLOR_WHITE, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
18522    },
18523
18524    edgeStyle: {
18525      selector: '.vjs-edge-style > select',
18526      id: '%s',
18527      label: 'Text Edge Style',
18528      options: [['none', 'None'], ['raised', 'Raised'], ['depressed', 'Depressed'], ['uniform', 'Uniform'], ['dropshadow', 'Dropshadow']]
18529    },
18530
18531    fontFamily: {
18532      selector: '.vjs-font-family > select',
18533      id: 'captions-font-family-%s',
18534      label: 'Font Family',
18535      options: [['proportionalSansSerif', 'Proportional Sans-Serif'], ['monospaceSansSerif', 'Monospace Sans-Serif'], ['proportionalSerif', 'Proportional Serif'], ['monospaceSerif', 'Monospace Serif'], ['casual', 'Casual'], ['script', 'Script'], ['small-caps', 'Small Caps']]
18536    },
18537
18538    fontPercent: {
18539      selector: '.vjs-font-percent > select',
18540      id: 'captions-font-size-%s',
18541      label: 'Font Size',
18542      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%']],
18543      default: 2,
18544      parser: function parser(v) {
18545        return v === '1.00' ? null : Number(v);
18546      }
18547    },
18548
18549    textOpacity: {
18550      selector: '.vjs-text-opacity > select',
18551      id: 'captions-foreground-opacity-%s',
18552      label: 'Transparency',
18553      options: [OPACITY_OPAQUE, OPACITY_SEMI]
18554    },
18555
18556    // Options for this object are defined below.
18557    windowColor: {
18558      selector: '.vjs-window-color > select',
18559      id: 'captions-window-color-%s',
18560      label: 'Color'
18561    },
18562
18563    // Options for this object are defined below.
18564    windowOpacity: {
18565      selector: '.vjs-window-opacity > select',
18566      id: 'captions-window-opacity-%s',
18567      label: 'Transparency',
18568      options: [OPACITY_TRANS, OPACITY_SEMI, OPACITY_OPAQUE]
18569    }
18570  };
18571
18572  selectConfigs.windowColor.options = selectConfigs.backgroundColor.options;
18573
18574  /**
18575   * Get the actual value of an option.
18576   *
18577   * @param  {string} value
18578   *         The value to get
18579   *
18580   * @param  {Function} [parser]
18581   *         Optional function to adjust the value.
18582   *
18583   * @return {Mixed}
18584   *         - Will be `undefined` if no value exists
18585   *         - Will be `undefined` if the given value is "none".
18586   *         - Will be the actual value otherwise.
18587   *
18588   * @private
18589   */
18590  function parseOptionValue(value, parser) {
18591    if (parser) {
18592      value = parser(value);
18593    }
18594
18595    if (value && value !== 'none') {
18596      return value;
18597    }
18598  }
18599
18600  /**
18601   * Gets the value of the selected <option> element within a <select> element.
18602   *
18603   * @param  {Element} el
18604   *         the element to look in
18605   *
18606   * @param  {Function} [parser]
18607   *         Optional function to adjust the value.
18608   *
18609   * @return {Mixed}
18610   *         - Will be `undefined` if no value exists
18611   *         - Will be `undefined` if the given value is "none".
18612   *         - Will be the actual value otherwise.
18613   *
18614   * @private
18615   */
18616  function getSelectedOptionValue(el, parser) {
18617    var value = el.options[el.options.selectedIndex].value;
18618
18619    return parseOptionValue(value, parser);
18620  }
18621
18622  /**
18623   * Sets the selected <option> element within a <select> element based on a
18624   * given value.
18625   *
18626   * @param {Element} el
18627   *        The element to look in.
18628   *
18629   * @param {string} value
18630   *        the property to look on.
18631   *
18632   * @param {Function} [parser]
18633   *        Optional function to adjust the value before comparing.
18634   *
18635   * @private
18636   */
18637  function setSelectedOption(el, value, parser) {
18638    if (!value) {
18639      return;
18640    }
18641
18642    for (var i = 0; i < el.options.length; i++) {
18643      if (parseOptionValue(el.options[i].value, parser) === value) {
18644        el.selectedIndex = i;
18645        break;
18646      }
18647    }
18648  }
18649
18650  /**
18651   * Manipulate Text Tracks settings.
18652   *
18653   * @extends ModalDialog
18654   */
18655
18656  var TextTrackSettings = function (_ModalDialog) {
18657    inherits(TextTrackSettings, _ModalDialog);
18658
18659    /**
18660     * Creates an instance of this class.
18661     *
18662     * @param {Player} player
18663     *         The `Player` that this class should be attached to.
18664     *
18665     * @param {Object} [options]
18666     *         The key/value store of player options.
18667     */
18668    function TextTrackSettings(player, options) {
18669      classCallCheck(this, TextTrackSettings);
18670
18671      options.temporary = false;
18672
18673      var _this = possibleConstructorReturn(this, _ModalDialog.call(this, player, options));
18674
18675      _this.updateDisplay = bind(_this, _this.updateDisplay);
18676
18677      // fill the modal and pretend we have opened it
18678      _this.fill();
18679      _this.hasBeenOpened_ = _this.hasBeenFilled_ = true;
18680
18681      _this.endDialog = createEl('p', {
18682        className: 'vjs-control-text',
18683        textContent: _this.localize('End of dialog window.')
18684      });
18685      _this.el().appendChild(_this.endDialog);
18686
18687      _this.setDefaults();
18688
18689      // Grab `persistTextTrackSettings` from the player options if not passed in child options
18690      if (options.persistTextTrackSettings === undefined) {
18691        _this.options_.persistTextTrackSettings = _this.options_.playerOptions.persistTextTrackSettings;
18692      }
18693
18694      _this.on(_this.$('.vjs-done-button'), 'click', function () {
18695        _this.saveSettings();
18696        _this.close();
18697      });
18698
18699      _this.on(_this.$('.vjs-default-button'), 'click', function () {
18700        _this.setDefaults();
18701        _this.updateDisplay();
18702      });
18703
18704      each(selectConfigs, function (config) {
18705        _this.on(_this.$(config.selector), 'change', _this.updateDisplay);
18706      });
18707
18708      if (_this.options_.persistTextTrackSettings) {
18709        _this.restoreSettings();
18710      }
18711      return _this;
18712    }
18713
18714    TextTrackSettings.prototype.dispose = function dispose() {
18715      this.endDialog = null;
18716
18717      _ModalDialog.prototype.dispose.call(this);
18718    };
18719
18720    /**
18721     * Create a <select> element with configured options.
18722     *
18723     * @param {string} key
18724     *        Configuration key to use during creation.
18725     *
18726     * @return {string}
18727     *         An HTML string.
18728     *
18729     * @private
18730     */
18731
18732
18733    TextTrackSettings.prototype.createElSelect_ = function createElSelect_(key) {
18734      var _this2 = this;
18735
18736      var legendId = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
18737      var type = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'label';
18738
18739      var config = selectConfigs[key];
18740      var id = config.id.replace('%s', this.id_);
18741      var selectLabelledbyIds = [legendId, id].join(' ').trim();
18742
18743      return ['<' + type + ' id="' + id + '" class="' + (type === 'label' ? 'vjs-label' : '') + '">', this.localize(config.label), '</' + type + '>', '<select aria-labelledby="' + selectLabelledbyIds + '">'].concat(config.options.map(function (o) {
18744        var optionId = id + '-' + o[1].replace(/\W+/g, '');
18745
18746        return ['<option id="' + optionId + '" value="' + o[0] + '" ', 'aria-labelledby="' + selectLabelledbyIds + ' ' + optionId + '">', _this2.localize(o[1]), '</option>'].join('');
18747      })).concat('</select>').join('');
18748    };
18749
18750    /**
18751     * Create foreground color element for the component
18752     *
18753     * @return {string}
18754     *         An HTML string.
18755     *
18756     * @private
18757     */
18758
18759
18760    TextTrackSettings.prototype.createElFgColor_ = function createElFgColor_() {
18761      var legendId = 'captions-text-legend-' + this.id_;
18762
18763      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('');
18764    };
18765
18766    /**
18767     * Create background color element for the component
18768     *
18769     * @return {string}
18770     *         An HTML string.
18771     *
18772     * @private
18773     */
18774
18775
18776    TextTrackSettings.prototype.createElBgColor_ = function createElBgColor_() {
18777      var legendId = 'captions-background-' + this.id_;
18778
18779      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('');
18780    };
18781
18782    /**
18783     * Create window color element for the component
18784     *
18785     * @return {string}
18786     *         An HTML string.
18787     *
18788     * @private
18789     */
18790
18791
18792    TextTrackSettings.prototype.createElWinColor_ = function createElWinColor_() {
18793      var legendId = 'captions-window-' + this.id_;
18794
18795      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('');
18796    };
18797
18798    /**
18799     * Create color elements for the component
18800     *
18801     * @return {Element}
18802     *         The element that was created
18803     *
18804     * @private
18805     */
18806
18807
18808    TextTrackSettings.prototype.createElColors_ = function createElColors_() {
18809      return createEl('div', {
18810        className: 'vjs-track-settings-colors',
18811        innerHTML: [this.createElFgColor_(), this.createElBgColor_(), this.createElWinColor_()].join('')
18812      });
18813    };
18814
18815    /**
18816     * Create font elements for the component
18817     *
18818     * @return {Element}
18819     *         The element that was created.
18820     *
18821     * @private
18822     */
18823
18824
18825    TextTrackSettings.prototype.createElFont_ = function createElFont_() {
18826      return createEl('div', {
18827        className: 'vjs-track-settings-font',
18828        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('')
18829      });
18830    };
18831
18832    /**
18833     * Create controls for the component
18834     *
18835     * @return {Element}
18836     *         The element that was created.
18837     *
18838     * @private
18839     */
18840
18841
18842    TextTrackSettings.prototype.createElControls_ = function createElControls_() {
18843      var defaultsDescription = this.localize('restore all settings to the default values');
18844
18845      return createEl('div', {
18846        className: 'vjs-track-settings-controls',
18847        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('')
18848      });
18849    };
18850
18851    TextTrackSettings.prototype.content = function content() {
18852      return [this.createElColors_(), this.createElFont_(), this.createElControls_()];
18853    };
18854
18855    TextTrackSettings.prototype.label = function label() {
18856      return this.localize('Caption Settings Dialog');
18857    };
18858
18859    TextTrackSettings.prototype.description = function description() {
18860      return this.localize('Beginning of dialog window. Escape will cancel and close the window.');
18861    };
18862
18863    TextTrackSettings.prototype.buildCSSClass = function buildCSSClass() {
18864      return _ModalDialog.prototype.buildCSSClass.call(this) + ' vjs-text-track-settings';
18865    };
18866
18867    /**
18868     * Gets an object of text track settings (or null).
18869     *
18870     * @return {Object}
18871     *         An object with config values parsed from the DOM or localStorage.
18872     */
18873
18874
18875    TextTrackSettings.prototype.getValues = function getValues() {
18876      var _this3 = this;
18877
18878      return reduce(selectConfigs, function (accum, config, key) {
18879        var value = getSelectedOptionValue(_this3.$(config.selector), config.parser);
18880
18881        if (value !== undefined) {
18882          accum[key] = value;
18883        }
18884
18885        return accum;
18886      }, {});
18887    };
18888
18889    /**
18890     * Sets text track settings from an object of values.
18891     *
18892     * @param {Object} values
18893     *        An object with config values parsed from the DOM or localStorage.
18894     */
18895
18896
18897    TextTrackSettings.prototype.setValues = function setValues(values) {
18898      var _this4 = this;
18899
18900      each(selectConfigs, function (config, key) {
18901        setSelectedOption(_this4.$(config.selector), values[key], config.parser);
18902      });
18903    };
18904
18905    /**
18906     * Sets all `<select>` elements to their default values.
18907     */
18908
18909
18910    TextTrackSettings.prototype.setDefaults = function setDefaults() {
18911      var _this5 = this;
18912
18913      each(selectConfigs, function (config) {
18914        var index = config.hasOwnProperty('default') ? config.default : 0;
18915
18916        _this5.$(config.selector).selectedIndex = index;
18917      });
18918    };
18919
18920    /**
18921     * Restore texttrack settings from localStorage
18922     */
18923
18924
18925    TextTrackSettings.prototype.restoreSettings = function restoreSettings() {
18926      var values = void 0;
18927
18928      try {
18929        values = JSON.parse(window_1.localStorage.getItem(LOCAL_STORAGE_KEY));
18930      } catch (err) {
18931        log$1.warn(err);
18932      }
18933
18934      if (values) {
18935        this.setValues(values);
18936      }
18937    };
18938
18939    /**
18940     * Save text track settings to localStorage
18941     */
18942
18943
18944    TextTrackSettings.prototype.saveSettings = function saveSettings() {
18945      if (!this.options_.persistTextTrackSettings) {
18946        return;
18947      }
18948
18949      var values = this.getValues();
18950
18951      try {
18952        if (Object.keys(values).length) {
18953          window_1.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(values));
18954        } else {
18955          window_1.localStorage.removeItem(LOCAL_STORAGE_KEY);
18956        }
18957      } catch (err) {
18958        log$1.warn(err);
18959      }
18960    };
18961
18962    /**
18963     * Update display of text track settings
18964     */
18965
18966
18967    TextTrackSettings.prototype.updateDisplay = function updateDisplay() {
18968      var ttDisplay = this.player_.getChild('textTrackDisplay');
18969
18970      if (ttDisplay) {
18971        ttDisplay.updateDisplay();
18972      }
18973    };
18974
18975    /**
18976     * conditionally blur the element and refocus the captions button
18977     *
18978     * @private
18979     */
18980
18981
18982    TextTrackSettings.prototype.conditionalBlur_ = function conditionalBlur_() {
18983      this.previouslyActiveEl_ = null;
18984      this.off(document_1, 'keydown', this.handleKeyDown);
18985
18986      var cb = this.player_.controlBar;
18987      var subsCapsBtn = cb && cb.subsCapsButton;
18988      var ccBtn = cb && cb.captionsButton;
18989
18990      if (subsCapsBtn) {
18991        subsCapsBtn.focus();
18992      } else if (ccBtn) {
18993        ccBtn.focus();
18994      }
18995    };
18996
18997    return TextTrackSettings;
18998  }(ModalDialog);
18999
19000  Component.registerComponent('TextTrackSettings', TextTrackSettings);
19001
19002  /**
19003   * @file resize-manager.js
19004   */
19005
19006  /**
19007   * A Resize Manager. It is in charge of triggering `playerresize` on the player in the right conditions.
19008   *
19009   * 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}.
19010   *
19011   * If the ResizeObserver is available natively, it will be used. A polyfill can be passed in as an option.
19012   * If a `playerresize` event is not needed, the ResizeManager component can be removed from the player, see the example below.
19013   * @example <caption>How to disable the resize manager</caption>
19014   * const player = videojs('#vid', {
19015   *   resizeManager: false
19016   * });
19017   *
19018   * @see {@link https://wicg.github.io/ResizeObserver/|ResizeObserver specification}
19019   *
19020   * @extends Component
19021   */
19022
19023  var ResizeManager = function (_Component) {
19024    inherits(ResizeManager, _Component);
19025
19026    /**
19027     * Create the ResizeManager.
19028     *
19029     * @param {Object} player
19030     *        The `Player` that this class should be attached to.
19031     *
19032     * @param {Object} [options]
19033     *        The key/value store of ResizeManager options.
19034     *
19035     * @param {Object} [options.ResizeObserver]
19036     *        A polyfill for ResizeObserver can be passed in here.
19037     *        If this is set to null it will ignore the native ResizeObserver and fall back to the iframe fallback.
19038     */
19039    function ResizeManager(player, options) {
19040      classCallCheck(this, ResizeManager);
19041
19042      var RESIZE_OBSERVER_AVAILABLE = options.ResizeObserver || window_1.ResizeObserver;
19043
19044      // if `null` was passed, we want to disable the ResizeObserver
19045      if (options.ResizeObserver === null) {
19046        RESIZE_OBSERVER_AVAILABLE = false;
19047      }
19048
19049      // Only create an element when ResizeObserver isn't available
19050      var options_ = mergeOptions({ createEl: !RESIZE_OBSERVER_AVAILABLE }, options);
19051
19052      var _this = possibleConstructorReturn(this, _Component.call(this, player, options_));
19053
19054      _this.ResizeObserver = options.ResizeObserver || window_1.ResizeObserver;
19055      _this.loadListener_ = null;
19056      _this.resizeObserver_ = null;
19057      _this.debouncedHandler_ = debounce(function () {
19058        _this.resizeHandler();
19059      }, 100, false, player);
19060
19061      if (RESIZE_OBSERVER_AVAILABLE) {
19062        _this.resizeObserver_ = new _this.ResizeObserver(_this.debouncedHandler_);
19063        _this.resizeObserver_.observe(player.el());
19064      } else {
19065        _this.loadListener_ = function () {
19066          if (_this.el_.contentWindow) {
19067            on(_this.el_.contentWindow, 'resize', _this.debouncedHandler_);
19068          }
19069          _this.off('load', _this.loadListener_);
19070        };
19071
19072        _this.on('load', _this.loadListener_);
19073      }
19074      return _this;
19075    }
19076
19077    ResizeManager.prototype.createEl = function createEl() {
19078      return _Component.prototype.createEl.call(this, 'iframe', {
19079        className: 'vjs-resize-manager'
19080      });
19081    };
19082
19083    /**
19084     * Called when a resize is triggered on the iframe or a resize is observed via the ResizeObserver
19085     *
19086     * @fires Player#playerresize
19087     */
19088
19089
19090    ResizeManager.prototype.resizeHandler = function resizeHandler() {
19091      /**
19092       * Called when the player size has changed
19093       *
19094       * @event Player#playerresize
19095       * @type {EventTarget~Event}
19096       */
19097      this.player_.trigger('playerresize');
19098    };
19099
19100    ResizeManager.prototype.dispose = function dispose() {
19101      if (this.resizeObserver_) {
19102        if (this.player_.el()) {
19103          this.resizeObserver_.unobserve(this.player_.el());
19104        }
19105        this.resizeObserver_.disconnect();
19106      }
19107
19108      if (this.el_ && this.el_.contentWindow) {
19109        off(this.el_.contentWindow, 'resize', this.debouncedHandler_);
19110      }
19111
19112      if (this.loadListener_) {
19113        this.off('load', this.loadListener_);
19114      }
19115
19116      this.ResizeObserver = null;
19117      this.resizeObserver = null;
19118      this.debouncedHandler_ = null;
19119      this.loadListener_ = null;
19120    };
19121
19122    return ResizeManager;
19123  }(Component);
19124
19125  Component.registerComponent('ResizeManager', ResizeManager);
19126
19127  /**
19128   * This function is used to fire a sourceset when there is something
19129   * similar to `mediaEl.load()` being called. It will try to find the source via
19130   * the `src` attribute and then the `<source>` elements. It will then fire `sourceset`
19131   * with the source that was found or empty string if we cannot know. If it cannot
19132   * find a source then `sourceset` will not be fired.
19133   *
19134   * @param {Html5} tech
19135   *        The tech object that sourceset was setup on
19136   *
19137   * @return {boolean}
19138   *         returns false if the sourceset was not fired and true otherwise.
19139   */
19140  var sourcesetLoad = function sourcesetLoad(tech) {
19141    var el = tech.el();
19142
19143    // if `el.src` is set, that source will be loaded.
19144    if (el.hasAttribute('src')) {
19145      tech.triggerSourceset(el.src);
19146      return true;
19147    }
19148
19149    /**
19150     * Since there isn't a src property on the media element, source elements will be used for
19151     * implementing the source selection algorithm. This happens asynchronously and
19152     * for most cases were there is more than one source we cannot tell what source will
19153     * be loaded, without re-implementing the source selection algorithm. At this time we are not
19154     * going to do that. There are three special cases that we do handle here though:
19155     *
19156     * 1. If there are no sources, do not fire `sourceset`.
19157     * 2. If there is only one `<source>` with a `src` property/attribute that is our `src`
19158     * 3. If there is more than one `<source>` but all of them have the same `src` url.
19159     *    That will be our src.
19160     */
19161    var sources = tech.$$('source');
19162    var srcUrls = [];
19163    var src = '';
19164
19165    // if there are no sources, do not fire sourceset
19166    if (!sources.length) {
19167      return false;
19168    }
19169
19170    // only count valid/non-duplicate source elements
19171    for (var i = 0; i < sources.length; i++) {
19172      var url = sources[i].src;
19173
19174      if (url && srcUrls.indexOf(url) === -1) {
19175        srcUrls.push(url);
19176      }
19177    }
19178
19179    // there were no valid sources
19180    if (!srcUrls.length) {
19181      return false;
19182    }
19183
19184    // there is only one valid source element url
19185    // use that
19186    if (srcUrls.length === 1) {
19187      src = srcUrls[0];
19188    }
19189
19190    tech.triggerSourceset(src);
19191    return true;
19192  };
19193
19194  /**
19195   * our implementation of an `innerHTML` descriptor for browsers
19196   * that do not have one.
19197   */
19198  var innerHTMLDescriptorPolyfill = Object.defineProperty({}, 'innerHTML', {
19199    get: function get() {
19200      return this.cloneNode(true).innerHTML;
19201    },
19202    set: function set(v) {
19203      // make a dummy node to use innerHTML on
19204      var dummy = document_1.createElement(this.nodeName.toLowerCase());
19205
19206      // set innerHTML to the value provided
19207      dummy.innerHTML = v;
19208
19209      // make a document fragment to hold the nodes from dummy
19210      var docFrag = document_1.createDocumentFragment();
19211
19212      // copy all of the nodes created by the innerHTML on dummy
19213      // to the document fragment
19214      while (dummy.childNodes.length) {
19215        docFrag.appendChild(dummy.childNodes[0]);
19216      }
19217
19218      // remove content
19219      this.innerText = '';
19220
19221      // now we add all of that html in one by appending the
19222      // document fragment. This is how innerHTML does it.
19223      window_1.Element.prototype.appendChild.call(this, docFrag);
19224
19225      // then return the result that innerHTML's setter would
19226      return this.innerHTML;
19227    }
19228  });
19229
19230  /**
19231   * Get a property descriptor given a list of priorities and the
19232   * property to get.
19233   */
19234  var getDescriptor = function getDescriptor(priority, prop) {
19235    var descriptor = {};
19236
19237    for (var i = 0; i < priority.length; i++) {
19238      descriptor = Object.getOwnPropertyDescriptor(priority[i], prop);
19239
19240      if (descriptor && descriptor.set && descriptor.get) {
19241        break;
19242      }
19243    }
19244
19245    descriptor.enumerable = true;
19246    descriptor.configurable = true;
19247
19248    return descriptor;
19249  };
19250
19251  var getInnerHTMLDescriptor = function getInnerHTMLDescriptor(tech) {
19252    return getDescriptor([tech.el(), window_1.HTMLMediaElement.prototype, window_1.Element.prototype, innerHTMLDescriptorPolyfill], 'innerHTML');
19253  };
19254
19255  /**
19256   * Patches browser internal functions so that we can tell synchronously
19257   * if a `<source>` was appended to the media element. For some reason this
19258   * causes a `sourceset` if the the media element is ready and has no source.
19259   * This happens when:
19260   * - The page has just loaded and the media element does not have a source.
19261   * - The media element was emptied of all sources, then `load()` was called.
19262   *
19263   * It does this by patching the following functions/properties when they are supported:
19264   *
19265   * - `append()` - can be used to add a `<source>` element to the media element
19266   * - `appendChild()` - can be used to add a `<source>` element to the media element
19267   * - `insertAdjacentHTML()` -  can be used to add a `<source>` element to the media element
19268   * - `innerHTML` -  can be used to add a `<source>` element to the media element
19269   *
19270   * @param {Html5} tech
19271   *        The tech object that sourceset is being setup on.
19272   */
19273  var firstSourceWatch = function firstSourceWatch(tech) {
19274    var el = tech.el();
19275
19276    // make sure firstSourceWatch isn't setup twice.
19277    if (el.resetSourceWatch_) {
19278      return;
19279    }
19280
19281    var old = {};
19282    var innerDescriptor = getInnerHTMLDescriptor(tech);
19283    var appendWrapper = function appendWrapper(appendFn) {
19284      return function () {
19285        for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
19286          args[_key] = arguments[_key];
19287        }
19288
19289        var retval = appendFn.apply(el, args);
19290
19291        sourcesetLoad(tech);
19292
19293        return retval;
19294      };
19295    };
19296
19297    ['append', 'appendChild', 'insertAdjacentHTML'].forEach(function (k) {
19298      if (!el[k]) {
19299        return;
19300      }
19301
19302      // store the old function
19303      old[k] = el[k];
19304
19305      // call the old function with a sourceset if a source
19306      // was loaded
19307      el[k] = appendWrapper(old[k]);
19308    });
19309
19310    Object.defineProperty(el, 'innerHTML', mergeOptions(innerDescriptor, {
19311      set: appendWrapper(innerDescriptor.set)
19312    }));
19313
19314    el.resetSourceWatch_ = function () {
19315      el.resetSourceWatch_ = null;
19316      Object.keys(old).forEach(function (k) {
19317        el[k] = old[k];
19318      });
19319
19320      Object.defineProperty(el, 'innerHTML', innerDescriptor);
19321    };
19322
19323    // on the first sourceset, we need to revert our changes
19324    tech.one('sourceset', el.resetSourceWatch_);
19325  };
19326
19327  /**
19328   * our implementation of a `src` descriptor for browsers
19329   * that do not have one.
19330   */
19331  var srcDescriptorPolyfill = Object.defineProperty({}, 'src', {
19332    get: function get() {
19333      if (this.hasAttribute('src')) {
19334        return getAbsoluteURL(window_1.Element.prototype.getAttribute.call(this, 'src'));
19335      }
19336
19337      return '';
19338    },
19339    set: function set(v) {
19340      window_1.Element.prototype.setAttribute.call(this, 'src', v);
19341
19342      return v;
19343    }
19344  });
19345
19346  var getSrcDescriptor = function getSrcDescriptor(tech) {
19347    return getDescriptor([tech.el(), window_1.HTMLMediaElement.prototype, srcDescriptorPolyfill], 'src');
19348  };
19349
19350  /**
19351   * setup `sourceset` handling on the `Html5` tech. This function
19352   * patches the following element properties/functions:
19353   *
19354   * - `src` - to determine when `src` is set
19355   * - `setAttribute()` - to determine when `src` is set
19356   * - `load()` - this re-triggers the source selection algorithm, and can
19357   *              cause a sourceset.
19358   *
19359   * If there is no source when we are adding `sourceset` support or during a `load()`
19360   * we also patch the functions listed in `firstSourceWatch`.
19361   *
19362   * @param {Html5} tech
19363   *        The tech to patch
19364   */
19365  var setupSourceset = function setupSourceset(tech) {
19366    if (!tech.featuresSourceset) {
19367      return;
19368    }
19369
19370    var el = tech.el();
19371
19372    // make sure sourceset isn't setup twice.
19373    if (el.resetSourceset_) {
19374      return;
19375    }
19376
19377    var srcDescriptor = getSrcDescriptor(tech);
19378    var oldSetAttribute = el.setAttribute;
19379    var oldLoad = el.load;
19380
19381    Object.defineProperty(el, 'src', mergeOptions(srcDescriptor, {
19382      set: function set(v) {
19383        var retval = srcDescriptor.set.call(el, v);
19384
19385        // we use the getter here to get the actual value set on src
19386        tech.triggerSourceset(el.src);
19387
19388        return retval;
19389      }
19390    }));
19391
19392    el.setAttribute = function (n, v) {
19393      var retval = oldSetAttribute.call(el, n, v);
19394
19395      if (/src/i.test(n)) {
19396        tech.triggerSourceset(el.src);
19397      }
19398
19399      return retval;
19400    };
19401
19402    el.load = function () {
19403      var retval = oldLoad.call(el);
19404
19405      // if load was called, but there was no source to fire
19406      // sourceset on. We have to watch for a source append
19407      // as that can trigger a `sourceset` when the media element
19408      // has no source
19409      if (!sourcesetLoad(tech)) {
19410        tech.triggerSourceset('');
19411        firstSourceWatch(tech);
19412      }
19413
19414      return retval;
19415    };
19416
19417    if (el.currentSrc) {
19418      tech.triggerSourceset(el.currentSrc);
19419    } else if (!sourcesetLoad(tech)) {
19420      firstSourceWatch(tech);
19421    }
19422
19423    el.resetSourceset_ = function () {
19424      el.resetSourceset_ = null;
19425      el.load = oldLoad;
19426      el.setAttribute = oldSetAttribute;
19427      Object.defineProperty(el, 'src', srcDescriptor);
19428      if (el.resetSourceWatch_) {
19429        el.resetSourceWatch_();
19430      }
19431    };
19432  };
19433
19434  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.']);
19435
19436  /**
19437   * HTML5 Media Controller - Wrapper for HTML5 Media API
19438   *
19439   * @mixes Tech~SourceHandlerAdditions
19440   * @extends Tech
19441   */
19442
19443  var Html5 = function (_Tech) {
19444    inherits(Html5, _Tech);
19445
19446    /**
19447     * Create an instance of this Tech.
19448     *
19449     * @param {Object} [options]
19450     *        The key/value store of player options.
19451     *
19452     * @param {Component~ReadyCallback} ready
19453     *        Callback function to call when the `HTML5` Tech is ready.
19454     */
19455    function Html5(options, ready) {
19456      classCallCheck(this, Html5);
19457
19458      var _this = possibleConstructorReturn(this, _Tech.call(this, options, ready));
19459
19460      var source = options.source;
19461      var crossoriginTracks = false;
19462
19463      // Set the source if one is provided
19464      // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted)
19465      // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source
19466      // anyway so the error gets fired.
19467      if (source && (_this.el_.currentSrc !== source.src || options.tag && options.tag.initNetworkState_ === 3)) {
19468        _this.setSource(source);
19469      } else {
19470        _this.handleLateInit_(_this.el_);
19471      }
19472
19473      // setup sourceset after late sourceset/init
19474      if (options.enableSourceset) {
19475        _this.setupSourcesetHandling_();
19476      }
19477
19478      if (_this.el_.hasChildNodes()) {
19479
19480        var nodes = _this.el_.childNodes;
19481        var nodesLength = nodes.length;
19482        var removeNodes = [];
19483
19484        while (nodesLength--) {
19485          var node = nodes[nodesLength];
19486          var nodeName = node.nodeName.toLowerCase();
19487
19488          if (nodeName === 'track') {
19489            if (!_this.featuresNativeTextTracks) {
19490              // Empty video tag tracks so the built-in player doesn't use them also.
19491              // This may not be fast enough to stop HTML5 browsers from reading the tags
19492              // so we'll need to turn off any default tracks if we're manually doing
19493              // captions and subtitles. videoElement.textTracks
19494              removeNodes.push(node);
19495            } else {
19496              // store HTMLTrackElement and TextTrack to remote list
19497              _this.remoteTextTrackEls().addTrackElement_(node);
19498              _this.remoteTextTracks().addTrack(node.track);
19499              _this.textTracks().addTrack(node.track);
19500              if (!crossoriginTracks && !_this.el_.hasAttribute('crossorigin') && isCrossOrigin(node.src)) {
19501                crossoriginTracks = true;
19502              }
19503            }
19504          }
19505        }
19506
19507        for (var i = 0; i < removeNodes.length; i++) {
19508          _this.el_.removeChild(removeNodes[i]);
19509        }
19510      }
19511
19512      _this.proxyNativeTracks_();
19513      if (_this.featuresNativeTextTracks && crossoriginTracks) {
19514        log$1.warn(tsml(_templateObject$1));
19515      }
19516
19517      // prevent iOS Safari from disabling metadata text tracks during native playback
19518      _this.restoreMetadataTracksInIOSNativePlayer_();
19519
19520      // Determine if native controls should be used
19521      // Our goal should be to get the custom controls on mobile solid everywhere
19522      // so we can remove this all together. Right now this will block custom
19523      // controls on touch enabled laptops like the Chrome Pixel
19524      if ((TOUCH_ENABLED || IS_IPHONE || IS_NATIVE_ANDROID) && options.nativeControlsForTouch === true) {
19525        _this.setControls(true);
19526      }
19527
19528      // on iOS, we want to proxy `webkitbeginfullscreen` and `webkitendfullscreen`
19529      // into a `fullscreenchange` event
19530      _this.proxyWebkitFullscreen_();
19531
19532      _this.triggerReady();
19533      return _this;
19534    }
19535
19536    /**
19537     * Dispose of `HTML5` media element and remove all tracks.
19538     */
19539
19540
19541    Html5.prototype.dispose = function dispose() {
19542      if (this.el_ && this.el_.resetSourceset_) {
19543        this.el_.resetSourceset_();
19544      }
19545      Html5.disposeMediaElement(this.el_);
19546      this.options_ = null;
19547
19548      // tech will handle clearing of the emulated track list
19549      _Tech.prototype.dispose.call(this);
19550    };
19551
19552    /**
19553     * Modify the media element so that we can detect when
19554     * the source is changed. Fires `sourceset` just after the source has changed
19555     */
19556
19557
19558    Html5.prototype.setupSourcesetHandling_ = function setupSourcesetHandling_() {
19559      setupSourceset(this);
19560    };
19561
19562    /**
19563     * When a captions track is enabled in the iOS Safari native player, all other
19564     * tracks are disabled (including metadata tracks), which nulls all of their
19565     * associated cue points. This will restore metadata tracks to their pre-fullscreen
19566     * state in those cases so that cue points are not needlessly lost.
19567     *
19568     * @private
19569     */
19570
19571
19572    Html5.prototype.restoreMetadataTracksInIOSNativePlayer_ = function restoreMetadataTracksInIOSNativePlayer_() {
19573      var textTracks = this.textTracks();
19574      var metadataTracksPreFullscreenState = void 0;
19575
19576      // captures a snapshot of every metadata track's current state
19577      var takeMetadataTrackSnapshot = function takeMetadataTrackSnapshot() {
19578        metadataTracksPreFullscreenState = [];
19579
19580        for (var i = 0; i < textTracks.length; i++) {
19581          var track = textTracks[i];
19582
19583          if (track.kind === 'metadata') {
19584            metadataTracksPreFullscreenState.push({
19585              track: track,
19586              storedMode: track.mode
19587            });
19588          }
19589        }
19590      };
19591
19592      // snapshot each metadata track's initial state, and update the snapshot
19593      // each time there is a track 'change' event
19594      takeMetadataTrackSnapshot();
19595      textTracks.addEventListener('change', takeMetadataTrackSnapshot);
19596
19597      this.on('dispose', function () {
19598        return textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
19599      });
19600
19601      var restoreTrackMode = function restoreTrackMode() {
19602        for (var i = 0; i < metadataTracksPreFullscreenState.length; i++) {
19603          var storedTrack = metadataTracksPreFullscreenState[i];
19604
19605          if (storedTrack.track.mode === 'disabled' && storedTrack.track.mode !== storedTrack.storedMode) {
19606            storedTrack.track.mode = storedTrack.storedMode;
19607          }
19608        }
19609        // we only want this handler to be executed on the first 'change' event
19610        textTracks.removeEventListener('change', restoreTrackMode);
19611      };
19612
19613      // when we enter fullscreen playback, stop updating the snapshot and
19614      // restore all track modes to their pre-fullscreen state
19615      this.on('webkitbeginfullscreen', function () {
19616        textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
19617
19618        // remove the listener before adding it just in case it wasn't previously removed
19619        textTracks.removeEventListener('change', restoreTrackMode);
19620        textTracks.addEventListener('change', restoreTrackMode);
19621      });
19622
19623      // start updating the snapshot again after leaving fullscreen
19624      this.on('webkitendfullscreen', function () {
19625        // remove the listener before adding it just in case it wasn't previously removed
19626        textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
19627        textTracks.addEventListener('change', takeMetadataTrackSnapshot);
19628
19629        // remove the restoreTrackMode handler in case it wasn't triggered during fullscreen playback
19630        textTracks.removeEventListener('change', restoreTrackMode);
19631      });
19632    };
19633
19634    /**
19635     * Attempt to force override of tracks for the given type
19636     *
19637     * @param {String} type - Track type to override, possible values include 'Audio',
19638     * 'Video', and 'Text'.
19639     * @param {Boolean} override - If set to true native audio/video will be overridden,
19640     * otherwise native audio/video will potentially be used.
19641     * @private
19642     */
19643
19644
19645    Html5.prototype.overrideNative_ = function overrideNative_(type, override) {
19646      var _this2 = this;
19647
19648      // If there is no behavioral change don't add/remove listeners
19649      if (override !== this['featuresNative' + type + 'Tracks']) {
19650        return;
19651      }
19652
19653      var lowerCaseType = type.toLowerCase();
19654
19655      if (this[lowerCaseType + 'TracksListeners_']) {
19656        Object.keys(this[lowerCaseType + 'TracksListeners_']).forEach(function (eventName) {
19657          var elTracks = _this2.el()[lowerCaseType + 'Tracks'];
19658
19659          elTracks.removeEventListener(eventName, _this2[lowerCaseType + 'TracksListeners_'][eventName]);
19660        });
19661      }
19662
19663      this['featuresNative' + type + 'Tracks'] = !override;
19664      this[lowerCaseType + 'TracksListeners_'] = null;
19665
19666      this.proxyNativeTracksForType_(lowerCaseType);
19667    };
19668
19669    /**
19670     * Attempt to force override of native audio tracks.
19671     *
19672     * @param {Boolean} override - If set to true native audio will be overridden,
19673     * otherwise native audio will potentially be used.
19674     */
19675
19676
19677    Html5.prototype.overrideNativeAudioTracks = function overrideNativeAudioTracks(override) {
19678      this.overrideNative_('Audio', override);
19679    };
19680
19681    /**
19682     * Attempt to force override of native video tracks.
19683     *
19684     * @param {Boolean} override - If set to true native video will be overridden,
19685     * otherwise native video will potentially be used.
19686     */
19687
19688
19689    Html5.prototype.overrideNativeVideoTracks = function overrideNativeVideoTracks(override) {
19690      this.overrideNative_('Video', override);
19691    };
19692
19693    /**
19694      * Proxy native track list events for the given type to our track
19695      * lists if the browser we are playing in supports that type of track list.
19696      *
19697      * @param {string} name - Track type; values include 'audio', 'video', and 'text'
19698      * @private
19699      */
19700
19701
19702    Html5.prototype.proxyNativeTracksForType_ = function proxyNativeTracksForType_(name) {
19703      var _this3 = this;
19704
19705      var props = NORMAL[name];
19706      var elTracks = this.el()[props.getterName];
19707      var techTracks = this[props.getterName]();
19708
19709      if (!this['featuresNative' + props.capitalName + 'Tracks'] || !elTracks || !elTracks.addEventListener) {
19710        return;
19711      }
19712      var listeners = {
19713        change: function change(e) {
19714          techTracks.trigger({
19715            type: 'change',
19716            target: techTracks,
19717            currentTarget: techTracks,
19718            srcElement: techTracks
19719          });
19720        },
19721        addtrack: function addtrack(e) {
19722          techTracks.addTrack(e.track);
19723        },
19724        removetrack: function removetrack(e) {
19725          techTracks.removeTrack(e.track);
19726        }
19727      };
19728      var removeOldTracks = function removeOldTracks() {
19729        var removeTracks = [];
19730
19731        for (var i = 0; i < techTracks.length; i++) {
19732          var found = false;
19733
19734          for (var j = 0; j < elTracks.length; j++) {
19735            if (elTracks[j] === techTracks[i]) {
19736              found = true;
19737              break;
19738            }
19739          }
19740
19741          if (!found) {
19742            removeTracks.push(techTracks[i]);
19743          }
19744        }
19745
19746        while (removeTracks.length) {
19747          techTracks.removeTrack(removeTracks.shift());
19748        }
19749      };
19750
19751      this[props.getterName + 'Listeners_'] = listeners;
19752
19753      Object.keys(listeners).forEach(function (eventName) {
19754        var listener = listeners[eventName];
19755
19756        elTracks.addEventListener(eventName, listener);
19757        _this3.on('dispose', function (e) {
19758          return elTracks.removeEventListener(eventName, listener);
19759        });
19760      });
19761
19762      // Remove (native) tracks that are not used anymore
19763      this.on('loadstart', removeOldTracks);
19764      this.on('dispose', function (e) {
19765        return _this3.off('loadstart', removeOldTracks);
19766      });
19767    };
19768
19769    /**
19770     * Proxy all native track list events to our track lists if the browser we are playing
19771     * in supports that type of track list.
19772     *
19773     * @private
19774     */
19775
19776
19777    Html5.prototype.proxyNativeTracks_ = function proxyNativeTracks_() {
19778      var _this4 = this;
19779
19780      NORMAL.names.forEach(function (name) {
19781        _this4.proxyNativeTracksForType_(name);
19782      });
19783    };
19784
19785    /**
19786     * Create the `Html5` Tech's DOM element.
19787     *
19788     * @return {Element}
19789     *         The element that gets created.
19790     */
19791
19792
19793    Html5.prototype.createEl = function createEl$$1() {
19794      var el = this.options_.tag;
19795
19796      // Check if this browser supports moving the element into the box.
19797      // On the iPhone video will break if you move the element,
19798      // So we have to create a brand new element.
19799      // If we ingested the player div, we do not need to move the media element.
19800      if (!el || !(this.options_.playerElIngest || this.movingMediaElementInDOM)) {
19801
19802        // If the original tag is still there, clone and remove it.
19803        if (el) {
19804          var clone = el.cloneNode(true);
19805
19806          if (el.parentNode) {
19807            el.parentNode.insertBefore(clone, el);
19808          }
19809          Html5.disposeMediaElement(el);
19810          el = clone;
19811        } else {
19812          el = document_1.createElement('video');
19813
19814          // determine if native controls should be used
19815          var tagAttributes = this.options_.tag && getAttributes(this.options_.tag);
19816          var attributes = mergeOptions({}, tagAttributes);
19817
19818          if (!TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) {
19819            delete attributes.controls;
19820          }
19821
19822          setAttributes(el, assign(attributes, {
19823            id: this.options_.techId,
19824            class: 'vjs-tech'
19825          }));
19826        }
19827
19828        el.playerId = this.options_.playerId;
19829      }
19830
19831      if (typeof this.options_.preload !== 'undefined') {
19832        setAttribute(el, 'preload', this.options_.preload);
19833      }
19834
19835      // Update specific tag settings, in case they were overridden
19836      // `autoplay` has to be *last* so that `muted` and `playsinline` are present
19837      // when iOS/Safari or other browsers attempt to autoplay.
19838      var settingsAttrs = ['loop', 'muted', 'playsinline', 'autoplay'];
19839
19840      for (var i = 0; i < settingsAttrs.length; i++) {
19841        var attr = settingsAttrs[i];
19842        var value = this.options_[attr];
19843
19844        if (typeof value !== 'undefined') {
19845          if (value) {
19846            setAttribute(el, attr, attr);
19847          } else {
19848            removeAttribute(el, attr);
19849          }
19850          el[attr] = value;
19851        }
19852      }
19853
19854      return el;
19855    };
19856
19857    /**
19858     * This will be triggered if the loadstart event has already fired, before videojs was
19859     * ready. Two known examples of when this can happen are:
19860     * 1. If we're loading the playback object after it has started loading
19861     * 2. The media is already playing the (often with autoplay on) then
19862     *
19863     * This function will fire another loadstart so that videojs can catchup.
19864     *
19865     * @fires Tech#loadstart
19866     *
19867     * @return {undefined}
19868     *         returns nothing.
19869     */
19870
19871
19872    Html5.prototype.handleLateInit_ = function handleLateInit_(el) {
19873      if (el.networkState === 0 || el.networkState === 3) {
19874        // The video element hasn't started loading the source yet
19875        // or didn't find a source
19876        return;
19877      }
19878
19879      if (el.readyState === 0) {
19880        // NetworkState is set synchronously BUT loadstart is fired at the
19881        // end of the current stack, usually before setInterval(fn, 0).
19882        // So at this point we know loadstart may have already fired or is
19883        // about to fire, and either way the player hasn't seen it yet.
19884        // We don't want to fire loadstart prematurely here and cause a
19885        // double loadstart so we'll wait and see if it happens between now
19886        // and the next loop, and fire it if not.
19887        // HOWEVER, we also want to make sure it fires before loadedmetadata
19888        // which could also happen between now and the next loop, so we'll
19889        // watch for that also.
19890        var loadstartFired = false;
19891        var setLoadstartFired = function setLoadstartFired() {
19892          loadstartFired = true;
19893        };
19894
19895        this.on('loadstart', setLoadstartFired);
19896
19897        var triggerLoadstart = function triggerLoadstart() {
19898          // We did miss the original loadstart. Make sure the player
19899          // sees loadstart before loadedmetadata
19900          if (!loadstartFired) {
19901            this.trigger('loadstart');
19902          }
19903        };
19904
19905        this.on('loadedmetadata', triggerLoadstart);
19906
19907        this.ready(function () {
19908          this.off('loadstart', setLoadstartFired);
19909          this.off('loadedmetadata', triggerLoadstart);
19910
19911          if (!loadstartFired) {
19912            // We did miss the original native loadstart. Fire it now.
19913            this.trigger('loadstart');
19914          }
19915        });
19916
19917        return;
19918      }
19919
19920      // From here on we know that loadstart already fired and we missed it.
19921      // The other readyState events aren't as much of a problem if we double
19922      // them, so not going to go to as much trouble as loadstart to prevent
19923      // that unless we find reason to.
19924      var eventsToTrigger = ['loadstart'];
19925
19926      // loadedmetadata: newly equal to HAVE_METADATA (1) or greater
19927      eventsToTrigger.push('loadedmetadata');
19928
19929      // loadeddata: newly increased to HAVE_CURRENT_DATA (2) or greater
19930      if (el.readyState >= 2) {
19931        eventsToTrigger.push('loadeddata');
19932      }
19933
19934      // canplay: newly increased to HAVE_FUTURE_DATA (3) or greater
19935      if (el.readyState >= 3) {
19936        eventsToTrigger.push('canplay');
19937      }
19938
19939      // canplaythrough: newly equal to HAVE_ENOUGH_DATA (4)
19940      if (el.readyState >= 4) {
19941        eventsToTrigger.push('canplaythrough');
19942      }
19943
19944      // We still need to give the player time to add event listeners
19945      this.ready(function () {
19946        eventsToTrigger.forEach(function (type) {
19947          this.trigger(type);
19948        }, this);
19949      });
19950    };
19951
19952    /**
19953     * Set current time for the `HTML5` tech.
19954     *
19955     * @param {number} seconds
19956     *        Set the current time of the media to this.
19957     */
19958
19959
19960    Html5.prototype.setCurrentTime = function setCurrentTime(seconds) {
19961      try {
19962        this.el_.currentTime = seconds;
19963      } catch (e) {
19964        log$1(e, 'Video is not ready. (Video.js)');
19965        // this.warning(VideoJS.warnings.videoNotReady);
19966      }
19967    };
19968
19969    /**
19970     * Get the current duration of the HTML5 media element.
19971     *
19972     * @return {number}
19973     *         The duration of the media or 0 if there is no duration.
19974     */
19975
19976
19977    Html5.prototype.duration = function duration() {
19978      var _this5 = this;
19979
19980      // Android Chrome will report duration as Infinity for VOD HLS until after
19981      // playback has started, which triggers the live display erroneously.
19982      // Return NaN if playback has not started and trigger a durationupdate once
19983      // the duration can be reliably known.
19984      if (this.el_.duration === Infinity && IS_ANDROID && IS_CHROME && this.el_.currentTime === 0) {
19985        // Wait for the first `timeupdate` with currentTime > 0 - there may be
19986        // several with 0
19987        var checkProgress = function checkProgress() {
19988          if (_this5.el_.currentTime > 0) {
19989            // Trigger durationchange for genuinely live video
19990            if (_this5.el_.duration === Infinity) {
19991              _this5.trigger('durationchange');
19992            }
19993            _this5.off('timeupdate', checkProgress);
19994          }
19995        };
19996
19997        this.on('timeupdate', checkProgress);
19998        return NaN;
19999      }
20000      return this.el_.duration || NaN;
20001    };
20002
20003    /**
20004     * Get the current width of the HTML5 media element.
20005     *
20006     * @return {number}
20007     *         The width of the HTML5 media element.
20008     */
20009
20010
20011    Html5.prototype.width = function width() {
20012      return this.el_.offsetWidth;
20013    };
20014
20015    /**
20016     * Get the current height of the HTML5 media element.
20017     *
20018     * @return {number}
20019     *         The height of the HTML5 media element.
20020     */
20021
20022
20023    Html5.prototype.height = function height() {
20024      return this.el_.offsetHeight;
20025    };
20026
20027    /**
20028     * Proxy iOS `webkitbeginfullscreen` and `webkitendfullscreen` into
20029     * `fullscreenchange` event.
20030     *
20031     * @private
20032     * @fires fullscreenchange
20033     * @listens webkitendfullscreen
20034     * @listens webkitbeginfullscreen
20035     * @listens webkitbeginfullscreen
20036     */
20037
20038
20039    Html5.prototype.proxyWebkitFullscreen_ = function proxyWebkitFullscreen_() {
20040      var _this6 = this;
20041
20042      if (!('webkitDisplayingFullscreen' in this.el_)) {
20043        return;
20044      }
20045
20046      var endFn = function endFn() {
20047        this.trigger('fullscreenchange', { isFullscreen: false });
20048      };
20049
20050      var beginFn = function beginFn() {
20051        if ('webkitPresentationMode' in this.el_ && this.el_.webkitPresentationMode !== 'picture-in-picture') {
20052          this.one('webkitendfullscreen', endFn);
20053
20054          this.trigger('fullscreenchange', { isFullscreen: true });
20055        }
20056      };
20057
20058      this.on('webkitbeginfullscreen', beginFn);
20059      this.on('dispose', function () {
20060        _this6.off('webkitbeginfullscreen', beginFn);
20061        _this6.off('webkitendfullscreen', endFn);
20062      });
20063    };
20064
20065    /**
20066     * Check if fullscreen is supported on the current playback device.
20067     *
20068     * @return {boolean}
20069     *         - True if fullscreen is supported.
20070     *         - False if fullscreen is not supported.
20071     */
20072
20073
20074    Html5.prototype.supportsFullScreen = function supportsFullScreen() {
20075      if (typeof this.el_.webkitEnterFullScreen === 'function') {
20076        var userAgent = window_1.navigator && window_1.navigator.userAgent || '';
20077
20078        // Seems to be broken in Chromium/Chrome && Safari in Leopard
20079        if (/Android/.test(userAgent) || !/Chrome|Mac OS X 10.5/.test(userAgent)) {
20080          return true;
20081        }
20082      }
20083      return false;
20084    };
20085
20086    /**
20087     * Request that the `HTML5` Tech enter fullscreen.
20088     */
20089
20090
20091    Html5.prototype.enterFullScreen = function enterFullScreen() {
20092      var video = this.el_;
20093
20094      if (video.paused && video.networkState <= video.HAVE_METADATA) {
20095        // attempt to prime the video element for programmatic access
20096        // this isn't necessary on the desktop but shouldn't hurt
20097        this.el_.play();
20098
20099        // playing and pausing synchronously during the transition to fullscreen
20100        // can get iOS ~6.1 devices into a play/pause loop
20101        this.setTimeout(function () {
20102          video.pause();
20103          video.webkitEnterFullScreen();
20104        }, 0);
20105      } else {
20106        video.webkitEnterFullScreen();
20107      }
20108    };
20109
20110    /**
20111     * Request that the `HTML5` Tech exit fullscreen.
20112     */
20113
20114
20115    Html5.prototype.exitFullScreen = function exitFullScreen() {
20116      this.el_.webkitExitFullScreen();
20117    };
20118
20119    /**
20120     * A getter/setter for the `Html5` Tech's source object.
20121     * > Note: Please use {@link Html5#setSource}
20122     *
20123     * @param {Tech~SourceObject} [src]
20124     *        The source object you want to set on the `HTML5` techs element.
20125     *
20126     * @return {Tech~SourceObject|undefined}
20127     *         - The current source object when a source is not passed in.
20128     *         - undefined when setting
20129     *
20130     * @deprecated Since version 5.
20131     */
20132
20133
20134    Html5.prototype.src = function src(_src) {
20135      if (_src === undefined) {
20136        return this.el_.src;
20137      }
20138
20139      // Setting src through `src` instead of `setSrc` will be deprecated
20140      this.setSrc(_src);
20141    };
20142
20143    /**
20144     * Reset the tech by removing all sources and then calling
20145     * {@link Html5.resetMediaElement}.
20146     */
20147
20148
20149    Html5.prototype.reset = function reset() {
20150      Html5.resetMediaElement(this.el_);
20151    };
20152
20153    /**
20154     * Get the current source on the `HTML5` Tech. Falls back to returning the source from
20155     * the HTML5 media element.
20156     *
20157     * @return {Tech~SourceObject}
20158     *         The current source object from the HTML5 tech. With a fallback to the
20159     *         elements source.
20160     */
20161
20162
20163    Html5.prototype.currentSrc = function currentSrc() {
20164      if (this.currentSource_) {
20165        return this.currentSource_.src;
20166      }
20167      return this.el_.currentSrc;
20168    };
20169
20170    /**
20171     * Set controls attribute for the HTML5 media Element.
20172     *
20173     * @param {string} val
20174     *        Value to set the controls attribute to
20175     */
20176
20177
20178    Html5.prototype.setControls = function setControls(val) {
20179      this.el_.controls = !!val;
20180    };
20181
20182    /**
20183     * Create and returns a remote {@link TextTrack} object.
20184     *
20185     * @param {string} kind
20186     *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
20187     *
20188     * @param {string} [label]
20189     *        Label to identify the text track
20190     *
20191     * @param {string} [language]
20192     *        Two letter language abbreviation
20193     *
20194     * @return {TextTrack}
20195     *         The TextTrack that gets created.
20196     */
20197
20198
20199    Html5.prototype.addTextTrack = function addTextTrack(kind, label, language) {
20200      if (!this.featuresNativeTextTracks) {
20201        return _Tech.prototype.addTextTrack.call(this, kind, label, language);
20202      }
20203
20204      return this.el_.addTextTrack(kind, label, language);
20205    };
20206
20207    /**
20208     * Creates either native TextTrack or an emulated TextTrack depending
20209     * on the value of `featuresNativeTextTracks`
20210     *
20211     * @param {Object} options
20212     *        The object should contain the options to initialize the TextTrack with.
20213     *
20214     * @param {string} [options.kind]
20215     *        `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
20216     *
20217     * @param {string} [options.label]
20218     *        Label to identify the text track
20219     *
20220     * @param {string} [options.language]
20221     *        Two letter language abbreviation.
20222     *
20223     * @param {boolean} [options.default]
20224     *        Default this track to on.
20225     *
20226     * @param {string} [options.id]
20227     *        The internal id to assign this track.
20228     *
20229     * @param {string} [options.src]
20230     *        A source url for the track.
20231     *
20232     * @return {HTMLTrackElement}
20233     *         The track element that gets created.
20234     */
20235
20236
20237    Html5.prototype.createRemoteTextTrack = function createRemoteTextTrack(options) {
20238      if (!this.featuresNativeTextTracks) {
20239        return _Tech.prototype.createRemoteTextTrack.call(this, options);
20240      }
20241      var htmlTrackElement = document_1.createElement('track');
20242
20243      if (options.kind) {
20244        htmlTrackElement.kind = options.kind;
20245      }
20246      if (options.label) {
20247        htmlTrackElement.label = options.label;
20248      }
20249      if (options.language || options.srclang) {
20250        htmlTrackElement.srclang = options.language || options.srclang;
20251      }
20252      if (options.default) {
20253        htmlTrackElement.default = options.default;
20254      }
20255      if (options.id) {
20256        htmlTrackElement.id = options.id;
20257      }
20258      if (options.src) {
20259        htmlTrackElement.src = options.src;
20260      }
20261
20262      return htmlTrackElement;
20263    };
20264
20265    /**
20266     * Creates a remote text track object and returns an html track element.
20267     *
20268     * @param {Object} options The object should contain values for
20269     * kind, language, label, and src (location of the WebVTT file)
20270     * @param {Boolean} [manualCleanup=true] if set to false, the TextTrack will be
20271     * automatically removed from the video element whenever the source changes
20272     * @return {HTMLTrackElement} An Html Track Element.
20273     * This can be an emulated {@link HTMLTrackElement} or a native one.
20274     * @deprecated The default value of the "manualCleanup" parameter will default
20275     * to "false" in upcoming versions of Video.js
20276     */
20277
20278
20279    Html5.prototype.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
20280      var htmlTrackElement = _Tech.prototype.addRemoteTextTrack.call(this, options, manualCleanup);
20281
20282      if (this.featuresNativeTextTracks) {
20283        this.el().appendChild(htmlTrackElement);
20284      }
20285
20286      return htmlTrackElement;
20287    };
20288
20289    /**
20290     * Remove remote `TextTrack` from `TextTrackList` object
20291     *
20292     * @param {TextTrack} track
20293     *        `TextTrack` object to remove
20294     */
20295
20296
20297    Html5.prototype.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
20298      _Tech.prototype.removeRemoteTextTrack.call(this, track);
20299
20300      if (this.featuresNativeTextTracks) {
20301        var tracks = this.$$('track');
20302
20303        var i = tracks.length;
20304
20305        while (i--) {
20306          if (track === tracks[i] || track === tracks[i].track) {
20307            this.el().removeChild(tracks[i]);
20308          }
20309        }
20310      }
20311    };
20312
20313    /**
20314     * Gets available media playback quality metrics as specified by the W3C's Media
20315     * Playback Quality API.
20316     *
20317     * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
20318     *
20319     * @return {Object}
20320     *         An object with supported media playback quality metrics
20321     */
20322
20323
20324    Html5.prototype.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
20325      if (typeof this.el().getVideoPlaybackQuality === 'function') {
20326        return this.el().getVideoPlaybackQuality();
20327      }
20328
20329      var videoPlaybackQuality = {};
20330
20331      if (typeof this.el().webkitDroppedFrameCount !== 'undefined' && typeof this.el().webkitDecodedFrameCount !== 'undefined') {
20332        videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount;
20333        videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount;
20334      }
20335
20336      if (window_1.performance && typeof window_1.performance.now === 'function') {
20337        videoPlaybackQuality.creationTime = window_1.performance.now();
20338      } else if (window_1.performance && window_1.performance.timing && typeof window_1.performance.timing.navigationStart === 'number') {
20339        videoPlaybackQuality.creationTime = window_1.Date.now() - window_1.performance.timing.navigationStart;
20340      }
20341
20342      return videoPlaybackQuality;
20343    };
20344
20345    return Html5;
20346  }(Tech);
20347
20348  /* HTML5 Support Testing ---------------------------------------------------- */
20349
20350  if (isReal()) {
20351
20352    /**
20353     * Element for testing browser HTML5 media capabilities
20354     *
20355     * @type {Element}
20356     * @constant
20357     * @private
20358     */
20359    Html5.TEST_VID = document_1.createElement('video');
20360    var track = document_1.createElement('track');
20361
20362    track.kind = 'captions';
20363    track.srclang = 'en';
20364    track.label = 'English';
20365    Html5.TEST_VID.appendChild(track);
20366  }
20367
20368  /**
20369   * Check if HTML5 media is supported by this browser/device.
20370   *
20371   * @return {boolean}
20372   *         - True if HTML5 media is supported.
20373   *         - False if HTML5 media is not supported.
20374   */
20375  Html5.isSupported = function () {
20376    // IE with no Media Player is a LIAR! (#984)
20377    try {
20378      Html5.TEST_VID.volume = 0.5;
20379    } catch (e) {
20380      return false;
20381    }
20382
20383    return !!(Html5.TEST_VID && Html5.TEST_VID.canPlayType);
20384  };
20385
20386  /**
20387   * Check if the tech can support the given type
20388   *
20389   * @param {string} type
20390   *        The mimetype to check
20391   * @return {string} 'probably', 'maybe', or '' (empty string)
20392   */
20393  Html5.canPlayType = function (type) {
20394    return Html5.TEST_VID.canPlayType(type);
20395  };
20396
20397  /**
20398   * Check if the tech can support the given source
20399   * @param {Object} srcObj
20400   *        The source object
20401   * @param {Object} options
20402   *        The options passed to the tech
20403   * @return {string} 'probably', 'maybe', or '' (empty string)
20404   */
20405  Html5.canPlaySource = function (srcObj, options) {
20406    return Html5.canPlayType(srcObj.type);
20407  };
20408
20409  /**
20410   * Check if the volume can be changed in this browser/device.
20411   * Volume cannot be changed in a lot of mobile devices.
20412   * Specifically, it can't be changed from 1 on iOS.
20413   *
20414   * @return {boolean}
20415   *         - True if volume can be controlled
20416   *         - False otherwise
20417   */
20418  Html5.canControlVolume = function () {
20419    // IE will error if Windows Media Player not installed #3315
20420    try {
20421      var volume = Html5.TEST_VID.volume;
20422
20423      Html5.TEST_VID.volume = volume / 2 + 0.1;
20424      return volume !== Html5.TEST_VID.volume;
20425    } catch (e) {
20426      return false;
20427    }
20428  };
20429
20430  /**
20431   * Check if the volume can be muted in this browser/device.
20432   * Some devices, e.g. iOS, don't allow changing volume
20433   * but permits muting/unmuting.
20434   *
20435   * @return {bolean}
20436   *      - True if volume can be muted
20437   *      - False otherwise
20438   */
20439  Html5.canMuteVolume = function () {
20440    try {
20441      var muted = Html5.TEST_VID.muted;
20442
20443      // in some versions of iOS muted property doesn't always
20444      // work, so we want to set both property and attribute
20445      Html5.TEST_VID.muted = !muted;
20446      if (Html5.TEST_VID.muted) {
20447        setAttribute(Html5.TEST_VID, 'muted', 'muted');
20448      } else {
20449        removeAttribute(Html5.TEST_VID, 'muted', 'muted');
20450      }
20451      return muted !== Html5.TEST_VID.muted;
20452    } catch (e) {
20453      return false;
20454    }
20455  };
20456
20457  /**
20458   * Check if the playback rate can be changed in this browser/device.
20459   *
20460   * @return {boolean}
20461   *         - True if playback rate can be controlled
20462   *         - False otherwise
20463   */
20464  Html5.canControlPlaybackRate = function () {
20465    // Playback rate API is implemented in Android Chrome, but doesn't do anything
20466    // https://github.com/videojs/video.js/issues/3180
20467    if (IS_ANDROID && IS_CHROME && CHROME_VERSION < 58) {
20468      return false;
20469    }
20470    // IE will error if Windows Media Player not installed #3315
20471    try {
20472      var playbackRate = Html5.TEST_VID.playbackRate;
20473
20474      Html5.TEST_VID.playbackRate = playbackRate / 2 + 0.1;
20475      return playbackRate !== Html5.TEST_VID.playbackRate;
20476    } catch (e) {
20477      return false;
20478    }
20479  };
20480
20481  /**
20482   * Check if we can override a video/audio elements attributes, with
20483   * Object.defineProperty.
20484   *
20485   * @return {boolean}
20486   *         - True if builtin attributes can be overridden
20487   *         - False otherwise
20488   */
20489  Html5.canOverrideAttributes = function () {
20490    // if we cannot overwrite the src/innerHTML property, there is no support
20491    // iOS 7 safari for instance cannot do this.
20492    try {
20493      var noop = function noop() {};
20494
20495      Object.defineProperty(document_1.createElement('video'), 'src', { get: noop, set: noop });
20496      Object.defineProperty(document_1.createElement('audio'), 'src', { get: noop, set: noop });
20497      Object.defineProperty(document_1.createElement('video'), 'innerHTML', { get: noop, set: noop });
20498      Object.defineProperty(document_1.createElement('audio'), 'innerHTML', { get: noop, set: noop });
20499    } catch (e) {
20500      return false;
20501    }
20502
20503    return true;
20504  };
20505
20506  /**
20507   * Check to see if native `TextTrack`s are supported by this browser/device.
20508   *
20509   * @return {boolean}
20510   *         - True if native `TextTrack`s are supported.
20511   *         - False otherwise
20512   */
20513  Html5.supportsNativeTextTracks = function () {
20514    return IS_ANY_SAFARI || IS_IOS && IS_CHROME;
20515  };
20516
20517  /**
20518   * Check to see if native `VideoTrack`s are supported by this browser/device
20519   *
20520   * @return {boolean}
20521   *        - True if native `VideoTrack`s are supported.
20522   *        - False otherwise
20523   */
20524  Html5.supportsNativeVideoTracks = function () {
20525    return !!(Html5.TEST_VID && Html5.TEST_VID.videoTracks);
20526  };
20527
20528  /**
20529   * Check to see if native `AudioTrack`s are supported by this browser/device
20530   *
20531   * @return {boolean}
20532   *        - True if native `AudioTrack`s are supported.
20533   *        - False otherwise
20534   */
20535  Html5.supportsNativeAudioTracks = function () {
20536    return !!(Html5.TEST_VID && Html5.TEST_VID.audioTracks);
20537  };
20538
20539  /**
20540   * An array of events available on the Html5 tech.
20541   *
20542   * @private
20543   * @type {Array}
20544   */
20545  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'];
20546
20547  /**
20548   * Boolean indicating whether the `Tech` supports volume control.
20549   *
20550   * @type {boolean}
20551   * @default {@link Html5.canControlVolume}
20552   */
20553  Html5.prototype.featuresVolumeControl = Html5.canControlVolume();
20554
20555  /**
20556   * Boolean indicating whether the `Tech` supports muting volume.
20557   *
20558   * @type {bolean}
20559   * @default {@link Html5.canMuteVolume}
20560   */
20561  Html5.prototype.featuresMuteControl = Html5.canMuteVolume();
20562
20563  /**
20564   * Boolean indicating whether the `Tech` supports changing the speed at which the media
20565   * plays. Examples:
20566   *   - Set player to play 2x (twice) as fast
20567   *   - Set player to play 0.5x (half) as fast
20568   *
20569   * @type {boolean}
20570   * @default {@link Html5.canControlPlaybackRate}
20571   */
20572  Html5.prototype.featuresPlaybackRate = Html5.canControlPlaybackRate();
20573
20574  /**
20575   * Boolean indicating whether the `Tech` supports the `sourceset` event.
20576   *
20577   * @type {boolean}
20578   * @default
20579   */
20580  Html5.prototype.featuresSourceset = Html5.canOverrideAttributes();
20581
20582  /**
20583   * Boolean indicating whether the `HTML5` tech currently supports the media element
20584   * moving in the DOM. iOS breaks if you move the media element, so this is set this to
20585   * false there. Everywhere else this should be true.
20586   *
20587   * @type {boolean}
20588   * @default
20589   */
20590  Html5.prototype.movingMediaElementInDOM = !IS_IOS;
20591
20592  // TODO: Previous comment: No longer appears to be used. Can probably be removed.
20593  //       Is this true?
20594  /**
20595   * Boolean indicating whether the `HTML5` tech currently supports automatic media resize
20596   * when going into fullscreen.
20597   *
20598   * @type {boolean}
20599   * @default
20600   */
20601  Html5.prototype.featuresFullscreenResize = true;
20602
20603  /**
20604   * Boolean indicating whether the `HTML5` tech currently supports the progress event.
20605   * If this is false, manual `progress` events will be triggered instead.
20606   *
20607   * @type {boolean}
20608   * @default
20609   */
20610  Html5.prototype.featuresProgressEvents = true;
20611
20612  /**
20613   * Boolean indicating whether the `HTML5` tech currently supports the timeupdate event.
20614   * If this is false, manual `timeupdate` events will be triggered instead.
20615   *
20616   * @default
20617   */
20618  Html5.prototype.featuresTimeupdateEvents = true;
20619
20620  /**
20621   * Boolean indicating whether the `HTML5` tech currently supports native `TextTrack`s.
20622   *
20623   * @type {boolean}
20624   * @default {@link Html5.supportsNativeTextTracks}
20625   */
20626  Html5.prototype.featuresNativeTextTracks = Html5.supportsNativeTextTracks();
20627
20628  /**
20629   * Boolean indicating whether the `HTML5` tech currently supports native `VideoTrack`s.
20630   *
20631   * @type {boolean}
20632   * @default {@link Html5.supportsNativeVideoTracks}
20633   */
20634  Html5.prototype.featuresNativeVideoTracks = Html5.supportsNativeVideoTracks();
20635
20636  /**
20637   * Boolean indicating whether the `HTML5` tech currently supports native `AudioTrack`s.
20638   *
20639   * @type {boolean}
20640   * @default {@link Html5.supportsNativeAudioTracks}
20641   */
20642  Html5.prototype.featuresNativeAudioTracks = Html5.supportsNativeAudioTracks();
20643
20644  // HTML5 Feature detection and Device Fixes --------------------------------- //
vendor: 14,640 bytes, lines 20645-21059
20645  var canPlayType = Html5.TEST_VID && Html5.TEST_VID.constructor.prototype.canPlayType;
20646  var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i;
20647
20648  Html5.patchCanPlayType = function () {
20649
20650    // Android 4.0 and above can play HLS to some extent but it reports being unable to do so
20651    // Firefox and Chrome report correctly
20652    if (ANDROID_VERSION >= 4.0 && !IS_FIREFOX && !IS_CHROME) {
20653      Html5.TEST_VID.constructor.prototype.canPlayType = function (type) {
20654        if (type && mpegurlRE.test(type)) {
20655          return 'maybe';
20656        }
20657        return canPlayType.call(this, type);
20658      };
20659    }
20660  };
20661
20662  Html5.unpatchCanPlayType = function () {
20663    var r = Html5.TEST_VID.constructor.prototype.canPlayType;
20664
20665    Html5.TEST_VID.constructor.prototype.canPlayType = canPlayType;
20666    return r;
20667  };
20668
20669  // by default, patch the media element
20670  Html5.patchCanPlayType();
20671
20672  Html5.disposeMediaElement = function (el) {
20673    if (!el) {
20674      return;
20675    }
20676
20677    if (el.parentNode) {
20678      el.parentNode.removeChild(el);
20679    }
20680
20681    // remove any child track or source nodes to prevent their loading
20682    while (el.hasChildNodes()) {
20683      el.removeChild(el.firstChild);
20684    }
20685
20686    // remove any src reference. not setting `src=''` because that causes a warning
20687    // in firefox
20688    el.removeAttribute('src');
20689
20690    // force the media element to update its loading state by calling load()
20691    // however IE on Windows 7N has a bug that throws an error so need a try/catch (#793)
20692    if (typeof el.load === 'function') {
20693      // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
20694      (function () {
20695        try {
20696          el.load();
20697        } catch (e) {
20698          // not supported
20699        }
20700      })();
20701    }
20702  };
20703
20704  Html5.resetMediaElement = function (el) {
20705    if (!el) {
20706      return;
20707    }
20708
20709    var sources = el.querySelectorAll('source');
20710    var i = sources.length;
20711
20712    while (i--) {
20713      el.removeChild(sources[i]);
20714    }
20715
20716    // remove any src reference.
20717    // not setting `src=''` because that throws an error
20718    el.removeAttribute('src');
20719
20720    if (typeof el.load === 'function') {
20721      // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
20722      (function () {
20723        try {
20724          el.load();
20725        } catch (e) {
20726          // satisfy linter
20727        }
20728      })();
20729    }
20730  };
20731
20732  /* Native HTML5 element property wrapping ----------------------------------- */
20733  // Wrap native boolean attributes with getters that check both property and attribute
20734  // The list is as followed:
20735  // muted, defaultMuted, autoplay, controls, loop, playsinline
20736  [
20737  /**
20738   * Get the value of `muted` from the media element. `muted` indicates
20739   * that the volume for the media should be set to silent. This does not actually change
20740   * the `volume` attribute.
20741   *
20742   * @method Html5#muted
20743   * @return {boolean}
20744   *         - True if the value of `volume` should be ignored and the audio set to silent.
20745   *         - False if the value of `volume` should be used.
20746   *
20747   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
20748   */
20749  'muted',
20750
20751  /**
20752   * Get the value of `defaultMuted` from the media element. `defaultMuted` indicates
20753   * whether the media should start muted or not. Only changes the default state of the
20754   * media. `muted` and `defaultMuted` can have different values. {@link Html5#muted} indicates the
20755   * current state.
20756   *
20757   * @method Html5#defaultMuted
20758   * @return {boolean}
20759   *         - The value of `defaultMuted` from the media element.
20760   *         - True indicates that the media should start muted.
20761   *         - False indicates that the media should not start muted
20762   *
20763   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
20764   */
20765  'defaultMuted',
20766
20767  /**
20768   * Get the value of `autoplay` from the media element. `autoplay` indicates
20769   * that the media should start to play as soon as the page is ready.
20770   *
20771   * @method Html5#autoplay
20772   * @return {boolean}
20773   *         - The value of `autoplay` from the media element.
20774   *         - True indicates that the media should start as soon as the page loads.
20775   *         - False indicates that the media should not start as soon as the page loads.
20776   *
20777   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
20778   */
20779  'autoplay',
20780
20781  /**
20782   * Get the value of `controls` from the media element. `controls` indicates
20783   * whether the native media controls should be shown or hidden.
20784   *
20785   * @method Html5#controls
20786   * @return {boolean}
20787   *         - The value of `controls` from the media element.
20788   *         - True indicates that native controls should be showing.
20789   *         - False indicates that native controls should be hidden.
20790   *
20791   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-controls}
20792   */
20793  'controls',
20794
20795  /**
20796   * Get the value of `loop` from the media element. `loop` indicates
20797   * that the media should return to the start of the media and continue playing once
20798   * it reaches the end.
20799   *
20800   * @method Html5#loop
20801   * @return {boolean}
20802   *         - The value of `loop` from the media element.
20803   *         - True indicates that playback should seek back to start once
20804   *           the end of a media is reached.
20805   *         - False indicates that playback should not loop back to the start when the
20806   *           end of the media is reached.
20807   *
20808   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
20809   */
20810  'loop',
20811
20812  /**
20813   * Get the value of `playsinline` from the media element. `playsinline` indicates
20814   * to the browser that non-fullscreen playback is preferred when fullscreen
20815   * playback is the native default, such as in iOS Safari.
20816   *
20817   * @method Html5#playsinline
20818   * @return {boolean}
20819   *         - The value of `playsinline` from the media element.
20820   *         - True indicates that the media should play inline.
20821   *         - False indicates that the media should not play inline.
20822   *
20823   * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
20824   */
20825  'playsinline'].forEach(function (prop) {
20826    Html5.prototype[prop] = function () {
20827      return this.el_[prop] || this.el_.hasAttribute(prop);
20828    };
20829  });
20830
20831  // Wrap native boolean attributes with setters that set both property and attribute
20832  // The list is as followed:
20833  // setMuted, setDefaultMuted, setAutoplay, setLoop, setPlaysinline
20834  // setControls is special-cased above
20835  [
20836  /**
20837   * Set the value of `muted` on the media element. `muted` indicates that the current
20838   * audio level should be silent.
20839   *
20840   * @method Html5#setMuted
20841   * @param {boolean} muted
20842   *        - True if the audio should be set to silent
20843   *        - False otherwise
20844   *
20845   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
20846   */
20847  'muted',
20848
20849  /**
20850   * Set the value of `defaultMuted` on the media element. `defaultMuted` indicates that the current
20851   * audio level should be silent, but will only effect the muted level on intial playback..
20852   *
20853   * @method Html5.prototype.setDefaultMuted
20854   * @param {boolean} defaultMuted
20855   *        - True if the audio should be set to silent
20856   *        - False otherwise
20857   *
20858   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
20859   */
20860  'defaultMuted',
20861
20862  /**
20863   * Set the value of `autoplay` on the media element. `autoplay` indicates
20864   * that the media should start to play as soon as the page is ready.
20865   *
20866   * @method Html5#setAutoplay
20867   * @param {boolean} autoplay
20868   *         - True indicates that the media should start as soon as the page loads.
20869   *         - False indicates that the media should not start as soon as the page loads.
20870   *
20871   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
20872   */
20873  'autoplay',
20874
20875  /**
20876   * Set the value of `loop` on the media element. `loop` indicates
20877   * that the media should return to the start of the media and continue playing once
20878   * it reaches the end.
20879   *
20880   * @method Html5#setLoop
20881   * @param {boolean} loop
20882   *         - True indicates that playback should seek back to start once
20883   *           the end of a media is reached.
20884   *         - False indicates that playback should not loop back to the start when the
20885   *           end of the media is reached.
20886   *
20887   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
20888   */
20889  'loop',
20890
20891  /**
20892   * Set the value of `playsinline` from the media element. `playsinline` indicates
20893   * to the browser that non-fullscreen playback is preferred when fullscreen
20894   * playback is the native default, such as in iOS Safari.
20895   *
20896   * @method Html5#setPlaysinline
20897   * @param {boolean} playsinline
20898   *         - True indicates that the media should play inline.
20899   *         - False indicates that the media should not play inline.
20900   *
20901   * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
20902   */
20903  'playsinline'].forEach(function (prop) {
20904    Html5.prototype['set' + toTitleCase(prop)] = function (v) {
20905      this.el_[prop] = v;
20906
20907      if (v) {
20908        this.el_.setAttribute(prop, prop);
20909      } else {
20910        this.el_.removeAttribute(prop);
20911      }
20912    };
20913  });
20914
20915  // Wrap native properties with a getter
20916  // The list is as followed
20917  // paused, currentTime, buffered, volume, poster, preload, error, seeking
20918  // seekable, ended, playbackRate, defaultPlaybackRate, played, networkState
20919  // readyState, videoWidth, videoHeight
20920  [
20921  /**
20922   * Get the value of `paused` from the media element. `paused` indicates whether the media element
20923   * is currently paused or not.
20924   *
20925   * @method Html5#paused
20926   * @return {boolean}
20927   *         The value of `paused` from the media element.
20928   *
20929   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-paused}
20930   */
20931  'paused',
20932
20933  /**
20934   * Get the value of `currentTime` from the media element. `currentTime` indicates
20935   * the current second that the media is at in playback.
20936   *
20937   * @method Html5#currentTime
20938   * @return {number}
20939   *         The value of `currentTime` from the media element.
20940   *
20941   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-currenttime}
20942   */
20943  'currentTime',
20944
20945  /**
20946   * Get the value of `buffered` from the media element. `buffered` is a `TimeRange`
20947   * object that represents the parts of the media that are already downloaded and
20948   * available for playback.
20949   *
20950   * @method Html5#buffered
20951   * @return {TimeRange}
20952   *         The value of `buffered` from the media element.
20953   *
20954   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-buffered}
20955   */
20956  'buffered',
20957
20958  /**
20959   * Get the value of `volume` from the media element. `volume` indicates
20960   * the current playback volume of audio for a media. `volume` will be a value from 0
20961   * (silent) to 1 (loudest and default).
20962   *
20963   * @method Html5#volume
20964   * @return {number}
20965   *         The value of `volume` from the media element. Value will be between 0-1.
20966   *
20967   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
20968   */
20969  'volume',
20970
20971  /**
20972   * Get the value of `poster` from the media element. `poster` indicates
20973   * that the url of an image file that can/will be shown when no media data is available.
20974   *
20975   * @method Html5#poster
20976   * @return {string}
20977   *         The value of `poster` from the media element. Value will be a url to an
20978   *         image.
20979   *
20980   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-video-poster}
20981   */
20982  'poster',
20983
20984  /**
20985   * Get the value of `preload` from the media element. `preload` indicates
20986   * what should download before the media is interacted with. It can have the following
20987   * values:
20988   * - none: nothing should be downloaded
20989   * - metadata: poster and the first few frames of the media may be downloaded to get
20990   *   media dimensions and other metadata
20991   * - auto: allow the media and metadata for the media to be downloaded before
20992   *    interaction
20993   *
20994   * @method Html5#preload
20995   * @return {string}
20996   *         The value of `preload` from the media element. Will be 'none', 'metadata',
20997   *         or 'auto'.
20998   *
20999   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
21000   */
21001  'preload',
21002
21003  /**
21004   * Get the value of the `error` from the media element. `error` indicates any
21005   * MediaError that may have occurred during playback. If error returns null there is no
21006   * current error.
21007   *
21008   * @method Html5#error
21009   * @return {MediaError|null}
21010   *         The value of `error` from the media element. Will be `MediaError` if there
21011   *         is a current error and null otherwise.
21012   *
21013   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-error}
21014   */
21015  'error',
21016
21017  /**
21018   * Get the value of `seeking` from the media element. `seeking` indicates whether the
21019   * media is currently seeking to a new position or not.
21020   *
21021   * @method Html5#seeking
21022   * @return {boolean}
21023   *         - The value of `seeking` from the media element.
21024   *         - True indicates that the media is currently seeking to a new position.
21025   *         - False indicates that the media is not seeking to a new position at this time.
21026   *
21027   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seeking}
21028   */
21029  'seeking',
21030
21031  /**
21032   * Get the value of `seekable` from the media element. `seekable` returns a
21033   * `TimeRange` object indicating ranges of time that can currently be `seeked` to.
21034   *
21035   * @method Html5#seekable
21036   * @return {TimeRange}
21037   *         The value of `seekable` from the media element. A `TimeRange` object
21038   *         indicating the current ranges of time that can be seeked to.
21039   *
21040   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seekable}
21041   */
21042  'seekable',
21043
21044  /**
21045   * Get the value of `ended` from the media element. `ended` indicates whether
21046   * the media has reached the end or not.
21047   *
21048   * @method Html5#ended
21049   * @return {boolean}
21050   *         - The value of `ended` from the media element.
21051   *         - True indicates that the media has ended.
21052   *         - False indicates that the media has not ended.
21053   *
21054   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-ended}
21055   */
21056  'ended',
21057
21058  /**
21059   * Get the value of `playbackRate` from the media element. `playbackRate` indicates
21060   * the rate at which the media is currently playing back. Examples:
21061   *   - if playbackRate is set to 2, media will play twice as fast.
21062   *   - if playbackRate is set to 0.5, media will play half as fast.
21063   *
21064   * @method Html5#playbackRate
21065   * @return {number}
21066   *         The value of `playbackRate` from the media element. A number indicating
21067   *         the current playback speed of the media, where 1 is normal speed.
21068   *
21069   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
21070   */
21071  'playbackRate',
21072
21073  /**
21074   * Get the value of `defaultPlaybackRate` from the media element. `defaultPlaybackRate` indicates
21075   * the rate at which the media is currently playing back. This value will not indicate the current
21076   * `playbackRate` after playback has started, use {@link Html5#playbackRate} for that.
21077   *
21078   * Examples:
21079   *   - if defaultPlaybackRate is set to 2, media will play twice as fast.
21080   *   - if defaultPlaybackRate is set to 0.5, media will play half as fast.
21081   *
21082   * @method Html5.prototype.defaultPlaybackRate
21083   * @return {number}
21084   *         The value of `defaultPlaybackRate` from the media element. A number indicating
21085   *         the current playback speed of the media, where 1 is normal speed.
21086   *
21087   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
21088   */
21089  'defaultPlaybackRate',
21090
21091  /**
21092   * Get the value of `played` from the media element. `played` returns a `TimeRange`
21093   * object representing points in the media timeline that have been played.
21094   *
21095   * @method Html5#played
21096   * @return {TimeRange}
21097   *         The value of `played` from the media element. A `TimeRange` object indicating
21098   *         the ranges of time that have been played.
21099   *
21100   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-played}
21101   */
21102  'played',
21103
21104  /**
21105   * Get the value of `networkState` from the media element. `networkState` indicates
21106   * the current network state. It returns an enumeration from the following list:
21107   * - 0: NETWORK_EMPTY
21108   * - 1: NETWORK_IDLE
21109   * - 2: NETWORK_LOADING
21110   * - 3: NETWORK_NO_SOURCE
21111   *
21112   * @method Html5#networkState
21113   * @return {number}
21114   *         The value of `networkState` from the media element. This will be a number
21115   *         from the list in the description.
21116   *
21117   * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-networkstate}
21118   */
21119  'networkState',
21120
21121  /**
21122   * Get the value of `readyState` from the media element. `readyState` indicates
21123   * the current state of the media element. It returns an enumeration from the
21124   * following list:
21125   * - 0: HAVE_NOTHING
21126   * - 1: HAVE_METADATA
21127   * - 2: HAVE_CURRENT_DATA
21128   * - 3: HAVE_FUTURE_DATA
21129   * - 4: HAVE_ENOUGH_DATA
21130   *
21131   * @method Html5#readyState
21132   * @return {number}
21133   *         The value of `readyState` from the media element. This will be a number
21134   *         from the list in the description.
21135   *
21136   * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#ready-states}
21137   */
21138  'readyState',
21139
21140  /**
21141   * Get the value of `videoWidth` from the video element. `videoWidth` indicates
21142   * the current width of the video in css pixels.
21143   *
21144   * @method Html5#videoWidth
21145   * @return {number}
21146   *         The value of `videoWidth` from the video element. This will be a number
21147   *         in css pixels.
21148   *
21149   * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
21150   */
21151  'videoWidth',
21152
21153  /**
21154   * Get the value of `videoHeight` from the video element. `videoHeight` indicates
21155   * the current height of the video in css pixels.
21156   *
21157   * @method Html5#videoHeight
21158   * @return {number}
21159   *         The value of `videoHeight` from the video element. This will be a number
21160   *         in css pixels.
21161   *
21162   * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
21163   */
21164  'videoHeight'].forEach(function (prop) {
21165    Html5.prototype[prop] = function () {
21166      return this.el_[prop];
21167    };
21168  });
21169
21170  // Wrap native properties with a setter in this format:
21171  // set + toTitleCase(name)
21172  // The list is as follows:
21173  // setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate
21174  [
21175  /**
21176   * Set the value of `volume` on the media element. `volume` indicates the current
21177   * audio level as a percentage in decimal form. This means that 1 is 100%, 0.5 is 50%, and
21178   * so on.
21179   *
21180   * @method Html5#setVolume
21181   * @param {number} percentAsDecimal
21182   *        The volume percent as a decimal. Valid range is from 0-1.
21183   *
21184   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
21185   */
21186  'volume',
21187
21188  /**
21189   * Set the value of `src` on the media element. `src` indicates the current
21190   * {@link Tech~SourceObject} for the media.
21191   *
21192   * @method Html5#setSrc
21193   * @param {Tech~SourceObject} src
21194   *        The source object to set as the current source.
21195   *
21196   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-src}
21197   */
21198  'src',
21199
21200  /**
21201   * Set the value of `poster` on the media element. `poster` is the url to
21202   * an image file that can/will be shown when no media data is available.
21203   *
21204   * @method Html5#setPoster
21205   * @param {string} poster
21206   *        The url to an image that should be used as the `poster` for the media
21207   *        element.
21208   *
21209   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-poster}
21210   */
21211  'poster',
21212
21213  /**
21214   * Set the value of `preload` on the media element. `preload` indicates
21215   * what should download before the media is interacted with. It can have the following
21216   * values:
21217   * - none: nothing should be downloaded
21218   * - metadata: poster and the first few frames of the media may be downloaded to get
21219   *   media dimensions and other metadata
21220   * - auto: allow the media and metadata for the media to be downloaded before
21221   *    interaction
21222   *
21223   * @method Html5#setPreload
21224   * @param {string} preload
21225   *         The value of `preload` to set on the media element. Must be 'none', 'metadata',
21226   *         or 'auto'.
21227   *
21228   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
21229   */
21230  'preload',
21231
21232  /**
21233   * Set the value of `playbackRate` on the media element. `playbackRate` indicates
21234   * the rate at which the media should play back. Examples:
21235   *   - if playbackRate is set to 2, media will play twice as fast.
21236   *   - if playbackRate is set to 0.5, media will play half as fast.
21237   *
21238   * @method Html5#setPlaybackRate
21239   * @return {number}
21240   *         The value of `playbackRate` from the media element. A number indicating
21241   *         the current playback speed of the media, where 1 is normal speed.
21242   *
21243   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
21244   */
21245  'playbackRate',
21246
21247  /**
21248   * Set the value of `defaultPlaybackRate` on the media element. `defaultPlaybackRate` indicates
21249   * the rate at which the media should play back upon initial startup. Changing this value
21250   * after a video has started will do nothing. Instead you should used {@link Html5#setPlaybackRate}.
21251   *
21252   * Example Values:
21253   *   - if playbackRate is set to 2, media will play twice as fast.
21254   *   - if playbackRate is set to 0.5, media will play half as fast.
21255   *
21256   * @method Html5.prototype.setDefaultPlaybackRate
21257   * @return {number}
21258   *         The value of `defaultPlaybackRate` from the media element. A number indicating
21259   *         the current playback speed of the media, where 1 is normal speed.
21260   *
21261   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate}
21262   */
21263  'defaultPlaybackRate'].forEach(function (prop) {
21264    Html5.prototype['set' + toTitleCase(prop)] = function (v) {
21265      this.el_[prop] = v;
21266    };
21267  });
21268
21269  // wrap native functions with a function
21270  // The list is as follows:
21271  // pause, load, play
21272  [
21273  /**
21274   * A wrapper around the media elements `pause` function. This will call the `HTML5`
21275   * media elements `pause` function.
21276   *
21277   * @method Html5#pause
21278   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause}
21279   */
21280  'pause',
21281
21282  /**
21283   * A wrapper around the media elements `load` function. This will call the `HTML5`s
21284   * media element `load` function.
21285   *
21286   * @method Html5#load
21287   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load}
21288   */
21289  'load',
21290
21291  /**
21292   * A wrapper around the media elements `play` function. This will call the `HTML5`s
21293   * media element `play` function.
21294   *
21295   * @method Html5#play
21296   * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play}
21297   */
21298  'play'].forEach(function (prop) {
21299    Html5.prototype[prop] = function () {
21300      return this.el_[prop]();
21301    };
21302  });
21303
21304  Tech.withSourceHandlers(Html5);
21305
21306  /**
21307   * Native source handler for Html5, simply passes the source to the media element.
21308   *
21309   * @property {Tech~SourceObject} source
21310   *        The source object
21311   *
21312   * @property {Html5} tech
21313   *        The instance of the HTML5 tech.
21314   */
21315  Html5.nativeSourceHandler = {};
21316
21317  /**
21318   * Check if the media element can play the given mime type.
21319   *
21320   * @param {string} type
21321   *        The mimetype to check
21322   *
21323   * @return {string}
21324   *         'probably', 'maybe', or '' (empty string)
21325   */
21326  Html5.nativeSourceHandler.canPlayType = function (type) {
21327    // IE without MediaPlayer throws an error (#519)
21328    try {
21329      return Html5.TEST_VID.canPlayType(type);
21330    } catch (e) {
21331      return '';
21332    }
21333  };
21334
21335  /**
21336   * Check if the media element can handle a source natively.
21337   *
21338   * @param {Tech~SourceObject} source
21339   *         The source object
21340   *
21341   * @param {Object} [options]
21342   *         Options to be passed to the tech.
21343   *
21344   * @return {string}
21345   *         'probably', 'maybe', or '' (empty string).
21346   */
21347  Html5.nativeSourceHandler.canHandleSource = function (source, options) {
21348
21349    // If a type was provided we should rely on that
21350    if (source.type) {
21351      return Html5.nativeSourceHandler.canPlayType(source.type);
21352
21353      // If no type, fall back to checking 'video/[EXTENSION]'
21354    } else if (source.src) {
21355      var ext = getFileExtension(source.src);
21356
21357      return Html5.nativeSourceHandler.canPlayType('video/' + ext);
21358    }
21359
21360    return '';
21361  };
21362
21363  /**
21364   * Pass the source to the native media element.
21365   *
21366   * @param {Tech~SourceObject} source
21367   *        The source object
21368   *
21369   * @param {Html5} tech
21370   *        The instance of the Html5 tech
21371   *
21372   * @param {Object} [options]
21373   *        The options to pass to the source
21374   */
21375  Html5.nativeSourceHandler.handleSource = function (source, tech, options) {
21376    tech.setSrc(source.src);
21377  };
21378
21379  /**
21380   * A noop for the native dispose function, as cleanup is not needed.
21381   */
21382  Html5.nativeSourceHandler.dispose = function () {};
21383
21384  // Register the native source handler
21385  Html5.registerSourceHandler(Html5.nativeSourceHandler);
21386
21387  Tech.registerTech('Html5', Html5);
21388
21389  var _templateObject$2 = taggedTemplateLiteralLoose(['\n        Using the tech directly can be dangerous. I hope you know 
21389what 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      ']);
21390
21391  // The following tech events are simply re-triggered
21392  // on the player when they happen
21393  var TECH_EVENTS_RETRIGGER = [
21394  /**
21395   * Fired while the user agent is downloading media data.
21396   *
21397   * @event Player#progress
21398   * @type {EventTarget~Event}
21399   */
21400  /**
21401   * Retrigger the `progress` event that was triggered by the {@link Tech}.
21402   *
21403   * @private
21404   * @method Player#handleTechProgress_
21405   * @fires Player#progress
21406   * @listens Tech#progress
21407   */
21408  'progress',
21409
21410  /**
21411   * Fires when the loading of an audio/video is aborted.
21412   *
21413   * @event Player#abort
21414   * @type {EventTarget~Event}
21415   */
21416  /**
21417   * Retrigger the `abort` event that was triggered by the {@link Tech}.
21418   *
21419   * @private
21420   * @method Player#handleTechAbort_
21421   * @fires Player#abort
21422   * @listens Tech#abort
21423   */
21424  'abort',
21425
21426  /**
21427   * Fires when the browser is intentionally not getting media data.
21428   *
21429   * @event Player#suspend
21430   * @type {EventTarget~Event}
21431   */
21432  /**
21433   * Retrigger the `suspend` event that was triggered by the {@link Tech}.
21434   *
21435   * @private
21436   * @method Player#handleTechSuspend_
21437   * @fires Player#suspend
21438   * @listens Tech#suspend
21439   */
21440  'suspend',
21441
21442  /**
21443   * Fires when the current playlist is empty.
21444   *
21445   * @event Player#emptied
21446   * @type {EventTarget~Event}
21447   */
21448  /**
21449   * Retrigger the `emptied` event that was triggered by the {@link Tech}.
21450   *
21451   * @private
21452   * @method Player#handleTechEmptied_
21453   * @fires Player#emptied
21454   * @listens Tech#emptied
21455   */
21456  'emptied',
21457  /**
21458   * Fires when the browser is trying to get media data, but data is not available.
21459   *
21460   * @event Player#stalled
21461   * @type {EventTarget~Event}
21462   */
21463  /**
21464   * Retrigger the `stalled` event that was triggered by the {@link Tech}.
21465   *
21466   * @private
21467   * @method Player#handleTechStalled_
21468   * @fires Player#stalled
21469   * @listens Tech#stalled
21470   */
21471  'stalled',
21472
21473  /**
21474   * Fires when the browser has loaded meta data for the audio/video.
21475   *
21476   * @event Player#loadedmetadata
21477   * @type {EventTarget~Event}
21478   */
21479  /**
21480   * Retrigger the `stalled` event that was triggered by the {@link Tech}.
21481   *
21482   * @private
21483   * @method Player#handleTechLoadedmetadata_
21484   * @fires Player#loadedmetadata
21485   * @listens Tech#loadedmetadata
21486   */
21487  'loadedmetadata',
21488
21489  /**
21490   * Fires when the browser has loaded the current frame of the audio/video.
21491   *
21492   * @event Player#loadeddata
21493   * @type {event}
21494   */
21495  /**
21496   * Retrigger the `loadeddata` event that was triggered by the {@link Tech}.
21497   *
21498   * @private
21499   * @method Player#handleTechLoaddeddata_
21500   * @fires Player#loadeddata
21501   * @listens Tech#loadeddata
21502   */
21503  'loadeddata',
21504
21505  /**
21506   * Fires when the current playback position has changed.
21507   *
21508   * @event Player#timeupdate
21509   * @type {event}
21510   */
21511  /**
21512   * Retrigger the `timeupdate` event that was triggered by the {@link Tech}.
21513   *
21514   * @private
21515   * @method Player#handleTechTimeUpdate_
21516   * @fires Player#timeupdate
21517   * @listens Tech#timeupdate
21518   */
21519  'timeupdate',
21520
21521  /**
21522   * Fires when the video's intrinsic dimensions change
21523   *
21524   * @event Player#resize
21525   * @type {event}
21526   */
21527  /**
21528   * Retrigger the `resize` event that was triggered by the {@link Tech}.
21529   *
21530   * @private
21531   * @method Player#handleTechResize_
21532   * @fires Player#resize
21533   * @listens Tech#resize
21534   */
21535  'resize',
21536
21537  /**
21538   * Fires when the volume has been changed
21539   *
21540   * @event Player#volumechange
21541   * @type {event}
21542   */
21543  /**
21544   * Retrigger the `volumechange` event that was triggered by the {@link Tech}.
21545   *
21546   * @private
21547   * @method Player#handleTechVolumechange_
21548   * @fires Player#volumechange
21549   * @listens Tech#volumechange
21550   */
21551  'volumechange',
21552
21553  /**
21554   * Fires when the text track has been changed
21555   *
21556   * @event Player#texttrackchange
21557   * @type {event}
21558   */
21559  /**
21560   * Retrigger the `texttrackchange` event that was triggered by the {@link Tech}.
21561   *
21562   * @private
21563   * @method Player#handleTechTexttrackchange_
21564   * @fires Player#texttrackchange
21565   * @listens Tech#texttrackchange
21566   */
21567  'texttrackchange'];
21568
21569  // events to queue when playback rate is zero
21570  // this is a hash for the sole purpose of mapping non-camel-cased event names
21571  // to camel-cased function names
21572  var TECH_EVENTS_QUEUE = {
21573    canplay: 'CanPlay',
21574    canplaythrough: 'CanPlayThrough',
21575    playing: 'Playing',
21576    seeked: 'Seeked'
21577  };
21578
21579  /**
21580   * An instance of the `Player` class is created when any of the Video.js setup methods
21581   * are used to initialize a video.
21582   *
21583   * After an instance has been created it can be accessed globally in two ways:
21584   * 1. By calling `videojs('example_video_1');`
21585   * 2. By using it directly via  `videojs.players.example_video_1;`
21586   *
21587   * @extends Component
21588   */
21589
21590  var Player = function (_Component) {
21591    inherits(Player, _Component);
21592
21593    /**
21594     * Create an instance of this class.
21595     *
21596     * @param {Element} tag
21597     *        The original video DOM element used for configuring options.
21598     *
21599     * @param {Object} [options]
21600     *        Object of option names and values.
21601     *
21602     * @param {Component~ReadyCallback} [ready]
21603     *        Ready callback function.
21604     */
21605    function Player(tag, options, ready) {
21606      classCallCheck(this, Player);
21607
21608      // Make sure tag ID exists
21609      tag.id = tag.id || options.id || 'vjs_video_' + newGUID();
21610
21611      // Set Options
21612      // The options argument overrides options set in the video tag
21613      // which overrides globally set options.
21614      // This latter part coincides with the load order
21615      // (tag must exist before Player)
21616      options = assign(Player.getTagSettings(tag), options);
21617
21618      // Delay the initialization of children because we need to set up
21619      // player properties first, and can't use `this` before `super()`
21620      options.initChildren = false;
21621
21622      // Same with creating the element
21623      options.createEl = false;
21624
21625      // don't auto mixin the evented mixin
21626      options.evented = false;
21627
21628      // we don't want the player to report touch activity on itself
21629      // see enableTouchActivity in Component
21630      options.reportTouchActivity = false;
21631
21632      // If language is not set, get the closest lang attribute
21633      if (!options.language) {
21634        if (typeof tag.closest === 'function') {
21635          var closest = tag.closest('[lang]');
21636
21637          if (closest && closest.getAttribute) {
21638            options.language = closest.getAttribute('lang');
21639          }
21640        } else {
21641          var element = tag;
21642
21643          while (element && element.nodeType === 1) {
21644            if (getAttributes(element).hasOwnProperty('lang')) {
21645              options.language = element.getAttribute('lang');
21646              break;
21647            }
21648            element = element.parentNode;
21649          }
21650        }
21651      }
21652
21653      // Run base component initializing with new options
21654
21655      // Tracks when a tech changes the poster
21656      var _this = possibleConstructorReturn(this, _Component.call(this, null, options, ready));
21657
21658      _this.isPosterFromTech_ = false;
vendor: 5,558 bytes, lines 21659-21824
21659
21660      // Holds callback info that gets queued when playback rate is zero
21661      // and a seek is happening
21662      _this.queuedCallbacks_ = [];
21663
21664      // Turn off API access because we're loading a new tech that might load asynchronously
21665      _this.isReady_ = false;
21666
21667      // Init state hasStarted_
21668      _this.hasStarted_ = false;
21669
21670      // Init state userActive_
21671      _this.userActive_ = false;
21672
21673      // if the global option object was accidentally blown away by
21674      // someone, bail early with an informative error
21675      if (!_this.options_ || !_this.options_.techOrder || !_this.options_.techOrder.length) {
21676        throw new Error('No techOrder specified. Did you overwrite ' + 'videojs.options instead of just changing the ' + 'properties you want to override?');
21677      }
21678
21679      // Store the original tag used to set options
21680      _this.tag = tag;
21681
21682      // Store the tag attributes used to restore html5 element
21683      _this.tagAttributes = tag && getAttributes(tag);
21684
21685      // Update current language
21686      _this.language(_this.options_.language);
21687
21688      // Update Supported Languages
21689      if (options.languages) {
21690        // Normalise player option languages to lowercase
21691        var languagesToLower = {};
21692
21693        Object.getOwnPropertyNames(options.languages).forEach(function (name$$1) {
21694          languagesToLower[name$$1.toLowerCase()] = options.languages[name$$1];
21695        });
21696        _this.languages_ = languagesToLower;
21697      } else {
21698        _this.languages_ = Player.prototype.options_.languages;
21699      }
21700
21701      // Cache for video property values.
21702      _this.cache_ = {};
21703
21704      // Set poster
21705      _this.poster_ = options.poster || '';
21706
21707      // Set controls
21708      _this.controls_ = !!options.controls;
21709
21710      // Set default values for lastVolume
21711      _this.cache_.lastVolume = 1;
21712
21713      // Original tag settings stored in options
21714      // now remove immediately so native controls don't flash.
21715      // May be turned back on by HTML5 tech if nativeControlsForTouch is true
21716      tag.controls = false;
21717      tag.removeAttribute('controls');
21718
21719      // the attribute overrides the option
21720      if (tag.hasAttribute('autoplay')) {
21721        _this.options_.autoplay = true;
21722      } else {
21723        // otherwise use the setter to validate and
21724        // set the correct value.
21725        _this.autoplay(_this.options_.autoplay);
21726      }
21727
21728      /*
21729       * Store the internal state of scrubbing
21730       *
21731       * @private
21732       * @return {Boolean} True if the user is scrubbing
21733       */
21734      _this.scrubbing_ = false;
21735
21736      _this.el_ = _this.createEl();
21737
21738      // Set default value for lastPlaybackRate
21739      _this.cache_.lastPlaybackRate = _this.defaultPlaybackRate();
21740
21741      // Make this an evented object and use `el_` as its event bus.
21742      evented(_this, { eventBusKey: 'el_' });
21743
21744      // We also want to pass the original player options to each component and plugin
21745      // as well so they don't need to reach back into the player for options later.
21746      // We also need to do another copy of this.options_ so we don't end up with
21747      // an infinite loop.
21748      var playerOptionsCopy = mergeOptions(_this.options_);
21749
21750      // Load plugins
21751      if (options.plugins) {
21752        var plugins = options.plugins;
21753
21754        Object.keys(plugins).forEach(function (name$$1) {
21755          if (typeof this[name$$1] === 'function') {
21756            this[name$$1](plugins[name$$1]);
21757          } else {
21758            throw new Error('plugin "' + name$$1 + '" does not exist');
21759          }
21760        }, _this);
21761      }
21762
21763      _this.options_.playerOptions = playerOptionsCopy;
21764
21765      _this.middleware_ = [];
21766
21767      _this.initChildren();
21768
21769      // Set isAudio based on whether or not an audio tag was used
21770      _this.isAudio(tag.nodeName.toLowerCase() === 'audio');
21771
21772      // Update controls className. Can't do this when the controls are initially
21773      // set because the element doesn't exist yet.
21774      if (_this.controls()) {
21775        _this.addClass('vjs-controls-enabled');
21776      } else {
21777        _this.addClass('vjs-controls-disabled');
21778      }
21779
21780      // Set ARIA label and region role depending on player type
21781      _this.el_.setAttribute('role', 'region');
21782      if (_this.isAudio()) {
21783        _this.el_.setAttribute('aria-label', _this.localize('Audio Player'));
21784      } else {
21785        _this.el_.setAttribute('aria-label', _this.localize('Video Player'));
21786      }
21787
21788      if (_this.isAudio()) {
21789        _this.addClass('vjs-audio');
21790      }
21791
21792      if (_this.flexNotSupported_()) {
21793        _this.addClass('vjs-no-flex');
21794      }
21795
21796      // TODO: Make this smarter. Toggle user state between touching/mousing
21797      // using events, since devices can have both touch and mouse events.
21798      // if (browser.TOUCH_ENABLED) {
21799      //   this.addClass('vjs-touch-enabled');
21800      // }
21801
21802      // iOS Safari has broken hover handling
21803      if (!IS_IOS) {
21804        _this.addClass('vjs-workinghover');
21805      }
21806
21807      // Make player easily findable by ID
21808      Player.players[_this.id_] = _this;
21809
21810      // Add a major version class to aid css in plugins
21811      var majorVersion = version.split('.')[0];
21812
21813      _this.addClass('vjs-v' + majorVersion);
21814
21815      // When the player is first initialized, trigger activity so components
21816      // like the control bar show themselves if needed
21817      _this.userActive(true);
21818      _this.reportUserActivity();
21819
21820      _this.one('play', _this.listenForUserActivity_);
21821      _this.on('fullscreenchange', _this.handleFullscreenChange_);
21822      _this.on('stageclick', _this.handleStageClick_);
21823
21824      _this.changingSrc_ = false;
21825      _this.playWaitingForReady_ = false;
21826      _this.playOnLoadstart_ = null;
21827      return _this;
21828    }
21829
21830    /**
21831     * Destroys the video player and does any necessary cleanup.
21832     *
21833     * This is especially helpful if you are dynamically adding and removing videos
21834     * to/from the DOM.
21835     *
21836     * @fires Player#dispose
21837     */
21838
21839
21840    Player.prototype.dispose = function dispose() {
21841      /**
21842       * Called when the player is being disposed of.
21843       *
21844       * @event Player#dispose
21845       * @type {EventTarget~Event}
21846       */
21847      this.trigger('dispose');
21848      // prevent dispose from being called twice
21849      this.off('dispose');
21850
21851      if (this.styleEl_ && this.styleEl_.parentNode) {
21852        this.styleEl_.parentNode.removeChild(this.styleEl_);
21853        this.styleEl_ = null;
21854      }
21855
21856      // Kill reference to this player
21857      Player.players[this.id_] = null;
21858
21859      if (this.tag && this.tag.player) {
21860        this.tag.player = null;
21861      }
21862
21863      if (this.el_ && this.el_.player) {
21864        this.el_.player = null;
21865      }
21866
21867      if (this.tech_) {
21868        this.tech_.dispose();
21869        this.isPosterFromTech_ = false;
vendor: 57,008 bytes, lines 21870-23739
21870        this.poster_ = '';
21871      }
21872
21873      if (this.playerElIngest_) {
21874        this.playerElIngest_ = null;
21875      }
21876
21877      if (this.tag) {
21878        this.tag = null;
21879      }
21880
21881      clearCacheForPlayer(this);
21882
21883      // the actual .el_ is removed here
21884      _Component.prototype.dispose.call(this);
21885    };
21886
21887    /**
21888     * Create the `Player`'s DOM element.
21889     *
21890     * @return {Element}
21891     *         The DOM element that gets created.
21892     */
21893
21894
21895    Player.prototype.createEl = function createEl$$1() {
21896      var tag = this.tag;
21897      var el = void 0;
21898      var playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player');
21899      var divEmbed = this.tag.tagName.toLowerCase() === 'video-js';
21900
21901      if (playerElIngest) {
21902        el = this.el_ = tag.parentNode;
21903      } else if (!divEmbed) {
21904        el = this.el_ = _Component.prototype.createEl.call(this, 'div');
21905      }
21906
21907      // Copy over all the attributes from the tag, including ID and class
21908      // ID will now reference player box, not the video tag
21909      var attrs = getAttributes(tag);
21910
21911      if (divEmbed) {
21912        el = this.el_ = tag;
21913        tag = this.tag = document_1.createElement('video');
21914        while (el.children.length) {
21915          tag.appendChild(el.firstChild);
21916        }
21917
21918        if (!hasClass(el, 'video-js')) {
21919          addClass(el, 'video-js');
21920        }
21921
21922        el.appendChild(tag);
21923
21924        playerElIngest = this.playerElIngest_ = el;
21925        // move properties over from our custom `video-js` element
21926        // to our new `video` element. This will move things like
21927        // `src` or `controls` that were set via js before the player
21928        // was initialized.
21929        Object.keys(el).forEach(function (k) {
21930          tag[k] = el[k];
21931        });
21932      }
21933
21934      // set tabindex to -1 to remove the video element from the focus order
21935      tag.setAttribute('tabindex', '-1');
21936      // Workaround for #4583 (JAWS+IE doesn't announce BPB or play button)
21937      // See https://github.com/FreedomScientific/VFO-standards-support/issues/78
21938      // Note that we can't detect if JAWS is being used, but this ARIA attribute
21939      //  doesn't change behavior of IE11 if JAWS is not being used
21940      if (IE_VERSION) {
21941        tag.setAttribute('role', 'application');
21942      }
21943
21944      // Remove width/height attrs from tag so CSS can make it 100% width/height
21945      tag.removeAttribute('width');
21946      tag.removeAttribute('height');
21947
21948      Object.getOwnPropertyNames(attrs).forEach(function (attr) {
21949        // don't copy over the class attribute to the player element when we're in a div embed
21950        // the class is already set up properly in the divEmbed case
21951        // and we want to make sure that the `video-js` class doesn't get lost
21952        if (!(divEmbed && attr === 'class')) {
21953          el.setAttribute(attr, attrs[attr]);
21954        }
21955
21956        if (divEmbed) {
21957          tag.setAttribute(attr, attrs[attr]);
21958        }
21959      });
21960
21961      // Update tag id/class for use as HTML5 playback tech
21962      // Might think we should do this after embedding in container so .vjs-tech class
21963      // doesn't flash 100% width/height, but class only applies with .video-js parent
21964      tag.playerId = tag.id;
21965      tag.id += '_html5_api';
21966      tag.className = 'vjs-tech';
21967
21968      // Make player findable on elements
21969      tag.player = el.player = this;
21970      // Default state of video is paused
21971      this.addClass('vjs-paused');
21972
21973      // Add a style element in the player that we'll use to set the width/height
21974      // of the player in a way that's still overrideable by CSS, just like the
21975      // video element
21976      if (window_1.VIDEOJS_NO_DYNAMIC_STYLE !== true) {
21977        this.styleEl_ = createStyleElement('vjs-styles-dimensions');
21978        var defaultsStyleEl = $('.vjs-styles-defaults');
21979        var head = $('head');
21980
21981        head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild);
21982      }
21983
21984      // Pass in the width/height/aspectRatio options which will update the style el
21985      this.width(this.options_.width);
21986      this.height(this.options_.height);
21987      this.fluid(this.options_.fluid);
21988      this.aspectRatio(this.options_.aspectRatio);
21989
21990      // Hide any links within the video/audio tag,
21991      // because IE doesn't hide them completely from screen readers.
21992      var links = tag.getElementsByTagName('a');
21993
21994      for (var i = 0; i < links.length; i++) {
21995        var linkEl = links.item(i);
21996
21997        addClass(linkEl, 'vjs-hidden');
21998        linkEl.setAttribute('hidden', 'hidden');
21999      }
22000
22001      // insertElFirst seems to cause the networkState to flicker from 3 to 2, so
22002      // keep track of the original for later so we can know if the source originally failed
22003      tag.initNetworkState_ = tag.networkState;
22004
22005      // Wrap video tag in div (el/box) container
22006      if (tag.parentNode && !playerElIngest) {
22007        tag.parentNode.insertBefore(el, tag);
22008      }
22009
22010      // insert the tag as the first child of the player element
22011      // then manually add it to the children array so that this.addChild
22012      // will work properly for other components
22013      //
22014      // Breaks iPhone, fixed in HTML5 setup.
22015      prependTo(tag, el);
22016      this.children_.unshift(tag);
22017
22018      // Set lang attr on player to ensure CSS :lang() in consistent with player
22019      // if it's been set to something different to the doc
22020      this.el_.setAttribute('lang', this.language_);
22021
22022      this.el_ = el;
22023
22024      return el;
22025    };
22026
22027    /**
22028     * A getter/setter for the `Player`'s width. Returns the player's configured value.
22029     * To get the current width use `currentWidth()`.
22030     *
22031     * @param {number} [value]
22032     *        The value to set the `Player`'s width to.
22033     *
22034     * @return {number}
22035     *         The current width of the `Player` when getting.
22036     */
22037
22038
22039    Player.prototype.width = function width(value) {
22040      return this.dimension('width', value);
22041    };
22042
22043    /**
22044     * A getter/setter for the `Player`'s height. Returns the player's configured value.
22045     * To get the current height use `currentheight()`.
22046     *
22047     * @param {number} [value]
22048     *        The value to set the `Player`'s heigth to.
22049     *
22050     * @return {number}
22051     *         The current height of the `Player` when getting.
22052     */
22053
22054
22055    Player.prototype.height = function height(value) {
22056      return this.dimension('height', value);
22057    };
22058
22059    /**
22060     * A getter/setter for the `Player`'s width & height.
22061     *
22062     * @param {string} dimension
22063     *        This string can be:
22064     *        - 'width'
22065     *        - 'height'
22066     *
22067     * @param {number} [value]
22068     *        Value for dimension specified in the first argument.
22069     *
22070     * @return {number}
22071     *         The dimension arguments value when getting (width/height).
22072     */
22073
22074
22075    Player.prototype.dimension = function dimension(_dimension, value) {
22076      var privDimension = _dimension + '_';
22077
22078      if (value === undefined) {
22079        return this[privDimension] || 0;
22080      }
22081
22082      if (value === '') {
22083        // If an empty string is given, reset the dimension to be automatic
22084        this[privDimension] = undefined;
22085        this.updateStyleEl_();
22086        return;
22087      }
22088
22089      var parsedVal = parseFloat(value);
22090
22091      if (isNaN(parsedVal)) {
22092        log$1.error('Improper value "' + value + '" supplied for for ' + _dimension);
22093        return;
22094      }
22095
22096      this[privDimension] = parsedVal;
22097      this.updateStyleEl_();
22098    };
22099
22100    /**
22101     * A getter/setter/toggler for the vjs-fluid `className` on the `Player`.
22102     *
22103     * @param {boolean} [bool]
22104     *        - A value of true adds the class.
22105     *        - A value of false removes the class.
22106     *        - No value will toggle the fluid class.
22107     *
22108     * @return {boolean|undefined}
22109     *         - The value of fluid when getting.
22110     *         - `undefined` when setting.
22111     */
22112
22113
22114    Player.prototype.fluid = function fluid(bool) {
22115      if (bool === undefined) {
22116        return !!this.fluid_;
22117      }
22118
22119      this.fluid_ = !!bool;
22120
22121      if (bool) {
22122        this.addClass('vjs-fluid');
22123      } else {
22124        this.removeClass('vjs-fluid');
22125      }
22126
22127      this.updateStyleEl_();
22128    };
22129
22130    /**
22131     * Get/Set the aspect ratio
22132     *
22133     * @param {string} [ratio]
22134     *        Aspect ratio for player
22135     *
22136     * @return {string|undefined}
22137     *         returns the current aspect ratio when getting
22138     */
22139
22140    /**
22141     * A getter/setter for the `Player`'s aspect ratio.
22142     *
22143     * @param {string} [ratio]
22144     *        The value to set the `Player's aspect ratio to.
22145     *
22146     * @return {string|undefined}
22147     *         - The current aspect ratio of the `Player` when getting.
22148     *         - undefined when setting
22149     */
22150
22151
22152    Player.prototype.aspectRatio = function aspectRatio(ratio) {
22153      if (ratio === undefined) {
22154        return this.aspectRatio_;
22155      }
22156
22157      // Check for width:height format
22158      if (!/^\d+\:\d+$/.test(ratio)) {
22159        throw new Error('Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.');
22160      }
22161      this.aspectRatio_ = ratio;
22162
22163      // We're assuming if you set an aspect ratio you want fluid mode,
22164      // because in fixed mode you could calculate width and height yourself.
22165      this.fluid(true);
22166
22167      this.updateStyleEl_();
22168    };
22169
22170    /**
22171     * Update styles of the `Player` element (height, width and aspect ratio).
22172     *
22173     * @private
22174     * @listens Tech#loadedmetadata
22175     */
22176
22177
22178    Player.prototype.updateStyleEl_ = function updateStyleEl_() {
22179      if (window_1.VIDEOJS_NO_DYNAMIC_STYLE === true) {
22180        var _width = typeof this.width_ === 'number' ? this.width_ : this.options_.width;
22181        var _height = typeof this.height_ === 'number' ? this.height_ : this.options_.height;
22182        var techEl = this.tech_ && this.tech_.el();
22183
22184        if (techEl) {
22185          if (_width >= 0) {
22186            techEl.width = _width;
22187          }
22188          if (_height >= 0) {
22189            techEl.height = _height;
22190          }
22191        }
22192
22193        return;
22194      }
22195
22196      var width = void 0;
22197      var height = void 0;
22198      var aspectRatio = void 0;
22199      var idClass = void 0;
22200
22201      // The aspect ratio is either used directly or to calculate width and height.
22202      if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') {
22203        // Use any aspectRatio that's been specifically set
22204        aspectRatio = this.aspectRatio_;
22205      } else if (this.videoWidth() > 0) {
22206        // Otherwise try to get the aspect ratio from the video metadata
22207        aspectRatio = this.videoWidth() + ':' + this.videoHeight();
22208      } else {
22209        // Or use a default. The video element's is 2:1, but 16:9 is more common.
22210        aspectRatio = '16:9';
22211      }
22212
22213      // Get the ratio as a decimal we can use to calculate dimensions
22214      var ratioParts = aspectRatio.split(':');
22215      var ratioMultiplier = ratioParts[1] / ratioParts[0];
22216
22217      if (this.width_ !== undefined) {
22218        // Use any width that's been specifically set
22219        width = this.width_;
22220      } else if (this.height_ !== undefined) {
22221        // Or calulate the width from the aspect ratio if a height has been set
22222        width = this.height_ / ratioMultiplier;
22223      } else {
22224        // Or use the video's metadata, or use the video el's default of 300
22225        width = this.videoWidth() || 300;
22226      }
22227
22228      if (this.height_ !== undefined) {
22229        // Use any height that's been specifically set
22230        height = this.height_;
22231      } else {
22232        // Otherwise calculate the height from the ratio and the width
22233        height = width * ratioMultiplier;
22234      }
22235
22236      // Ensure the CSS class is valid by starting with an alpha character
22237      if (/^[^a-zA-Z]/.test(this.id())) {
22238        idClass = 'dimensions-' + this.id();
22239      } else {
22240        idClass = this.id() + '-dimensions';
22241      }
22242
22243      // Ensure the right class is still on the player for the style element
22244      this.addClass(idClass);
22245
22246      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    ');
22247    };
22248
22249    /**
22250     * Load/Create an instance of playback {@link Tech} including element
22251     * and API methods. Then append the `Tech` element in `Player` as a child.
22252     *
22253     * @param {string} techName
22254     *        name of the playback technology
22255     *
22256     * @param {string} source
22257     *        video source
22258     *
22259     * @private
22260     */
22261
22262
22263    Player.prototype.loadTech_ = function loadTech_(techName, source) {
22264      var _this2 = this;
22265
22266      // Pause and remove current playback technology
22267      if (this.tech_) {
22268        this.unloadTech_();
22269      }
22270
22271      var titleTechName = toTitleCase(techName);
22272      var camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1);
22273
22274      // get rid of the HTML5 video tag as soon as we are using another tech
22275      if (titleTechName !== 'Html5' && this.tag) {
22276        Tech.getTech('Html5').disposeMediaElement(this.tag);
22277        this.tag.player = null;
22278        this.tag = null;
22279      }
22280
22281      this.techName_ = titleTechName;
22282
22283      // Turn off API access because we're loading a new tech that might load asynchronously
22284      this.isReady_ = false;
22285
22286      // if autoplay is a string we pass false to the tech
22287      // because the player is going to handle autoplay on `loadstart`
22288      var autoplay = typeof this.autoplay() === 'string' ? false : this.autoplay();
22289
22290      // Grab tech-specific options from player options and add source and parent element to use.
22291      var techOptions = {
22292        source: source,
22293        autoplay: autoplay,
22294        'nativeControlsForTouch': this.options_.nativeControlsForTouch,
22295        'playerId': this.id(),
22296        'techId': this.id() + '_' + titleTechName + '_api',
22297        'playsinline': this.options_.playsinline,
22298        'preload': this.options_.preload,
22299        'loop': this.options_.loop,
22300        'muted': this.options_.muted,
22301        'poster': this.poster(),
22302        'language': this.language(),
22303        'playerElIngest': this.playerElIngest_ || false,
22304        'vtt.js': this.options_['vtt.js'],
22305        'canOverridePoster': !!this.options_.techCanOverridePoster,
22306        'enableSourceset': this.options_.enableSourceset
22307      };
22308
22309      ALL.names.forEach(function (name$$1) {
22310        var props = ALL[name$$1];
22311
22312        techOptions[props.getterName] = _this2[props.privateName];
22313      });
22314
22315      assign(techOptions, this.options_[titleTechName]);
22316      assign(techOptions, this.options_[camelTechName]);
22317      assign(techOptions, this.options_[techName.toLowerCase()]);
22318
22319      if (this.tag) {
22320        techOptions.tag = this.tag;
22321      }
22322
22323      if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) {
22324        techOptions.startTime = this.cache_.currentTime;
22325      }
22326
22327      // Initialize tech instance
22328      var TechClass = Tech.getTech(techName);
22329
22330      if (!TechClass) {
22331        throw new Error('No Tech named \'' + titleTechName + '\' exists! \'' + titleTechName + '\' should be registered using videojs.registerTech()\'');
22332      }
22333
22334      this.tech_ = new TechClass(techOptions);
22335
22336      // player.triggerReady is always async, so don't need this to be async
22337      this.tech_.ready(bind(this, this.handleTechReady_), true);
22338
22339      textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech_);
22340
22341      // Listen to all HTML5-defined events and trigger them on the player
22342      TECH_EVENTS_RETRIGGER.forEach(function (event) {
22343        _this2.on(_this2.tech_, event, _this2['handleTech' + toTitleCase(event) + '_']);
22344      });
22345
22346      Object.keys(TECH_EVENTS_QUEUE).forEach(function (event) {
22347        _this2.on(_this2.tech_, event, function (eventObj) {
22348          if (_this2.tech_.playbackRate() === 0 && _this2.tech_.seeking()) {
22349            _this2.queuedCallbacks_.push({
22350              callback: _this2['handleTech' + TECH_EVENTS_QUEUE[event] + '_'].bind(_this2),
22351              event: eventObj
22352            });
22353            return;
22354          }
22355          _this2['handleTech' + TECH_EVENTS_QUEUE[event] + '_'](eventObj);
22356        });
22357      });
22358
22359      this.on(this.tech_, 'loadstart', this.handleTechLoadStart_);
22360      this.on(this.tech_, 'sourceset', this.handleTechSourceset_);
22361      this.on(this.tech_, 'waiting', this.handleTechWaiting_);
22362      this.on(this.tech_, 'ended', this.handleTechEnded_);
22363      this.on(this.tech_, 'seeking', this.handleTechSeeking_);
22364      this.on(this.tech_, 'play', this.handleTechPlay_);
22365      this.on(this.tech_, 'firstplay', this.handleTechFirstPlay_);
22366      this.on(this.tech_, 'pause', this.handleTechPause_);
22367      this.on(this.tech_, 'durationchange', this.handleTechDurationChange_);
22368      this.on(this.tech_, 'fullscreenchange', this.handleTechFullscreenChange_);
22369      this.on(this.tech_, 'error', this.handleTechError_);
22370      this.on(this.tech_, 'loadedmetadata', this.updateStyleEl_);
22371      this.on(this.tech_, 'posterchange', this.handleTechPosterChange_);
22372      this.on(this.tech_, 'textdata', this.handleTechTextData_);
22373      this.on(this.tech_, 'ratechange', this.handleTechRateChange_);
22374
22375      this.usingNativeControls(this.techGet_('controls'));
22376
22377      if (this.controls() && !this.usingNativeControls()) {
22378        this.addTechControlsListeners_();
22379      }
22380
22381      // Add the tech element in the DOM if it was not already there
22382      // Make sure to not insert the original video element if using Html5
22383      if (this.tech_.el().parentNode !== this.el() && (titleTechName !== 'Html5' || !this.tag)) {
22384        prependTo(this.tech_.el(), this.el());
22385      }
22386
22387      // Get rid of the original video tag reference after the first tech is loaded
22388      if (this.tag) {
22389        this.tag.player = null;
22390        this.tag = null;
22391      }
22392    };
22393
22394    /**
22395     * Unload and dispose of the current playback {@link Tech}.
22396     *
22397     * @private
22398     */
22399
22400
22401    Player.prototype.unloadTech_ = function unloadTech_() {
22402      var _this3 = this;
22403
22404      // Save the current text tracks so that we can reuse the same text tracks with the next tech
22405      ALL.names.forEach(function (name$$1) {
22406        var props = ALL[name$$1];
22407
22408        _this3[props.privateName] = _this3[props.getterName]();
22409      });
22410      this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_);
22411
22412      this.isReady_ = false;
22413
22414      this.tech_.dispose();
22415
22416      this.tech_ = false;
22417
22418      if (this.isPosterFromTech_) {
22419        this.poster_ = '';
22420        this.trigger('posterchange');
22421      }
22422
22423      this.isPosterFromTech_ = false;
22424    };
22425
22426    /**
22427     * Return a reference to the current {@link Tech}.
22428     * It will print a warning by default about the danger of using the tech directly
22429     * but any argument that is passed in will silence the warning.
22430     *
22431     * @param {*} [safety]
22432     *        Anything passed in to silence the warning
22433     *
22434     * @return {Tech}
22435     *         The Tech
22436     */
22437
22438
22439    Player.prototype.tech = function tech(safety) {
22440      if (safety === undefined) {
22441        log$1.warn(tsml(_templateObject$2));
22442      }
22443
22444      return this.tech_;
22445    };
22446
22447    /**
22448     * Set up click and touch listeners for the playback element
22449     *
22450     * - On desktops: a click on the video itself will toggle playback
22451     * - On mobile devices: a click on the video toggles controls
22452     *   which is done by toggling the user state between active and
22453     *   inactive
22454     * - A tap can signal that a user has become active or has become inactive
22455     *   e.g. a quick tap on an iPhone movie should reveal the controls. Another
22456     *   quick tap should hide them again (signaling the user is in an inactive
22457     *   viewing state)
22458     * - In addition to this, we still want the user to be considered inactive after
22459     *   a few seconds of inactivity.
22460     *
22461     * > Note: the only part of iOS interaction we can't mimic with this setup
22462     * is a touch and hold on the video element counting as activity in order to
22463     * keep the controls showing, but that shouldn't be an issue. A touch and hold
22464     * on any controls will still keep the user active
22465     *
22466     * @private
22467     */
22468
22469
22470    Player.prototype.addTechControlsListeners_ = function addTechControlsListeners_() {
22471      // Make sure to remove all the previous listeners in case we are called multiple times.
22472      this.removeTechControlsListeners_();
22473
22474      // Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do
22475      // trigger mousedown/up.
22476      // http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object
22477      // Any touch events are set to block the mousedown event from happening
22478      this.on(this.tech_, 'mousedown', this.handleTechClick_);
22479      this.on(this.tech_, 'dblclick', this.handleTechDoubleClick_);
22480
22481      // If the controls were hidden we don't want that to change without a tap event
22482      // so we'll check if the controls were already showing before reporting user
22483      // activity
22484      this.on(this.tech_, 'touchstart', this.handleTechTouchStart_);
22485      this.on(this.tech_, 'touchmove', this.handleTechTouchMove_);
22486      this.on(this.tech_, 'touchend', this.handleTechTouchEnd_);
22487
22488      // The tap listener needs to come after the touchend listener because the tap
22489      // listener cancels out any reportedUserActivity when setting userActive(false)
22490      this.on(this.tech_, 'tap', this.handleTechTap_);
22491    };
22492
22493    /**
22494     * Remove the listeners used for click and tap controls. This is needed for
22495     * toggling to controls disabled, where a tap/touch should do nothing.
22496     *
22497     * @private
22498     */
22499
22500
22501    Player.prototype.removeTechControlsListeners_ = function removeTechControlsListeners_() {
22502      // We don't want to just use `this.off()` because there might be other needed
22503      // listeners added by techs that extend this.
22504      this.off(this.tech_, 'tap', this.handleTechTap_);
22505      this.off(this.tech_, 'touchstart', this.handleTechTouchStart_);
22506      this.off(this.tech_, 'touchmove', this.handleTechTouchMove_);
22507      this.off(this.tech_, 'touchend', this.handleTechTouchEnd_);
22508      this.off(this.tech_, 'mousedown', this.handleTechClick_);
22509      this.off(this.tech_, 'dblclick', this.handleTechDoubleClick_);
22510    };
22511
22512    /**
22513     * Player waits for the tech to be ready
22514     *
22515     * @private
22516     */
22517
22518
22519    Player.prototype.handleTechReady_ = function handleTechReady_() {
22520      this.triggerReady();
22521
22522      // Keep the same volume as before
22523      if (this.cache_.volume) {
22524        this.techCall_('setVolume', this.cache_.volume);
22525      }
22526
22527      // Look if the tech found a higher resolution poster while loading
22528      this.handleTechPosterChange_();
22529
22530      // Update the duration if available
22531      this.handleTechDurationChange_();
22532    };
22533
22534    /**
22535     * Retrigger the `loadstart` event that was triggered by the {@link Tech}. This
22536     * function will also trigger {@link Player#firstplay} if it is the first loadstart
22537     * for a video.
22538     *
22539     * @fires Player#loadstart
22540     * @fires Player#firstplay
22541     * @listens Tech#loadstart
22542     * @private
22543     */
22544
22545
22546    Player.prototype.handleTechLoadStart_ = function handleTechLoadStart_() {
22547      // TODO: Update to use `emptied` event instead. See #1277.
22548
22549      this.removeClass('vjs-ended');
22550      this.removeClass('vjs-seeking');
22551
22552      // reset the error state
22553      this.error(null);
22554
22555      // If it's already playing we want to trigger a firstplay event now.
22556      // The firstplay event relies on both the play and loadstart events
22557      // which can happen in any order for a new source
22558      if (!this.paused()) {
22559        /**
22560         * Fired when the user agent begins looking for media data
22561         *
22562         * @event Player#loadstart
22563         * @type {EventTarget~Event}
22564         */
22565        this.trigger('loadstart');
22566        this.trigger('firstplay');
22567      } else {
22568        // reset the hasStarted state
22569        this.hasStarted(false);
22570        this.trigger('loadstart');
22571      }
22572
22573      // autoplay happens after loadstart for the browser,
22574      // so we mimic that behavior
22575      this.manualAutoplay_(this.autoplay());
22576    };
22577
22578    /**
22579     * Handle autoplay string values, rather than the typical boolean
22580     * values that should be handled by the tech. Note that this is not
22581     * part of any specification. Valid values and what they do can be
22582     * found on the autoplay getter at Player#autoplay()
22583     */
22584
22585
22586    Player.prototype.manualAutoplay_ = function manualAutoplay_(type) {
22587      var _this4 = this;
22588
22589      if (!this.tech_ || typeof type !== 'string') {
22590        return;
22591      }
22592
22593      var muted = function muted() {
22594        var previouslyMuted = _this4.muted();
22595
22596        _this4.muted(true);
22597
22598        var playPromise = _this4.play();
22599
22600        if (!playPromise || !playPromise.then || !playPromise.catch) {
22601          return;
22602        }
22603
22604        return playPromise.catch(function (e) {
22605          // restore old value of muted on failure
22606          _this4.muted(previouslyMuted);
22607        });
22608      };
22609
22610      var promise = void 0;
22611
22612      if (type === 'any') {
22613        promise = this.play();
22614
22615        if (promise && promise.then && promise.catch) {
22616          promise.catch(function () {
22617            return muted();
22618          });
22619        }
22620      } else if (type === 'muted') {
22621        promise = muted();
22622      } else {
22623        promise = this.play();
22624      }
22625
22626      if (!promise || !promise.then || !promise.catch) {
22627        return;
22628      }
22629
22630      return promise.then(function () {
22631        _this4.trigger({ type: 'autoplay-success', autoplay: type });
22632      }).catch(function (e) {
22633        _this4.trigger({ type: 'autoplay-failure', autoplay: type });
22634      });
22635    };
22636
22637    /**
22638     * Update the internal source caches so that we return the correct source from
22639     * `src()`, `currentSource()`, and `currentSources()`.
22640     *
22641     * > Note: `currentSources` will not be updated if the source that is passed in exists
22642     *         in the current `currentSources` cache.
22643     *
22644     *
22645     * @param {Tech~SourceObject} srcObj
22646     *        A string or object source to update our caches to.
22647     */
22648
22649
22650    Player.prototype.updateSourceCaches_ = function updateSourceCaches_() {
22651      var srcObj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
22652
22653
22654      var src = srcObj;
22655      var type = '';
22656
22657      if (typeof src !== 'string') {
22658        src = srcObj.src;
22659        type = srcObj.type;
22660      }
22661      // make sure all the caches are set to default values
22662      // to prevent null checking
22663      this.cache_.source = this.cache_.source || {};
22664      this.cache_.sources = this.cache_.sources || [];
22665
22666      // try to get the type of the src that was passed in
22667      if (src && !type) {
22668        type = findMimetype(this, src);
22669      }
22670
22671      // update `currentSource` cache always
22672      this.cache_.source = mergeOptions({}, srcObj, { src: src, type: type });
22673
22674      var matchingSources = this.cache_.sources.filter(function (s) {
22675        return s.src && s.src === src;
22676      });
22677      var sourceElSources = [];
22678      var sourceEls = this.$$('source');
22679      var matchingSourceEls = [];
22680
22681      for (var i = 0; i < sourceEls.length; i++) {
22682        var sourceObj = getAttributes(sourceEls[i]);
22683
22684        sourceElSources.push(sourceObj);
22685
22686        if (sourceObj.src && sourceObj.src === src) {
22687          matchingSourceEls.push(sourceObj.src);
22688        }
22689      }
22690
22691      // if we have matching source els but not matching sources
22692      // the current source cache is not up to date
22693      if (matchingSourceEls.length && !matchingSources.length) {
22694        this.cache_.sources = sourceElSources;
22695        // if we don't have matching source or source els set the
22696        // sources cache to the `currentSource` cache
22697      } else if (!matchingSources.length) {
22698        this.cache_.sources = [this.cache_.source];
22699      }
22700
22701      // update the tech `src` cache
22702      this.cache_.src = src;
22703    };
22704
22705    /**
22706     * *EXPERIMENTAL* Fired when the source is set or changed on the {@link Tech}
22707     * causing the media element to reload.
22708     *
22709     * It will fire for the initial source and each subsequent source.
22710     * This event is a custom event from Video.js and is triggered by the {@link Tech}.
22711     *
22712     * The event object for this event contains a `src` property that will contain the source
22713     * that was available when the event was triggered. This is generally only necessary if Video.js
22714     * is switching techs while the source was being changed.
22715     *
22716     * It is also fired when `load` is called on the player (or media element)
22717     * because the {@link https://html.spec.whatwg.org/multipage/media.html#dom-media-load|specification for `load`}
22718     * says that the resource selection algorithm needs to be aborted and restarted.
22719     * In this case, it is very likely that the `src` property will be set to the
22720     * empty string `""` to indicate we do not know what the source will be but
22721     * that it is changing.
22722     *
22723     * *This event is currently still experimental and may change in minor releases.*
22724     * __To use this, pass `enableSourceset` option to the player.__
22725     *
22726     * @event Player#sourceset
22727     * @type {EventTarget~Event}
22728     * @prop {string} src
22729     *                The source url available when the `sourceset` was triggered.
22730     *                It will be an empty string if we cannot know what the source is
22731     *                but know that the source will change.
22732     */
22733    /**
22734     * Retrigger the `sourceset` event that was triggered by the {@link Tech}.
22735     *
22736     * @fires Player#sourceset
22737     * @listens Tech#sourceset
22738     * @private
22739     */
22740
22741
22742    Player.prototype.handleTechSourceset_ = function handleTechSourceset_(event) {
22743      var _this5 = this;
22744
22745      // only update the source cache when the source
22746      // was not updated using the player api
22747      if (!this.changingSrc_) {
22748        // update the source to the intial source right away
22749        // in some cases this will be empty string
22750        this.updateSourceCaches_(event.src);
22751
22752        // if the `sourceset` `src` was an empty string
22753        // wait for a `loadstart` to update the cache to `currentSrc`.
22754        // If a sourceset happens before a `loadstart`, we reset the state
22755        // as this function will be called again.
22756        if (!event.src) {
22757          var updateCache = function updateCache(e) {
22758            if (e.type !== 'sourceset') {
22759              _this5.updateSourceCaches_(_this5.techGet_('currentSrc'));
22760            }
22761
22762            _this5.tech_.off(['sourceset', 'loadstart'], updateCache);
22763          };
22764
22765          this.tech_.one(['sourceset', 'loadstart'], updateCache);
22766        }
22767      }
22768
22769      this.trigger({
22770        src: event.src,
22771        type: 'sourceset'
22772      });
22773    };
22774
22775    /**
22776     * Add/remove the vjs-has-started class
22777     *
22778     * @fires Player#firstplay
22779     *
22780     * @param {boolean} request
22781     *        - true: adds the class
22782     *        - false: remove the class
22783     *
22784     * @return {boolean}
22785     *         the boolean value of hasStarted_
22786     */
22787
22788
22789    Player.prototype.hasStarted = function hasStarted(request) {
22790      if (request === undefined) {
22791        // act as getter, if we have no request to change
22792        return this.hasStarted_;
22793      }
22794
22795      if (request === this.hasStarted_) {
22796        return;
22797      }
22798
22799      this.hasStarted_ = request;
22800
22801      if (this.hasStarted_) {
22802        this.addClass('vjs-has-started');
22803        this.trigger('firstplay');
22804      } else {
22805        this.removeClass('vjs-has-started');
22806      }
22807    };
22808
22809    /**
22810     * Fired whenever the media begins or resumes playback
22811     *
22812     * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play}
22813     * @fires Player#play
22814     * @listens Tech#play
22815     * @private
22816     */
22817
22818
22819    Player.prototype.handleTechPlay_ = function handleTechPlay_() {
22820      this.removeClass('vjs-ended');
22821      this.removeClass('vjs-paused');
22822      this.addClass('vjs-playing');
22823
22824      // hide the poster when the user hits play
22825      this.hasStarted(true);
22826      /**
22827       * Triggered whenever an {@link Tech#play} event happens. Indicates that
22828       * playback has started or resumed.
22829       *
22830       * @event Player#play
22831       * @type {EventTarget~Event}
22832       */
22833      this.trigger('play');
22834    };
22835
22836    /**
22837     * Retrigger the `ratechange` event that was triggered by the {@link Tech}.
22838     *
22839     * If there were any events queued while the playback rate was zero, fire
22840     * those events now.
22841     *
22842     * @private
22843     * @method Player#handleTechRateChange_
22844     * @fires Player#ratechange
22845     * @listens Tech#ratechange
22846     */
22847
22848
22849    Player.prototype.handleTechRateChange_ = function handleTechRateChange_() {
22850      if (this.tech_.playbackRate() > 0 && this.cache_.lastPlaybackRate === 0) {
22851        this.queuedCallbacks_.forEach(function (queued) {
22852          return queued.callback(queued.event);
22853        });
22854        this.queuedCallbacks_ = [];
22855      }
22856      this.cache_.lastPlaybackRate = this.tech_.playbackRate();
22857      /**
22858       * Fires when the playing speed of the audio/video is changed
22859       *
22860       * @event Player#ratechange
22861       * @type {event}
22862       */
22863      this.trigger('ratechange');
22864    };
22865
22866    /**
22867     * Retrigger the `waiting` event that was triggered by the {@link Tech}.
22868     *
22869     * @fires Player#waiting
22870     * @listens Tech#waiting
22871     * @private
22872     */
22873
22874
22875    Player.prototype.handleTechWaiting_ = function handleTechWaiting_() {
22876      var _this6 = this;
22877
22878      this.addClass('vjs-waiting');
22879      /**
22880       * A readyState change on the DOM element has caused playback to stop.
22881       *
22882       * @event Player#waiting
22883       * @type {EventTarget~Event}
22884       */
22885      this.trigger('waiting');
22886      this.one('timeupdate', function () {
22887        return _this6.removeClass('vjs-waiting');
22888      });
22889    };
22890
22891    /**
22892     * Retrigger the `canplay` event that was triggered by the {@link Tech}.
22893     * > Note: This is not consistent between browsers. See #1351
22894     *
22895     * @fires Player#canplay
22896     * @listens Tech#canplay
22897     * @private
22898     */
22899
22900
22901    Player.prototype.handleTechCanPlay_ = function handleTechCanPlay_() {
22902      this.removeClass('vjs-waiting');
22903      /**
22904       * The media has a readyState of HAVE_FUTURE_DATA or greater.
22905       *
22906       * @event Player#canplay
22907       * @type {EventTarget~Event}
22908       */
22909      this.trigger('canplay');
22910    };
22911
22912    /**
22913     * Retrigger the `canplaythrough` event that was triggered by the {@link Tech}.
22914     *
22915     * @fires Player#canplaythrough
22916     * @listens Tech#canplaythrough
22917     * @private
22918     */
22919
22920
22921    Player.prototype.handleTechCanPlayThrough_ = function handleTechCanPlayThrough_() {
22922      this.removeClass('vjs-waiting');
22923      /**
22924       * The media has a readyState of HAVE_ENOUGH_DATA or greater. This means that the
22925       * entire media file can be played without buffering.
22926       *
22927       * @event Player#canplaythrough
22928       * @type {EventTarget~Event}
22929       */
22930      this.trigger('canplaythrough');
22931    };
22932
22933    /**
22934     * Retrigger the `playing` event that was triggered by the {@link Tech}.
22935     *
22936     * @fires Player#playing
22937     * @listens Tech#playing
22938     * @private
22939     */
22940
22941
22942    Player.prototype.handleTechPlaying_ = function handleTechPlaying_() {
22943      this.removeClass('vjs-waiting');
22944      /**
22945       * The media is no longer blocked from playback, and has started playing.
22946       *
22947       * @event Player#playing
22948       * @type {EventTarget~Event}
22949       */
22950      this.trigger('playing');
22951    };
22952
22953    /**
22954     * Retrigger the `seeking` event that was triggered by the {@link Tech}.
22955     *
22956     * @fires Player#seeking
22957     * @listens Tech#seeking
22958     * @private
22959     */
22960
22961
22962    Player.prototype.handleTechSeeking_ = function handleTechSeeking_() {
22963      this.addClass('vjs-seeking');
22964      /**
22965       * Fired whenever the player is jumping to a new time
22966       *
22967       * @event Player#seeking
22968       * @type {EventTarget~Event}
22969       */
22970      this.trigger('seeking');
22971    };
22972
22973    /**
22974     * Retrigger the `seeked` event that was triggered by the {@link Tech}.
22975     *
22976     * @fires Player#seeked
22977     * @listens Tech#seeked
22978     * @private
22979     */
22980
22981
22982    Player.prototype.handleTechSeeked_ = function handleTechSeeked_() {
22983      this.removeClass('vjs-seeking');
22984      /**
22985       * Fired when the player has finished jumping to a new time
22986       *
22987       * @event Player#seeked
22988       * @type {EventTarget~Event}
22989       */
22990      this.trigger('seeked');
22991    };
22992
22993    /**
22994     * Retrigger the `firstplay` event that was triggered by the {@link Tech}.
22995     *
22996     * @fires Player#firstplay
22997     * @listens Tech#firstplay
22998     * @deprecated As of 6.0 firstplay event is deprecated.
22999     * @deprecated As of 6.0 passing the `starttime` option to the player and the firstplay event are deprecated.
23000     * @private
23001     */
23002
23003
23004    Player.prototype.handleTechFirstPlay_ = function handleTechFirstPlay_() {
23005      // If the first starttime attribute is specified
23006      // then we will start at the given offset in seconds
23007      if (this.options_.starttime) {
23008        log$1.warn('Passing the `starttime` option to the player will be deprecated in 6.0');
23009        this.currentTime(this.options_.starttime);
23010      }
23011
23012      this.addClass('vjs-has-started');
23013      /**
23014       * Fired the first time a video is played. Not part of the HLS spec, and this is
23015       * probably not the best implementation yet, so use sparingly. If you don't have a
23016       * reason to prevent playback, use `myPlayer.one('play');` instead.
23017       *
23018       * @event Player#firstplay
23019       * @deprecated As of 6.0 firstplay event is deprecated.
23020       * @type {EventTarget~Event}
23021       */
23022      this.trigger('firstplay');
23023    };
23024
23025    /**
23026     * Retrigger the `pause` event that was triggered by the {@link Tech}.
23027     *
23028     * @fires Player#pause
23029     * @listens Tech#pause
23030     * @private
23031     */
23032
23033
23034    Player.prototype.handleTechPause_ = function handleTechPause_() {
23035      this.removeClass('vjs-playing');
23036      this.addClass('vjs-paused');
23037      /**
23038       * Fired whenever the media has been paused
23039       *
23040       * @event Player#pause
23041       * @type {EventTarget~Event}
23042       */
23043      this.trigger('pause');
23044    };
23045
23046    /**
23047     * Retrigger the `ended` event that was triggered by the {@link Tech}.
23048     *
23049     * @fires Player#ended
23050     * @listens Tech#ended
23051     * @private
23052     */
23053
23054
23055    Player.prototype.handleTechEnded_ = function handleTechEnded_() {
23056      this.addClass('vjs-ended');
23057      if (this.options_.loop) {
23058        this.currentTime(0);
23059        this.play();
23060      } else if (!this.paused()) {
23061        this.pause();
23062      }
23063
23064      /**
23065       * Fired when the end of the media resource is reached (currentTime == duration)
23066       *
23067       * @event Player#ended
23068       * @type {EventTarget~Event}
23069       */
23070      this.trigger('ended');
23071    };
23072
23073    /**
23074     * Fired when the duration of the media resource is first known or changed
23075     *
23076     * @listens Tech#durationchange
23077     * @private
23078     */
23079
23080
23081    Player.prototype.handleTechDurationChange_ = function handleTechDurationChange_() {
23082      this.duration(this.techGet_('duration'));
23083    };
23084
23085    /**
23086     * Handle a click on the media element to play/pause
23087     *
23088     * @param {EventTarget~Event} event
23089     *        the event that caused this function to trigger
23090     *
23091     * @listens Tech#mousedown
23092     * @private
23093     */
23094
23095
23096    Player.prototype.handleTechClick_ = function handleTechClick_(event) {
23097      if (!isSingleLeftClick(event)) {
23098        return;
23099      }
23100
23101      // When controls are disabled a click should not toggle playback because
23102      // the click is considered a control
23103      if (!this.controls_) {
23104        return;
23105      }
23106
23107      if (this.paused()) {
23108        silencePromise(this.play());
23109      } else {
23110        this.pause();
23111      }
23112    };
23113
23114    /**
23115     * Handle a double-click on the media element to enter/exit fullscreen
23116     *
23117     * @param {EventTarget~Event} event
23118     *        the event that caused this function to trigger
23119     *
23120     * @listens Tech#dblclick
23121     * @private
23122     */
23123
23124
23125    Player.prototype.handleTechDoubleClick_ = function handleTechDoubleClick_(event) {
23126      if (!this.controls_) {
23127        return;
23128      }
23129
23130      // we do not want to toggle fullscreen state
23131      // when double-clicking inside a control bar or a modal
23132      var inAllowedEls = Array.prototype.some.call(this.$$('.vjs-control-bar, .vjs-modal-dialog'), function (el) {
23133        return el.contains(event.target);
23134      });
23135
23136      if (!inAllowedEls) {
23137        if (this.isFullscreen()) {
23138          this.exitFullscreen();
23139        } else {
23140          this.requestFullscreen();
23141        }
23142      }
23143    };
23144
23145    /**
23146     * Handle a tap on the media element. It will toggle the user
23147     * activity state, which hides and shows the controls.
23148     *
23149     * @listens Tech#tap
23150     * @private
23151     */
23152
23153
23154    Player.prototype.handleTechTap_ = function handleTechTap_() {
23155      this.userActive(!this.userActive());
23156    };
23157
23158    /**
23159     * Handle touch to start
23160     *
23161     * @listens Tech#touchstart
23162     * @private
23163     */
23164
23165
23166    Player.prototype.handleTechTouchStart_ = function handleTechTouchStart_() {
23167      this.userWasActive = this.userActive();
23168    };
23169
23170    /**
23171     * Handle touch to move
23172     *
23173     * @listens Tech#touchmove
23174     * @private
23175     */
23176
23177
23178    Player.prototype.handleTechTouchMove_ = function handleTechTouchMove_() {
23179      if (this.userWasActive) {
23180        this.reportUserActivity();
23181      }
23182    };
23183
23184    /**
23185     * Handle touch to end
23186     *
23187     * @param {EventTarget~Event} event
23188     *        the touchend event that triggered
23189     *        this function
23190     *
23191     * @listens Tech#touchend
23192     * @private
23193     */
23194
23195
23196    Player.prototype.handleTechTouchEnd_ = function handleTechTouchEnd_(event) {
23197      // Stop the mouse events from also happening
23198      event.preventDefault();
23199    };
23200
23201    /**
23202     * Fired when the player switches in or out of fullscreen mode
23203     *
23204     * @private
23205     * @listens Player#fullscreenchange
23206     */
23207
23208
23209    Player.prototype.handleFullscreenChange_ = function handleFullscreenChange_() {
23210      if (this.isFullscreen()) {
23211        this.addClass('vjs-fullscreen');
23212      } else {
23213        this.removeClass('vjs-fullscreen');
23214      }
23215    };
23216
23217    /**
23218     * native click events on the SWF aren't triggered on IE11, Win8.1RT
23219     * use stageclick events triggered from inside the SWF instead
23220     *
23221     * @private
23222     * @listens stageclick
23223     */
23224
23225
23226    Player.prototype.handleStageClick_ = function handleStageClick_() {
23227      this.reportUserActivity();
23228    };
23229
23230    /**
23231     * Handle Tech Fullscreen Change
23232     *
23233     * @param {EventTarget~Event} event
23234     *        the fullscreenchange event that triggered this function
23235     *
23236     * @param {Object} data
23237     *        the data that was sent with the event
23238     *
23239     * @private
23240     * @listens Tech#fullscreenchange
23241     * @fires Player#fullscreenchange
23242     */
23243
23244
23245    Player.prototype.handleTechFullscreenChange_ = function handleTechFullscreenChange_(event, data) {
23246      if (data) {
23247        this.isFullscreen(data.isFullscreen);
23248      }
23249      /**
23250       * Fired when going in and out of fullscreen.
23251       *
23252       * @event Player#fullscreenchange
23253       * @type {EventTarget~Event}
23254       */
23255      this.trigger('fullscreenchange');
23256    };
23257
23258    /**
23259     * Fires when an error occurred during the loading of an audio/video.
23260     *
23261     * @private
23262     * @listens Tech#error
23263     */
23264
23265
23266    Player.prototype.handleTechError_ = function handleTechError_() {
23267      var error = this.tech_.error();
23268
23269      this.error(error);
23270    };
23271
23272    /**
23273     * Retrigger the `textdata` event that was triggered by the {@link Tech}.
23274     *
23275     * @fires Player#textdata
23276     * @listens Tech#textdata
23277     * @private
23278     */
23279
23280
23281    Player.prototype.handleTechTextData_ = function handleTechTextData_() {
23282      var data = null;
23283
23284      if (arguments.length > 1) {
23285        data = arguments[1];
23286      }
23287
23288      /**
23289       * Fires when we get a textdata event from tech
23290       *
23291       * @event Player#textdata
23292       * @type {EventTarget~Event}
23293       */
23294      this.trigger('textdata', data);
23295    };
23296
23297    /**
23298     * Get object for cached values.
23299     *
23300     * @return {Object}
23301     *         get the current object cache
23302     */
23303
23304
23305    Player.prototype.getCache = function getCache() {
23306      return this.cache_;
23307    };
23308
23309    /**
23310     * Pass values to the playback tech
23311     *
23312     * @param {string} [method]
23313     *        the method to call
23314     *
23315     * @param {Object} arg
23316     *        the argument to pass
23317     *
23318     * @private
23319     */
23320
23321
23322    Player.prototype.techCall_ = function techCall_(method, arg) {
23323      // If it's not ready yet, call method when it is
23324
23325      this.ready(function () {
23326        if (method in allowedSetters) {
23327          return set$1(this.middleware_, this.tech_, method, arg);
23328        } else if (method in allowedMediators) {
23329          return mediate(this.middleware_, this.tech_, method, arg);
23330        }
23331
23332        try {
23333          if (this.tech_) {
23334            this.tech_[method](arg);
23335          }
23336        } catch (e) {
23337          log$1(e);
23338          throw e;
23339        }
23340      }, true);
23341    };
23342
23343    /**
23344     * Get calls can't wait for the tech, and sometimes don't need to.
23345     *
23346     * @param {string} method
23347     *        Tech method
23348     *
23349     * @return {Function|undefined}
23350     *         the method or undefined
23351     *
23352     * @private
23353     */
23354
23355
23356    Player.prototype.techGet_ = function techGet_(method) {
23357      if (!this.tech_ || !this.tech_.isReady_) {
23358        return;
23359      }
23360
23361      if (method in allowedGetters) {
23362        return get$1(this.middleware_, this.tech_, method);
23363      } else if (method in allowedMediators) {
23364        return mediate(this.middleware_, this.tech_, method);
23365      }
23366
23367      // Flash likes to die and reload when you hide or reposition it.
23368      // In these cases the object methods go away and we get errors.
23369      // When that happens we'll catch the errors and inform tech that it's not ready any more.
23370      try {
23371        return this.tech_[method]();
23372      } catch (e) {
23373
23374        // When building additional tech libs, an expected method may not be defined yet
23375        if (this.tech_[method] === undefined) {
23376          log$1('Video.js: ' + method + ' method not defined for ' + this.techName_ + ' playback technology.', e);
23377          throw e;
23378        }
23379
23380        // When a method isn't available on the object it throws a TypeError
23381        if (e.name === 'TypeError') {
23382          log$1('Video.js: ' + method + ' unavailable on ' + this.techName_ + ' playback technology element.', e);
23383          this.tech_.isReady_ = false;
23384          throw e;
23385        }
23386
23387        // If error unknown, just log and throw
23388        log$1(e);
23389        throw e;
23390      }
23391    };
23392
23393    /**
23394     * Attempt to begin playback at the first opportunity.
23395     *
23396     * @return {Promise|undefined}
23397     *         Returns a `Promise` only if the browser returns one and the player
23398     *         is ready to begin playback. For some browsers and all non-ready
23399     *         situations, this will return `undefined`.
23400     */
23401
23402
23403    Player.prototype.play = function play() {
23404      var _this7 = this;
23405
23406      // If this is called while we have a play queued up on a loadstart, remove
23407      // that listener to avoid getting in a potentially bad state.
23408      if (this.playOnLoadstart_) {
23409        this.off('loadstart', this.playOnLoadstart_);
23410      }
23411
23412      // If the player/tech is not ready, queue up another call to `play()` for
23413      // when it is. This will loop back into this method for another attempt at
23414      // playback when the tech is ready.
23415      if (!this.isReady_) {
23416
23417        // Bail out if we're already waiting for `ready`!
23418        if (this.playWaitingForReady_) {
23419          return;
23420        }
23421
23422        this.playWaitingForReady_ = true;
23423        this.ready(function () {
23424          _this7.playWaitingForReady_ = false;
23425          silencePromise(_this7.play());
23426        });
23427
23428        // If the player/tech is ready and we have a source, we can attempt playback.
23429      } else if (!this.changingSrc_ && (this.src() || this.currentSrc())) {
23430        return this.techGet_('play');
23431
23432        // If the tech is ready, but we do not have a source, we'll need to wait
23433        // for both the `ready` and a `loadstart` when the source is finally
23434        // resolved by middleware and set on the player.
23435        //
23436        // This can happen if `play()` is called while changing sources or before
23437        // one has been set on the player.
23438      } else {
23439
23440        this.playOnLoadstart_ = function () {
23441          _this7.playOnLoadstart_ = null;
23442          silencePromise(_this7.play());
23443        };
23444
23445        this.one('loadstart', this.playOnLoadstart_);
23446      }
23447    };
23448
23449    /**
23450     * Pause the video playback
23451     *
23452     * @return {Player}
23453     *         A reference to the player object this function was called on
23454     */
23455
23456
23457    Player.prototype.pause = function pause() {
23458      this.techCall_('pause');
23459    };
23460
23461    /**
23462     * Check if the player is paused or has yet to play
23463     *
23464     * @return {boolean}
23465     *         - false: if the media is currently playing
23466     *         - true: if media is not currently playing
23467     */
23468
23469
23470    Player.prototype.paused = function paused() {
23471      // The initial state of paused should be true (in Safari it's actually false)
23472      return this.techGet_('paused') === false ? false : true;
23473    };
23474
23475    /**
23476     * Get a TimeRange object representing the current ranges of time that the user
23477     * has played.
23478     *
23479     * @return {TimeRange}
23480     *         A time range object that represents all the increments of time that have
23481     *         been played.
23482     */
23483
23484
23485    Player.prototype.played = function played() {
23486      return this.techGet_('played') || createTimeRanges(0, 0);
23487    };
23488
23489    /**
23490     * Returns whether or not the user is "scrubbing". Scrubbing is
23491     * when the user has clicked the progress bar handle and is
23492     * dragging it along the progress bar.
23493     *
23494     * @param {boolean} [isScrubbing]
23495     *        whether the user is or is not scrubbing
23496     *
23497     * @return {boolean}
23498     *         The value of scrubbing when getting
23499     */
23500
23501
23502    Player.prototype.scrubbing = function scrubbing(isScrubbing) {
23503      if (typeof isScrubbing === 'undefined') {
23504        return this.scrubbing_;
23505      }
23506      this.scrubbing_ = !!isScrubbing;
23507
23508      if (isScrubbing) {
23509        this.addClass('vjs-scrubbing');
23510      } else {
23511        this.removeClass('vjs-scrubbing');
23512      }
23513    };
23514
23515    /**
23516     * Get or set the current time (in seconds)
23517     *
23518     * @param {number|string} [seconds]
23519     *        The time to seek to in seconds
23520     *
23521     * @return {number}
23522     *         - the current time in seconds when getting
23523     */
23524
23525
23526    Player.prototype.currentTime = function currentTime(seconds) {
23527      if (typeof seconds !== 'undefined') {
23528        if (seconds < 0) {
23529          seconds = 0;
23530        }
23531        this.techCall_('setCurrentTime', seconds);
23532        return;
23533      }
23534
23535      // cache last currentTime and return. default to 0 seconds
23536      //
23537      // Caching the currentTime is meant to prevent a massive amount of reads on the tech's
23538      // currentTime when scrubbing, but may not provide much performance benefit afterall.
23539      // Should be tested. Also something has to read the actual current time or the cache will
23540      // never get updated.
23541      this.cache_.currentTime = this.techGet_('currentTime') || 0;
23542      return this.cache_.currentTime;
23543    };
23544
23545    /**
23546     * Normally gets the length in time of the video in seconds;
23547     * in all but the rarest use cases an argument will NOT be passed to the method
23548     *
23549     * > **NOTE**: The video must have started loading before the duration can be
23550     * known, and in the case of Flash, may not be known until the video starts
23551     * playing.
23552     *
23553     * @fires Player#durationchange
23554     *
23555     * @param {number} [seconds]
23556     *        The duration of the video to set in seconds
23557     *
23558     * @return {number}
23559     *         - The duration of the video in seconds when getting
23560     */
23561
23562
23563    Player.prototype.duration = function duration(seconds) {
23564      if (seconds === undefined) {
23565        // return NaN if the duration is not known
23566        return this.cache_.duration !== undefined ? this.cache_.duration : NaN;
23567      }
23568
23569      seconds = parseFloat(seconds);
23570
23571      // Standardize on Infinity for signaling video is live
23572      if (seconds < 0) {
23573        seconds = Infinity;
23574      }
23575
23576      if (seconds !== this.cache_.duration) {
23577        // Cache the last set value for optimized scrubbing (esp. Flash)
23578        this.cache_.duration = seconds;
23579
23580        if (seconds === Infinity) {
23581          this.addClass('vjs-live');
23582        } else {
23583          this.removeClass('vjs-live');
23584        }
23585        /**
23586         * @event Player#durationchange
23587         * @type {EventTarget~Event}
23588         */
23589        this.trigger('durationchange');
23590      }
23591    };
23592
23593    /**
23594     * Calculates how much time is left in the video. Not part
23595     * of the native video API.
23596     *
23597     * @return {number}
23598     *         The time remaining in seconds
23599     */
23600
23601
23602    Player.prototype.remainingTime = function remainingTime() {
23603      return this.duration() - this.currentTime();
23604    };
23605
23606    /**
23607     * A remaining time function that is intented to be used when
23608     * the time is to be displayed directly to the user.
23609     *
23610     * @return {number}
23611     *         The rounded time remaining in seconds
23612     */
23613
23614
23615    Player.prototype.remainingTimeDisplay = function remainingTimeDisplay() {
23616      return Math.floor(this.duration()) - Math.floor(this.currentTime());
23617    };
23618
23619    //
23620    // Kind of like an array of portions of the video that have been downloaded.
23621
23622    /**
23623     * Get a TimeRange object with an array of the times of the video
23624     * that have been downloaded. If you just want the percent of the
23625     * video that's been downloaded, use bufferedPercent.
23626     *
23627     * @see [Buffered Spec]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered}
23628     *
23629     * @return {TimeRange}
23630     *         A mock TimeRange object (following HTML spec)
23631     */
23632
23633
23634    Player.prototype.buffered = function buffered() {
23635      var buffered = this.techGet_('buffered');
23636
23637      if (!buffered || !buffered.length) {
23638        buffered = createTimeRanges(0, 0);
23639      }
23640
23641      return buffered;
23642    };
23643
23644    /**
23645     * Get the percent (as a decimal) of the video that's been downloaded.
23646     * This method is not a part of the native HTML video API.
23647     *
23648     * @return {number}
23649     *         A decimal between 0 and 1 representing the percent
23650     *         that is buffered 0 being 0% and 1 being 100%
23651     */
23652
23653
23654    Player.prototype.bufferedPercent = function bufferedPercent$$1() {
23655      return bufferedPercent(this.buffered(), this.duration());
23656    };
23657
23658    /**
23659     * Get the ending time of the last buffered time range
23660     * This is used in the progress bar to encapsulate all time ranges.
23661     *
23662     * @return {number}
23663     *         The end of the last buffered time range
23664     */
23665
23666
23667    Player.prototype.bufferedEnd = function bufferedEnd() {
23668      var buffered = this.buffered();
23669      var duration = this.duration();
23670      var end = buffered.end(buffered.length - 1);
23671
23672      if (end > duration) {
23673        end = duration;
23674      }
23675
23676      return end;
23677    };
23678
23679    /**
23680     * Get or set the current volume of the media
23681     *
23682     * @param  {number} [percentAsDecimal]
23683     *         The new volume as a decimal percent:
23684     *         - 0 is muted/0%/off
23685     *         - 1.0 is 100%/full
23686     *         - 0.5 is half volume or 50%
23687     *
23688     * @return {number}
23689     *         The current volume as a percent when getting
23690     */
23691
23692
23693    Player.prototype.volume = function volume(percentAsDecimal) {
23694      var vol = void 0;
23695
23696      if (percentAsDecimal !== undefined) {
23697        // Force value to between 0 and 1
23698        vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal)));
23699        this.cache_.volume = vol;
23700        this.techCall_('setVolume', vol);
23701
23702        if (vol > 0) {
23703          this.lastVolume_(vol);
23704        }
23705
23706        return;
23707      }
23708
23709      // Default to 1 when returning current volume.
23710      vol = parseFloat(this.techGet_('volume'));
23711      return isNaN(vol) ? 1 : vol;
23712    };
23713
23714    /**
23715     * Get the current muted state, or turn mute on or off
23716     *
23717     * @param {boolean} [muted]
23718     *        - true to mute
23719     *        - false to unmute
23720     *
23721     * @return {boolean}
23722     *         - true if mute is on and getting
23723     *         - false if mute is off and getting
23724     */
23725
23726
23727    Player.prototype.muted = function muted(_muted) {
23728      if (_muted !== undefined) {
23729        this.techCall_('setMuted', _muted);
23730        return;
23731      }
23732      return this.techGet_('muted') || false;
23733    };
23734
23735    /**
23736     * Get the current defaultMuted state, or turn defaultMuted on or off. defaultMuted
23737     * indicates the state of muted on initial playback.
23738     *
23739     * ```js
23740     *   var myPlayer = videojs('some-player-id');
23741     *
23742     *   myPlayer.src("http://www.example.com/path/to/video.mp4");
23743     *
23744     *   // get, should be false
23745     *   console.log(myPlayer.defaultMuted());
23746     *   // set to true
23747     *   myPlayer.defaultMuted(true);
23748     *   // get should be true
23749     *   console.log(myPlayer.defaultMuted());
23750     * ```
23751     *
23752     * @param {boolean} [defaultMuted]
23753     *        - true to mute
23754     *        - false to unmute
23755     *
23756     * @return {boolean|Player}
23757     *         - true if defaultMuted is on and getting
23758     *         - false if defaultMuted is off and getting
23759     *         - A reference to the current player when setting
23760     */
23761
23762
23763    Player.prototype.defaultMuted = function defaultMuted(_defaultMuted) {
23764      if (_defaultMuted !== undefined) {
23765        return this.techCall_('setDefaultMuted', _defaultMuted);
23766      }
23767      return this.techGet_('defaultMuted') || false;
23768    };
23769
23770    /**
23771     * Get the last volume, or set it
23772     *
23773     * @param  {number} [percentAsDecimal]
23774     *         The new last volume as a decimal percent:
23775     *         - 0 is muted/0%/off
23776     *         - 1.0 is 100%/full
23777     *         - 0.5 is half volume or 50%
23778     *
23779     * @return {number}
23780     *         the current value of lastVolume as a percent when getting
23781     *
23782     * @private
23783     */
23784
23785
23786    Player.prototype.lastVolume_ = function lastVolume_(percentAsDecimal) {
23787      if (percentAsDecimal !== undefined && percentAsDecimal !== 0) {
23788        this.cache_.lastVolume = percentAsDecimal;
23789        return;
23790      }
23791      return this.cache_.lastVolume;
23792    };
23793
23794    /**
23795     * Check if current tech can support native fullscreen
23796     * (e.g. with built in controls like iOS, so not our flash swf)
23797     *
23798     * @return {boolean}
23799     *         if native fullscreen is supported
23800     */
23801
23802
23803    Player.prototype.supportsFullScreen = function supportsFullScreen() {
23804      return this.techGet_('supportsFullScreen') || false;
23805    };
23806
23807    /**
23808     * Check if the player is in fullscreen mode or tell the player that it
23809     * is or is not in fullscreen mode.
23810     *
23811     * > NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official
23812     * property and instead document.fullscreenElement is used. But isFullscreen is
23813     * still a valuable property for internal player workings.
23814     *
23815     * @param  {boolean} [isFS]
23816     *         Set the players current fullscreen state
23817     *
23818     * @return {boolean}
23819     *         - true if fullscreen is on and getting
23820     *         - false if fullscreen is off and getting
23821     */
23822
23823
23824    Player.prototype.isFullscreen = function isFullscreen(isFS) {
23825      if (isFS !== undefined) {
23826        this.isFullscreen_ = !!isFS;
23827        return;
23828      }
23829      return !!this.isFullscreen_;
23830    };
23831
23832    /**
23833     * Increase the size of the video to full screen
23834     * In some browsers, full screen is not supported natively, so it enters
23835     * "full window mode", where the video fills the browser window.
23836     * In browsers and devices that support native full screen, sometimes the
23837     * browser's default controls will be shown, and not the Video.js custom skin.
23838     * This includes most mobile devices (iOS, Android) and older versions of
23839     * Safari.
23840     *
23841     * @fires Player#fullscreenchange
23842     */
23843
23844
23845    Player.prototype.requestFullscreen = function requestFullscreen() {
23846      var fsApi = FullscreenApi;
23847
23848      this.isFullscreen(true);
23849
23850      if (fsApi.requestFullscreen) {
23851        // the browser supports going fullscreen at the element level so we can
23852        // take the controls fullscreen as well as the video
23853
23854        // Trigger fullscreenchange event after change
23855        // We have to specifically add this each time, and remove
23856        // when canceling fullscreen. Otherwise if there's multiple
23857        // players on a page, they would all be reacting to the same fullscreen
23858        // events
23859        on(document_1, fsApi.fullscreenchange, bind(this, function documentFullscreenChange(e) {
23860          this.isFullscreen(document_1[fsApi.fullscreenElement]);
23861
23862          // If cancelling fullscreen, remove event listener.
23863          if (this.isFullscreen() === false) {
23864            off(document_1, fsApi.fullscreenchange, documentFullscreenChange);
23865          }
23866          /**
23867           * @event Player#fullscreenchange
23868           * @type {EventTarget~Event}
23869           */
23870          this.trigger('fullscreenchange');
23871        }));
23872
23873        this.el_[fsApi.requestFullscreen]();
23874      } else if (this.tech_.supportsFullScreen()) {
23875        // we can't take the video.js controls fullscreen but we can go fullscreen
23876        // with native controls
23877        this.techCall_('enterFullScreen');
23878      } else {
23879        // fullscreen isn't supported so we'll just stretch the video element to
23880        // fill the viewport
23881        this.enterFullWindow();
23882        /**
23883         * @event Player#fullscreenchange
23884         * @type {EventTarget~Event}
23885         */
23886        this.trigger('fullscreenchange');
23887      }
23888    };
23889
23890    /**
23891     * Return the video to its normal size after having been in full screen mode
23892     *
23893     * @fires Player#fullscreenchange
23894     */
23895
23896
23897    Player.prototype.exitFullscreen = function exitFullscreen() {
23898      var fsApi = FullscreenApi;
23899
23900      this.isFullscreen(false);
23901
23902      // Check for browser element fullscreen support
23903      if (fsApi.requestFullscreen) {
23904        document_1[fsApi.exitFullscreen]();
23905      } else if (this.tech_.supportsFullScreen()) {
23906        this.techCall_('exitFullScreen');
23907      } else {
23908        this.exitFullWindow();
23909        /**
23910         * @event Player#fullscreenchange
23911         * @type {EventTarget~Event}
23912         */
23913        this.trigger('fullscreenchange');
23914      }
23915    };
23916
23917    /**
23918     * When fullscreen isn't supported we can stretch the
23919     * video container to as wide as the browser will let us.
23920     *
23921     * @fires Player#enterFullWindow
23922     */
23923
23924
23925    Player.prototype.enterFullWindow = function enterFullWindow() {
23926      this.isFullWindow = true;
23927
23928      // Storing original doc overflow value to return to when fullscreen is off
23929      this.docOrigOverflow = document_1.documentElement.style.overflow;
23930
23931      // Add listener for esc key to exit fullscreen
23932      on(document_1, 'keydown', bind(this, this.fullWindowOnEscKey));
23933
23934      // Hide any scroll bars
23935      document_1.documentElement.style.overflow = 'hidden';
23936
23937      // Apply fullscreen styles
23938      addClass(document_1.body, 'vjs-full-window');
23939
23940      /**
23941       * @event Player#enterFullWindow
23942       * @type {EventTarget~Event}
23943       */
23944      this.trigger('enterFullWindow');
23945    };
23946
23947    /**
23948     * Check for call to either exit full window or
23949     * full screen on ESC key
23950     *
23951     * @param {string} event
23952     *        Event to check for key press
23953     */
23954
23955
23956    Player.prototype.fullWindowOnEscKey = function fullWindowOnEscKey(event) {
23957      if (event.keyCode === 27) {
23958        if (this.isFullscreen() === true) {
23959          this.exitFullscreen();
23960        } else {
23961          this.exitFullWindow();
23962        }
23963      }
23964    };
23965
23966    /**
23967     * Exit full window
23968     *
23969     * @fires Player#exitFullWindow
23970     */
23971
23972
23973    Player.prototype.exitFullWindow = function exitFullWindow() {
23974      this.isFullWindow = false;
vendor: 35,746 bytes, lines 23975-25158
23975      off(document_1, 'keydown', this.fullWindowOnEscKey);
23976
23977      // Unhide scroll bars.
23978      document_1.documentElement.style.overflow = this.docOrigOverflow;
23979
23980      // Remove fullscreen styles
23981      removeClass(document_1.body, 'vjs-full-window');
23982
23983      // Resize the box, controller, and poster to original sizes
23984      // this.positionAll();
23985      /**
23986       * @event Player#exitFullWindow
23987       * @type {EventTarget~Event}
23988       */
23989      this.trigger('exitFullWindow');
23990    };
23991
23992    /**
23993     * Check whether the player can play a given mimetype
23994     *
23995     * @see https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype
23996     *
23997     * @param {string} type
23998     *        The mimetype to check
23999     *
24000     * @return {string}
24001     *         'probably', 'maybe', or '' (empty string)
24002     */
24003
24004
24005    Player.prototype.canPlayType = function canPlayType(type) {
24006      var can = void 0;
24007
24008      // Loop through each playback technology in the options order
24009      for (var i = 0, j = this.options_.techOrder; i < j.length; i++) {
24010        var techName = j[i];
24011        var tech = Tech.getTech(techName);
24012
24013        // Support old behavior of techs being registered as components.
24014        // Remove once that deprecated behavior is removed.
24015        if (!tech) {
24016          tech = Component.getComponent(techName);
24017        }
24018
24019        // Check if the current tech is defined before continuing
24020        if (!tech) {
24021          log$1.error('The "' + techName + '" tech is undefined. Skipped browser support check for that tech.');
24022          continue;
24023        }
24024
24025        // Check if the browser supports this technology
24026        if (tech.isSupported()) {
24027          can = tech.canPlayType(type);
24028
24029          if (can) {
24030            return can;
24031          }
24032        }
24033      }
24034
24035      return '';
24036    };
24037
24038    /**
24039     * Select source based on tech-order or source-order
24040     * Uses source-order selection if `options.sourceOrder` is truthy. Otherwise,
24041     * defaults to tech-order selection
24042     *
24043     * @param {Array} sources
24044     *        The sources for a media asset
24045     *
24046     * @return {Object|boolean}
24047     *         Object of source and tech order or false
24048     */
24049
24050
24051    Player.prototype.selectSource = function selectSource(sources) {
24052      var _this8 = this;
24053
24054      // Get only the techs specified in `techOrder` that exist and are supported by the
24055      // current platform
24056      var techs = this.options_.techOrder.map(function (techName) {
24057        return [techName, Tech.getTech(techName)];
24058      }).filter(function (_ref) {
24059        var techName = _ref[0],
24060            tech = _ref[1];
24061
24062        // Check if the current tech is defined before continuing
24063        if (tech) {
24064          // Check if the browser supports this technology
24065          return tech.isSupported();
24066        }
24067
24068        log$1.error('The "' + techName + '" tech is undefined. Skipped browser support check for that tech.');
24069        return false;
24070      });
24071
24072      // Iterate over each `innerArray` element once per `outerArray` element and execute
24073      // `tester` with both. If `tester` returns a non-falsy value, exit early and return
24074      // that value.
24075      var findFirstPassingTechSourcePair = function findFirstPassingTechSourcePair(outerArray, innerArray, tester) {
24076        var found = void 0;
24077
24078        outerArray.some(function (outerChoice) {
24079          return innerArray.some(function (innerChoice) {
24080            found = tester(outerChoice, innerChoice);
24081
24082            if (found) {
24083              return true;
24084            }
24085          });
24086        });
24087
24088        return found;
24089      };
24090
24091      var foundSourceAndTech = void 0;
24092      var flip = function flip(fn) {
24093        return function (a, b) {
24094          return fn(b, a);
24095        };
24096      };
24097      var finder = function finder(_ref2, source) {
24098        var techName = _ref2[0],
24099            tech = _ref2[1];
24100
24101        if (tech.canPlaySource(source, _this8.options_[techName.toLowerCase()])) {
24102          return { source: source, tech: techName };
24103        }
24104      };
24105
24106      // Depending on the truthiness of `options.sourceOrder`, we swap the order of techs and sources
24107      // to select from them based on their priority.
24108      if (this.options_.sourceOrder) {
24109        // Source-first ordering
24110        foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder));
24111      } else {
24112        // Tech-first ordering
24113        foundSourceAndTech = findFirstPassingTechSourcePair(techs, sources, finder);
24114      }
24115
24116      return foundSourceAndTech || false;
24117    };
24118
24119    /**
24120     * Get or set the video source.
24121     *
24122     * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source]
24123     *        A SourceObject, an array of SourceObjects, or a string referencing
24124     *        a URL to a media source. It is _highly recommended_ that an object
24125     *        or array of objects is used here, so that source selection
24126     *        algorithms can take the `type` into account.
24127     *
24128     *        If not provided, this method acts as a getter.
24129     *
24130     * @return {string|undefined}
24131     *         If the `source` argument is missing, returns the current source
24132     *         URL. Otherwise, returns nothing/undefined.
24133     */
24134
24135
24136    Player.prototype.src = function src(source) {
24137      var _this9 = this;
24138
24139      // getter usage
24140      if (typeof source === 'undefined') {
24141        return this.cache_.src || '';
24142      }
24143      // filter out invalid sources and turn our source into
24144      // an array of source objects
24145      var sources = filterSource(source);
24146
24147      // if a source was passed in then it is invalid because
24148      // it was filtered to a zero length Array. So we have to
24149      // show an error
24150      if (!sources.length) {
24151        this.setTimeout(function () {
24152          this.error({ code: 4, message: this.localize(this.options_.notSupportedMessage) });
24153        }, 0);
24154        return;
24155      }
24156
24157      // intial sources
24158      this.changingSrc_ = true;
24159
24160      this.cache_.sources = sources;
24161      this.updateSourceCaches_(sources[0]);
24162
24163      // middlewareSource is the source after it has been changed by middleware
24164      setSource(this, sources[0], function (middlewareSource, mws) {
24165        _this9.middleware_ = mws;
24166
24167        // since sourceSet is async we have to update the cache again after we select a source since
24168        // the source that is selected could be out of order from the cache update above this callback.
24169        _this9.cache_.sources = sources;
24170        _this9.updateSourceCaches_(middlewareSource);
24171
24172        var err = _this9.src_(middlewareSource);
24173
24174        if (err) {
24175          if (sources.length > 1) {
24176            return _this9.src(sources.slice(1));
24177          }
24178
24179          _this9.changingSrc_ = false;
24180
24181          // We need to wrap this in a timeout to give folks a chance to add error event handlers
24182          _this9.setTimeout(function () {
24183            this.error({ code: 4, message: this.localize(this.options_.notSupportedMessage) });
24184          }, 0);
24185
24186          // we could not find an appropriate tech, but let's still notify the delegate that this is it
24187          // this needs a better comment about why this is needed
24188          _this9.triggerReady();
24189
24190          return;
24191        }
24192
24193        setTech(mws, _this9.tech_);
24194      });
24195    };
24196
24197    /**
24198     * Set the source object on the tech, returns a boolean that indicates whether
24199     * there is a tech that can play the source or not
24200     *
24201     * @param {Tech~SourceObject} source
24202     *        The source object to set on the Tech
24203     *
24204     * @return {Boolean}
24205     *         - True if there is no Tech to playback this source
24206     *         - False otherwise
24207     *
24208     * @private
24209     */
24210
24211
24212    Player.prototype.src_ = function src_(source) {
24213      var _this10 = this;
24214
24215      var sourceTech = this.selectSource([source]);
24216
24217      if (!sourceTech) {
24218        return true;
24219      }
24220
24221      if (!titleCaseEquals(sourceTech.tech, this.techName_)) {
24222        this.changingSrc_ = true;
24223        // load this technology with the chosen source
24224        this.loadTech_(sourceTech.tech, sourceTech.source);
24225        this.tech_.ready(function () {
24226          _this10.changingSrc_ = false;
24227        });
24228        return false;
24229      }
24230
24231      // wait until the tech is ready to set the source
24232      // and set it synchronously if possible (#2326)
24233      this.ready(function () {
24234
24235        // The setSource tech method was added with source handlers
24236        // so older techs won't support it
24237        // We need to check the direct prototype for the case where subclasses
24238        // of the tech do not support source handlers
24239        if (this.tech_.constructor.prototype.hasOwnProperty('setSource')) {
24240          this.techCall_('setSource', source);
24241        } else {
24242          this.techCall_('src', source.src);
24243        }
24244
24245        this.changingSrc_ = false;
24246      }, true);
24247
24248      return false;
24249    };
24250
24251    /**
24252     * Begin loading the src data.
24253     */
24254
24255
24256    Player.prototype.load = function load() {
24257      this.techCall_('load');
24258    };
24259
24260    /**
24261     * Reset the player. Loads the first tech in the techOrder,
24262     * and calls `reset` on the tech`.
24263     */
24264
24265
24266    Player.prototype.reset = function reset() {
24267      this.loadTech_(this.options_.techOrder[0], null);
24268      this.techCall_('reset');
24269    };
24270
24271    /**
24272     * Returns all of the current source objects.
24273     *
24274     * @return {Tech~SourceObject[]}
24275     *         The current source objects
24276     */
24277
24278
24279    Player.prototype.currentSources = function currentSources() {
24280      var source = this.currentSource();
24281      var sources = [];
24282
24283      // assume `{}` or `{ src }`
24284      if (Object.keys(source).length !== 0) {
24285        sources.push(source);
24286      }
24287
24288      return this.cache_.sources || sources;
24289    };
24290
24291    /**
24292     * Returns the current source object.
24293     *
24294     * @return {Tech~SourceObject}
24295     *         The current source object
24296     */
24297
24298
24299    Player.prototype.currentSource = function currentSource() {
24300      return this.cache_.source || {};
24301    };
24302
24303    /**
24304     * Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4
24305     * Can be used in conjunction with `currentType` to assist in rebuilding the current source object.
24306     *
24307     * @return {string}
24308     *         The current source
24309     */
24310
24311
24312    Player.prototype.currentSrc = function currentSrc() {
24313      return this.currentSource() && this.currentSource().src || '';
24314    };
24315
24316    /**
24317     * Get the current source type e.g. video/mp4
24318     * This can allow you rebuild the current source object so that you could load the same
24319     * source and tech later
24320     *
24321     * @return {string}
24322     *         The source MIME type
24323     */
24324
24325
24326    Player.prototype.currentType = function currentType() {
24327      return this.currentSource() && this.currentSource().type || '';
24328    };
24329
24330    /**
24331     * Get or set the preload attribute
24332     *
24333     * @param {boolean} [value]
24334     *        - true means that we should preload
24335     *        - false means that we should not preload
24336     *
24337     * @return {string}
24338     *         The preload attribute value when getting
24339     */
24340
24341
24342    Player.prototype.preload = function preload(value) {
24343      if (value !== undefined) {
24344        this.techCall_('setPreload', value);
24345        this.options_.preload = value;
24346        return;
24347      }
24348      return this.techGet_('preload');
24349    };
24350
24351    /**
24352     * Get or set the autoplay option. When this is a boolean it will
24353     * modify the attribute on the tech. When this is a string the attribute on
24354     * the tech will be removed and `Player` will handle autoplay on loadstarts.
24355     *
24356     * @param {boolean|string} [value]
24357     *        - true: autoplay using the browser behavior
24358     *        - false: do not autoplay
24359     *        - 'play': call play() on every loadstart
24360     *        - 'muted': call muted() then play() on every loadstart
24361     *        - 'any': call play() on every loadstart. if that fails call muted() then play().
24362     *        - *: values other than those listed here will be set `autoplay` to true
24363     *
24364     * @return {boolean|string}
24365     *         The current value of autoplay when getting
24366     */
24367
24368
24369    Player.prototype.autoplay = function autoplay(value) {
24370      // getter usage
24371      if (value === undefined) {
24372        return this.options_.autoplay || false;
24373      }
24374
24375      var techAutoplay = void 0;
24376
24377      // if the value is a valid string set it to that
24378      if (typeof value === 'string' && /(any|play|muted)/.test(value)) {
24379        this.options_.autoplay = value;
24380        this.manualAutoplay_(value);
24381        techAutoplay = false;
24382
24383        // any falsy value sets autoplay to false in the browser,
24384        // lets do the same
24385      } else if (!value) {
24386        this.options_.autoplay = false;
24387
24388        // any other value (ie truthy) sets autoplay to true
24389      } else {
24390        this.options_.autoplay = true;
24391      }
24392
24393      techAutoplay = techAutoplay || this.options_.autoplay;
24394
24395      // if we don't have a tech then we do not queue up
24396      // a setAutoplay call on tech ready. We do this because the
24397      // autoplay option will be passed in the constructor and we
24398      // do not need to set it twice
24399      if (this.tech_) {
24400        this.techCall_('setAutoplay', techAutoplay);
24401      }
24402    };
24403
24404    /**
24405     * Set or unset the playsinline attribute.
24406     * Playsinline tells the browser that non-fullscreen playback is preferred.
24407     *
24408     * @param {boolean} [value]
24409     *        - true means that we should try to play inline by default
24410     *        - false means that we should use the browser's default playback mode,
24411     *          which in most cases is inline. iOS Safari is a notable exception
24412     *          and plays fullscreen by default.
24413     *
24414     * @return {string|Player}
24415     *         - the current value of playsinline
24416     *         - the player when setting
24417     *
24418     * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
24419     */
24420
24421
24422    Player.prototype.playsinline = function playsinline(value) {
24423      if (value !== undefined) {
24424        this.techCall_('setPlaysinline', value);
24425        this.options_.playsinline = value;
24426        return this;
24427      }
24428      return this.techGet_('playsinline');
24429    };
24430
24431    /**
24432     * Get or set the loop attribute on the video element.
24433     *
24434     * @param {boolean} [value]
24435     *        - true means that we should loop the video
24436     *        - false means that we should not loop the video
24437     *
24438     * @return {string}
24439     *         The current value of loop when getting
24440     */
24441
24442
24443    Player.prototype.loop = function loop(value) {
24444      if (value !== undefined) {
24445        this.techCall_('setLoop', value);
24446        this.options_.loop = value;
24447        return;
24448      }
24449      return this.techGet_('loop');
24450    };
24451
24452    /**
24453     * Get or set the poster image source url
24454     *
24455     * @fires Player#posterchange
24456     *
24457     * @param {string} [src]
24458     *        Poster image source URL
24459     *
24460     * @return {string}
24461     *         The current value of poster when getting
24462     */
24463
24464
24465    Player.prototype.poster = function poster(src) {
24466      if (src === undefined) {
24467        return this.poster_;
24468      }
24469
24470      // The correct way to remove a poster is to set as an empty string
24471      // other falsey values will throw errors
24472      if (!src) {
24473        src = '';
24474      }
24475
24476      if (src === this.poster_) {
24477        return;
24478      }
24479
24480      // update the internal poster variable
24481      this.poster_ = src;
24482
24483      // update the tech's poster
24484      this.techCall_('setPoster', src);
24485
24486      this.isPosterFromTech_ = false;
24487
24488      // alert components that the poster has been set
24489      /**
24490       * This event fires when the poster image is changed on the player.
24491       *
24492       * @event Player#posterchange
24493       * @type {EventTarget~Event}
24494       */
24495      this.trigger('posterchange');
24496    };
24497
24498    /**
24499     * Some techs (e.g. YouTube) can provide a poster source in an
24500     * asynchronous way. We want the poster component to use this
24501     * poster source so that it covers up the tech's controls.
24502     * (YouTube's play button). However we only want to use this
24503     * source if the player user hasn't set a poster through
24504     * the normal APIs.
24505     *
24506     * @fires Player#posterchange
24507     * @listens Tech#posterchange
24508     * @private
24509     */
24510
24511
24512    Player.prototype.handleTechPosterChange_ = function handleTechPosterChange_() {
24513      if ((!this.poster_ || this.options_.techCanOverridePoster) && this.tech_ && this.tech_.poster) {
24514        var newPoster = this.tech_.poster() || '';
24515
24516        if (newPoster !== this.poster_) {
24517          this.poster_ = newPoster;
24518          this.isPosterFromTech_ = true;
24519
24520          // Let components know the poster has changed
24521          this.trigger('posterchange');
24522        }
24523      }
24524    };
24525
24526    /**
24527     * Get or set whether or not the controls are showing.
24528     *
24529     * @fires Player#controlsenabled
24530     *
24531     * @param {boolean} [bool]
24532     *        - true to turn controls on
24533     *        - false to turn controls off
24534     *
24535     * @return {boolean}
24536     *         The current value of controls when getting
24537     */
24538
24539
24540    Player.prototype.controls = function controls(bool) {
24541      if (bool === undefined) {
24542        return !!this.controls_;
24543      }
24544
24545      bool = !!bool;
24546
24547      // Don't trigger a change event unless it actually changed
24548      if (this.controls_ === bool) {
24549        return;
24550      }
24551
24552      this.controls_ = bool;
24553
24554      if (this.usingNativeControls()) {
24555        this.techCall_('setControls', bool);
24556      }
24557
24558      if (this.controls_) {
24559        this.removeClass('vjs-controls-disabled');
24560        this.addClass('vjs-controls-enabled');
24561        /**
24562         * @event Player#controlsenabled
24563         * @type {EventTarget~Event}
24564         */
24565        this.trigger('controlsenabled');
24566        if (!this.usingNativeControls()) {
24567          this.addTechControlsListeners_();
24568        }
24569      } else {
24570        this.removeClass('vjs-controls-enabled');
24571        this.addClass('vjs-controls-disabled');
24572        /**
24573         * @event Player#controlsdisabled
24574         * @type {EventTarget~Event}
24575         */
24576        this.trigger('controlsdisabled');
24577        if (!this.usingNativeControls()) {
24578          this.removeTechControlsListeners_();
24579        }
24580      }
24581    };
24582
24583    /**
24584     * Toggle native controls on/off. Native controls are the controls built into
24585     * devices (e.g. default iPhone controls), Flash, or other techs
24586     * (e.g. Vimeo Controls)
24587     * **This should only be set by the current tech, because only the tech knows
24588     * if it can support native controls**
24589     *
24590     * @fires Player#usingnativecontrols
24591     * @fires Player#usingcustomcontrols
24592     *
24593     * @param {boolean} [bool]
24594     *        - true to turn native controls on
24595     *        - false to turn native controls off
24596     *
24597     * @return {boolean}
24598     *         The current value of native controls when getting
24599     */
24600
24601
24602    Player.prototype.usingNativeControls = function usingNativeControls(bool) {
24603      if (bool === undefined) {
24604        return !!this.usingNativeControls_;
24605      }
24606
24607      bool = !!bool;
24608
24609      // Don't trigger a change event unless it actually changed
24610      if (this.usingNativeControls_ === bool) {
24611        return;
24612      }
24613
24614      this.usingNativeControls_ = bool;
24615
24616      if (this.usingNativeControls_) {
24617        this.addClass('vjs-using-native-controls');
24618
24619        /**
24620         * player is using the native device controls
24621         *
24622         * @event Player#usingnativecontrols
24623         * @type {EventTarget~Event}
24624         */
24625        this.trigger('usingnativecontrols');
24626      } else {
24627        this.removeClass('vjs-using-native-controls');
24628
24629        /**
24630         * player is using the custom HTML controls
24631         *
24632         * @event Player#usingcustomcontrols
24633         * @type {EventTarget~Event}
24634         */
24635        this.trigger('usingcustomcontrols');
24636      }
24637    };
24638
24639    /**
24640     * Set or get the current MediaError
24641     *
24642     * @fires Player#error
24643     *
24644     * @param  {MediaError|string|number} [err]
24645     *         A MediaError or a string/number to be turned
24646     *         into a MediaError
24647     *
24648     * @return {MediaError|null}
24649     *         The current MediaError when getting (or null)
24650     */
24651
24652
24653    Player.prototype.error = function error(err) {
24654      if (err === undefined) {
24655        return this.error_ || null;
24656      }
24657
24658      // restoring to default
24659      if (err === null) {
24660        this.error_ = err;
24661        this.removeClass('vjs-error');
24662        if (this.errorDisplay) {
24663          this.errorDisplay.close();
24664        }
24665        return;
24666      }
24667
24668      this.error_ = new MediaError(err);
24669
24670      // add the vjs-error classname to the player
24671      this.addClass('vjs-error');
24672
24673      // log the name of the error type and any message
24674      // IE11 logs "[object object]" and required you to expand message to see error object
24675      log$1.error('(CODE:' + this.error_.code + ' ' + MediaError.errorTypes[this.error_.code] + ')', this.error_.message, this.error_);
24676
24677      /**
24678       * @event Player#error
24679       * @type {EventTarget~Event}
24680       */
24681      this.trigger('error');
24682
24683      return;
24684    };
24685
24686    /**
24687     * Report user activity
24688     *
24689     * @param {Object} event
24690     *        Event object
24691     */
24692
24693
24694    Player.prototype.reportUserActivity = function reportUserActivity(event) {
24695      this.userActivity_ = true;
24696    };
24697
24698    /**
24699     * Get/set if user is active
24700     *
24701     * @fires Player#useractive
24702     * @fires Player#userinactive
24703     *
24704     * @param {boolean} [bool]
24705     *        - true if the user is active
24706     *        - false if the user is inactive
24707     *
24708     * @return {boolean}
24709     *         The current value of userActive when getting
24710     */
24711
24712
24713    Player.prototype.userActive = function userActive(bool) {
24714      if (bool === undefined) {
24715        return this.userActive_;
24716      }
24717
24718      bool = !!bool;
24719
24720      if (bool === this.userActive_) {
24721        return;
24722      }
24723
24724      this.userActive_ = bool;
24725
24726      if (this.userActive_) {
24727        this.userActivity_ = true;
24728        this.removeClass('vjs-user-inactive');
24729        this.addClass('vjs-user-active');
24730        /**
24731         * @event Player#useractive
24732         * @type {EventTarget~Event}
24733         */
24734        this.trigger('useractive');
24735        return;
24736      }
24737
24738      // Chrome/Safari/IE have bugs where when you change the cursor it can
24739      // trigger a mousemove event. This causes an issue when you're hiding
24740      // the cursor when the user is inactive, and a mousemove signals user
24741      // activity. Making it impossible to go into inactive mode. Specifically
24742      // this happens in fullscreen when we really need to hide the cursor.
24743      //
24744      // When this gets resolved in ALL browsers it can be removed
24745      // https://code.google.com/p/chromium/issues/detail?id=103041
24746      if (this.tech_) {
24747        this.tech_.one('mousemove', function (e) {
24748          e.stopPropagation();
24749          e.preventDefault();
24750        });
24751      }
24752
24753      this.userActivity_ = false;
24754      this.removeClass('vjs-user-active');
24755      this.addClass('vjs-user-inactive');
24756      /**
24757       * @event Player#userinactive
24758       * @type {EventTarget~Event}
24759       */
24760      this.trigger('userinactive');
24761    };
24762
24763    /**
24764     * Listen for user activity based on timeout value
24765     *
24766     * @private
24767     */
24768
24769
24770    Player.prototype.listenForUserActivity_ = function listenForUserActivity_() {
24771      var mouseInProgress = void 0;
24772      var lastMoveX = void 0;
24773      var lastMoveY = void 0;
24774      var handleActivity = bind(this, this.reportUserActivity);
24775
24776      var handleMouseMove = function handleMouseMove(e) {
24777        // #1068 - Prevent mousemove spamming
24778        // Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970
24779        if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) {
24780          lastMoveX = e.screenX;
24781          lastMoveY = e.screenY;
24782          handleActivity();
24783        }
24784      };
24785
24786      var handleMouseDown = function handleMouseDown() {
24787        handleActivity();
24788        // For as long as the they are touching the device or have their mouse down,
24789        // we consider them active even if they're not moving their finger or mouse.
24790        // So we want to continue to update that they are active
24791        this.clearInterval(mouseInProgress);
24792        // Setting userActivity=true now and setting the interval to the same time
24793        // as the activityCheck interval (250) should ensure we never miss the
24794        // next activityCheck
24795        mouseInProgress = this.setInterval(handleActivity, 250);
24796      };
24797
24798      var handleMouseUp = function handleMouseUp(event) {
24799        handleActivity();
24800        // Stop the interval that maintains activity if the mouse/touch is down
24801        this.clearInterval(mouseInProgress);
24802      };
24803
24804      // Any mouse movement will be considered user activity
24805      this.on('mousedown', handleMouseDown);
24806      this.on('mousemove', handleMouseMove);
24807      this.on('mouseup', handleMouseUp);
24808
24809      // Listen for keyboard navigation
24810      // Shouldn't need to use inProgress interval because of key repeat
24811      this.on('keydown', handleActivity);
24812      this.on('keyup', handleActivity);
24813
24814      // Run an interval every 250 milliseconds instead of stuffing everything into
24815      // the mousemove/touchmove function itself, to prevent performance degradation.
24816      // `this.reportUserActivity` simply sets this.userActivity_ to true, which
24817      // then gets picked up by this loop
24818      // http://ejohn.org/blog/learning-from-twitter/
24819      var inactivityTimeout = void 0;
24820
24821      this.setInterval(function () {
24822        // Check to see if mouse/touch activity has happened
24823        if (!this.userActivity_) {
24824          return;
24825        }
24826
24827        // Reset the activity tracker
24828        this.userActivity_ = false;
24829
24830        // If the user state was inactive, set the state to active
24831        this.userActive(true);
24832
24833        // Clear any existing inactivity timeout to start the timer over
24834        this.clearTimeout(inactivityTimeout);
24835
24836        var timeout = this.options_.inactivityTimeout;
24837
24838        if (timeout <= 0) {
24839          return;
24840        }
24841
24842        // In <timeout> milliseconds, if no more activity has occurred the
24843        // user will be considered inactive
24844        inactivityTimeout = this.setTimeout(function () {
24845          // Protect against the case where the inactivityTimeout can trigger just
24846          // before the next user activity is picked up by the activity check loop
24847          // causing a flicker
24848          if (!this.userActivity_) {
24849            this.userActive(false);
24850          }
24851        }, timeout);
24852      }, 250);
24853    };
24854
24855    /**
24856     * Gets or sets the current playback rate. A playback rate of
24857     * 1.0 represents normal speed and 0.5 would indicate half-speed
24858     * playback, for instance.
24859     *
24860     * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate
24861     *
24862     * @param {number} [rate]
24863     *       New playback rate to set.
24864     *
24865     * @return {number}
24866     *         The current playback rate when getting or 1.0
24867     */
24868
24869
24870    Player.prototype.playbackRate = function playbackRate(rate) {
24871      if (rate !== undefined) {
24872        // NOTE: this.cache_.lastPlaybackRate is set from the tech handler
24873        // that is registered above
24874        this.techCall_('setPlaybackRate', rate);
24875        return;
24876      }
24877
24878      if (this.tech_ && this.tech_.featuresPlaybackRate) {
24879        return this.cache_.lastPlaybackRate || this.techGet_('playbackRate');
24880      }
24881      return 1.0;
24882    };
24883
24884    /**
24885     * Gets or sets the current default playback rate. A default playback rate of
24886     * 1.0 represents normal speed and 0.5 would indicate half-speed playback, for instance.
24887     * defaultPlaybackRate will only represent what the initial playbackRate of a video was, not
24888     * not the current playbackRate.
24889     *
24890     * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate
24891     *
24892     * @param {number} [rate]
24893     *       New default playback rate to set.
24894     *
24895     * @return {number|Player}
24896     *         - The default playback rate when getting or 1.0
24897     *         - the player when setting
24898     */
24899
24900
24901    Player.prototype.defaultPlaybackRate = function defaultPlaybackRate(rate) {
24902      if (rate !== undefined) {
24903        return this.techCall_('setDefaultPlaybackRate', rate);
24904      }
24905
24906      if (this.tech_ && this.tech_.featuresPlaybackRate) {
24907        return this.techGet_('defaultPlaybackRate');
24908      }
24909      return 1.0;
24910    };
24911
24912    /**
24913     * Gets or sets the audio flag
24914     *
24915     * @param {boolean} bool
24916     *        - true signals that this is an audio player
24917     *        - false signals that this is not an audio player
24918     *
24919     * @return {boolean}
24920     *         The current value of isAudio when getting
24921     */
24922
24923
24924    Player.prototype.isAudio = function isAudio(bool) {
24925      if (bool !== undefined) {
24926        this.isAudio_ = !!bool;
24927        return;
24928      }
24929
24930      return !!this.isAudio_;
24931    };
24932
24933    /**
24934     * A helper method for adding a {@link TextTrack} to our
24935     * {@link TextTrackList}.
24936     *
24937     * In addition to the W3C settings we allow adding additional info through options.
24938     *
24939     * @see http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
24940     *
24941     * @param {string} [kind]
24942     *        the kind of TextTrack you are adding
24943     *
24944     * @param {string} [label]
24945     *        the label to give the TextTrack label
24946     *
24947     * @param {string} [language]
24948     *        the language to set on the TextTrack
24949     *
24950     * @return {TextTrack|undefined}
24951     *         the TextTrack that was added or undefined
24952     *         if there is no tech
24953     */
24954
24955
24956    Player.prototype.addTextTrack = function addTextTrack(kind, label, language) {
24957      if (this.tech_) {
24958        return this.tech_.addTextTrack(kind, label, language);
24959      }
24960    };
24961
24962    /**
24963     * Create a remote {@link TextTrack} and an {@link HTMLTrackElement}. It will
24964     * automatically removed from the video element whenever the source changes, unless
24965     * manualCleanup is set to false.
24966     *
24967     * @param {Object} options
24968     *        Options to pass to {@link HTMLTrackElement} during creation. See
24969     *        {@link HTMLTrackElement} for object properties that you should use.
24970     *
24971     * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be
24972     *
24973     * @return {HtmlTrackElement}
24974     *         the HTMLTrackElement that was created and added
24975     *         to the HtmlTrackElementList and the remote
24976     *         TextTrackList
24977     *
24978     * @deprecated The default value of the "manualCleanup" parameter will default
24979     *             to "false" in upcoming versions of Video.js
24980     */
24981
24982
24983    Player.prototype.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
24984      if (this.tech_) {
24985        return this.tech_.addRemoteTextTrack(options, manualCleanup);
24986      }
24987    };
24988
24989    /**
24990     * Remove a remote {@link TextTrack} from the respective
24991     * {@link TextTrackList} and {@link HtmlTrackElementList}.
24992     *
24993     * @param {Object} track
24994     *        Remote {@link TextTrack} to remove
24995     *
24996     * @return {undefined}
24997     *         does not return anything
24998     */
24999
25000
25001    Player.prototype.removeRemoteTextTrack = function removeRemoteTextTrack() {
25002      var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
25003          _ref3$track = _ref3.track,
25004          track = _ref3$track === undefined ? arguments[0] : _ref3$track;
25005
25006      // destructure the input into an object with a track argument, defaulting to arguments[0]
25007      // default the whole argument to an empty object if nothing was passed in
25008
25009      if (this.tech_) {
25010        return this.tech_.removeRemoteTextTrack(track);
25011      }
25012    };
25013
25014    /**
25015     * Gets available media playback quality metrics as specified by the W3C's Media
25016     * Playback Quality API.
25017     *
25018     * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
25019     *
25020     * @return {Object|undefined}
25021     *         An object with supported media playback quality metrics or undefined if there
25022     *         is no tech or the tech does not support it.
25023     */
25024
25025
25026    Player.prototype.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
25027      return this.techGet_('getVideoPlaybackQuality');
25028    };
25029
25030    /**
25031     * Get video width
25032     *
25033     * @return {number}
25034     *         current video width
25035     */
25036
25037
25038    Player.prototype.videoWidth = function videoWidth() {
25039      return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0;
25040    };
25041
25042    /**
25043     * Get video height
25044     *
25045     * @return {number}
25046     *         current video height
25047     */
25048
25049
25050    Player.prototype.videoHeight = function videoHeight() {
25051      return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0;
25052    };
25053
25054    /**
25055     * The player's language code
25056     * NOTE: The language should be set in the player options if you want the
25057     * the controls to be built with a specific language. Changing the language
25058     * later will not update controls text.
25059     *
25060     * @param {string} [code]
25061     *        the language code to set the player to
25062     *
25063     * @return {string}
25064     *         The current language code when getting
25065     */
25066
25067
25068    Player.prototype.language = function language(code) {
25069      if (code === undefined) {
25070        return this.language_;
25071      }
25072
25073      this.language_ = String(code).toLowerCase();
25074    };
25075
25076    /**
25077     * Get the player's language dictionary
25078     * Merge every time, because a newly added plugin might call videojs.addLanguage() at any time
25079     * Languages specified directly in the player options have precedence
25080     *
25081     * @return {Array}
25082     *         An array of of supported languages
25083     */
25084
25085
25086    Player.prototype.languages = function languages() {
25087      return mergeOptions(Player.prototype.options_.languages, this.languages_);
25088    };
25089
25090    /**
25091     * returns a JavaScript object reperesenting the current track
25092     * information. **DOES not return it as JSON**
25093     *
25094     * @return {Object}
25095     *         Object representing the current of track info
25096     */
25097
25098
25099    Player.prototype.toJSON = function toJSON() {
25100      var options = mergeOptions(this.options_);
25101      var tracks = options.tracks;
25102
25103      options.tracks = [];
25104
25105      for (var i = 0; i < tracks.length; i++) {
25106        var track = tracks[i];
25107
25108        // deep merge tracks and null out player so no circular references
25109        track = mergeOptions(track);
25110        track.player = undefined;
25111        options.tracks[i] = track;
25112      }
25113
25114      return options;
25115    };
25116
25117    /**
25118     * Creates a simple modal dialog (an instance of the {@link ModalDialog}
25119     * component) that immediately overlays the player with arbitrary
25120     * content and removes itself when closed.
25121     *
25122     * @param {string|Function|Element|Array|null} content
25123     *        Same as {@link ModalDialog#content}'s param of the same name.
25124     *        The most straight-forward usage is to provide a string or DOM
25125     *        element.
25126     *
25127     * @param {Object} [options]
25128     *        Extra options which will be passed on to the {@link ModalDialog}.
25129     *
25130     * @return {ModalDialog}
25131     *         the {@link ModalDialog} that was created
25132     */
25133
25134
25135    Player.prototype.createModal = function createModal(content, options) {
25136      var _this11 = this;
25137
25138      options = options || {};
25139      options.content = content || '';
25140
25141      var modal = new ModalDialog(this, options);
25142
25143      this.addChild(modal);
25144      modal.on('dispose', function () {
25145        _this11.removeChild(modal);
25146      });
25147
25148      modal.open();
25149      return modal;
25150    };
25151
25152    /**
25153     * Gets tag settings
25154     *
25155     * @param {Element} tag
25156     *        The player tag
25157     *
25158     * @return {Object}
vendor: 4,580 bytes, lines 25159-25329
25159     *         An object containing all of the settings
25160     *         for a player tag
25161     */
25162
25163
25164    Player.getTagSettings = function getTagSettings(tag) {
25165      var baseOptions = {
25166        sources: [],
25167        tracks: []
25168      };
25169
25170      var tagOptions = getAttributes(tag);
25171      var dataSetup = tagOptions['data-setup'];
25172
25173      if (hasClass(tag, 'vjs-fluid')) {
25174        tagOptions.fluid = true;
25175      }
25176
25177      // Check if data-setup attr exists.
25178      if (dataSetup !== null) {
25179        // Parse options JSON
25180        // If empty string, make it a parsable json object.
25181        var _safeParseTuple = tuple(dataSetup || '{}'),
25182            err = _safeParseTuple[0],
25183            data = _safeParseTuple[1];
25184
25185        if (err) {
25186          log$1.error(err);
25187        }
25188        assign(tagOptions, data);
25189      }
25190
25191      assign(baseOptions, tagOptions);
25192
25193      // Get tag children settings
25194      if (tag.hasChildNodes()) {
25195        var children = tag.childNodes;
25196
25197        for (var i = 0, j = children.length; i < j; i++) {
25198          var child = children[i];
25199          // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
25200          var childName = child.nodeName.toLowerCase();
25201
25202          if (childName === 'source') {
25203            baseOptions.sources.push(getAttributes(child));
25204          } else if (childName === 'track') {
25205            baseOptions.tracks.push(getAttributes(child));
25206          }
25207        }
25208      }
25209
25210      return baseOptions;
25211    };
25212
25213    /**
25214     * Determine whether or not flexbox is supported
25215     *
25216     * @return {boolean}
25217     *         - true if flexbox is supported
25218     *         - false if flexbox is not supported
25219     */
25220
25221
25222    Player.prototype.flexNotSupported_ = function flexNotSupported_() {
25223      var elem = document_1.createElement('i');
25224
25225      // Note: We don't actually use flexBasis (or flexOrder), but it's one of the more
25226      // common flex features that we can rely on when checking for flex support.
25227      return !('flexBasis' in elem.style || 'webkitFlexBasis' in elem.style || 'mozFlexBasis' in elem.style || 'msFlexBasis' in elem.style ||
25228      // IE10-specific (2012 flex spec), available for completeness
25229      'msFlexOrder' in elem.style);
25230    };
25231
25232    return Player;
25233  }(Component);
25234
25235  /**
25236   * Get the {@link VideoTrackList}
25237   * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist
25238   *
25239   * @return {VideoTrackList}
25240   *         the current video track list
25241   *
25242   * @method Player.prototype.videoTracks
25243   */
25244
25245  /**
25246   * Get the {@link AudioTrackList}
25247   * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist
25248   *
25249   * @return {AudioTrackList}
25250   *         the current audio track list
25251   *
25252   * @method Player.prototype.audioTracks
25253   */
25254
25255  /**
25256   * Get the {@link TextTrackList}
25257   *
25258   * @link http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
25259   *
25260   * @return {TextTrackList}
25261   *         the current text track list
25262   *
25263   * @method Player.prototype.textTracks
25264   */
25265
25266  /**
25267   * Get the remote {@link TextTrackList}
25268   *
25269   * @return {TextTrackList}
25270   *         The current remote text track list
25271   *
25272   * @method Player.prototype.remoteTextTracks
25273   */
25274
25275  /**
25276   * Get the remote {@link HtmlTrackElementList} tracks.
25277   *
25278   * @return {HtmlTrackElementList}
25279   *         The current remote text track element list
25280   *
25281   * @method Player.prototype.remoteTextTrackEls
25282   */
25283
25284  ALL.names.forEach(function (name$$1) {
25285    var props = ALL[name$$1];
25286
25287    Player.prototype[props.getterName] = function () {
25288      if (this.tech_) {
25289        return this.tech_[props.getterName]();
25290      }
25291
25292      // if we have not yet loadTech_, we create {video,audio,text}Tracks_
25293      // these will be passed to the tech during loading
25294      this[props.privateName] = this[props.privateName] || new props.ListClass();
25295      return this[props.privateName];
25296    };
25297  });
25298
25299  /**
25300   * Global player list
25301   *
25302   * @type {Object}
25303   */
25304  Player.players = {};
25305
25306  var navigator = window_1.navigator;
25307
25308  /*
25309   * Player instance options, surfaced using options
25310   * options = Player.prototype.options_
25311   * Make changes in options, not here.
25312   *
25313   * @type {Object}
25314   * @private
25315   */
25316  Player.prototype.options_ = {
25317    // Default order of fallback technology
25318    techOrder: Tech.defaultTechOrder_,
25319
25320    html5: {},
25321    flash: {},
25322
25323    // default inactivity timeout
25324    inactivityTimeout: 2000,
25325
25326    // default playback rates
25327    playbackRates: [],
25328    // Add playback rate selection by adding rates
25329    // 'playbackRates': [0.5, 1, 1.5, 2],
vendor: 34,162 bytes, lines 25330-26521
25330
25331    // Included control sets
25332    children: ['mediaLoader', 'posterImage', 'textTrackDisplay', 'loadingSpinner', 'bigPlayButton', 'controlBar', 'errorDisplay', 'textTrackSettings', 'resizeManager'],
25333
25334    language: navigator && (navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language) || 'en',
25335
25336    // locales and their language translations
25337    languages: {},
25338
25339    // Default message to show when a video cannot be played.
25340    notSupportedMessage: 'No compatible source was found for this media.'
25341  };
25342
25343  [
25344  /**
25345   * Returns whether or not the player is in the "ended" state.
25346   *
25347   * @return {Boolean} True if the player is in the ended state, false if not.
25348   * @method Player#ended
25349   */
25350  'ended',
25351  /**
25352   * Returns whether or not the player is in the "seeking" state.
25353   *
25354   * @return {Boolean} True if the player is in the seeking state, false if not.
25355   * @method Player#seeking
25356   */
25357  'seeking',
25358  /**
25359   * Returns the TimeRanges of the media that are currently available
25360   * for seeking to.
25361   *
25362   * @return {TimeRanges} the seekable intervals of the media timeline
25363   * @method Player#seekable
25364   */
25365  'seekable',
25366  /**
25367   * Returns the current state of network activity for the element, from
25368   * the codes in the list below.
25369   * - NETWORK_EMPTY (numeric value 0)
25370   *   The element has not yet been initialised. All attributes are in
25371   *   their initial states.
25372   * - NETWORK_IDLE (numeric value 1)
25373   *   The element's resource selection algorithm is active and has
25374   *   selected a resource, but it is not actually using the network at
25375   *   this time.
25376   * - NETWORK_LOADING (numeric value 2)
25377   *   The user agent is actively trying to download data.
25378   * - NETWORK_NO_SOURCE (numeric value 3)
25379   *   The element's resource selection algorithm is active, but it has
25380   *   not yet found a resource to use.
25381   *
25382   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states
25383   * @return {number} the current network activity state
25384   * @method Player#networkState
25385   */
25386  'networkState',
25387  /**
25388   * Returns a value that expresses the current state of the element
25389   * with respect to rendering the current playback position, from the
25390   * codes in the list below.
25391   * - HAVE_NOTHING (numeric value 0)
25392   *   No information regarding the media resource is available.
25393   * - HAVE_METADATA (numeric value 1)
25394   *   Enough of the resource has been obtained that the duration of the
25395   *   resource is available.
25396   * - HAVE_CURRENT_DATA (numeric value 2)
25397   *   Data for the immediate current playback position is available.
25398   * - HAVE_FUTURE_DATA (numeric value 3)
25399   *   Data for the immediate current playback position is available, as
25400   *   well as enough data for the user agent to advance the current
25401   *   playback position in the direction of playback.
25402   * - HAVE_ENOUGH_DATA (numeric value 4)
25403   *   The user agent estimates that enough data is available for
25404   *   playback to proceed uninterrupted.
25405   *
25406   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate
25407   * @return {number} the current playback rendering state
25408   * @method Player#readyState
25409   */
25410  'readyState'].forEach(function (fn) {
25411    Player.prototype[fn] = function () {
25412      return this.techGet_(fn);
25413    };
25414  });
25415
25416  TECH_EVENTS_RETRIGGER.forEach(function (event) {
25417    Player.prototype['handleTech' + toTitleCase(event) + '_'] = function () {
25418      return this.trigger(event);
25419    };
25420  });
25421
25422  /**
25423   * Fired when the player has initial duration and dimension information
25424   *
25425   * @event Player#loadedmetadata
25426   * @type {EventTarget~Event}
25427   */
25428
25429  /**
25430   * Fired when the player has downloaded data at the current playback position
25431   *
25432   * @event Player#loadeddata
25433   * @type {EventTarget~Event}
25434   */
25435
25436  /**
25437   * Fired when the current playback position has changed *
25438   * During playback this is fired every 15-250 milliseconds, depending on the
25439   * playback technology in use.
25440   *
25441   * @event Player#timeupdate
25442   * @type {EventTarget~Event}
25443   */
25444
25445  /**
25446   * Fired when the volume changes
25447   *
25448   * @event Player#volumechange
25449   * @type {EventTarget~Event}
25450   */
25451
25452  /**
25453   * Reports whether or not a player has a plugin available.
25454   *
25455   * This does not report whether or not the plugin has ever been initialized
25456   * on this player. For that, [usingPlugin]{@link Player#usingPlugin}.
25457   *
25458   * @method Player#hasPlugin
25459   * @param  {string}  name
25460   *         The name of a plugin.
25461   *
25462   * @return {boolean}
25463   *         Whether or not this player has the requested plugin available.
25464   */
25465
25466  /**
25467   * Reports whether or not a player is using a plugin by name.
25468   *
25469   * For basic plugins, this only reports whether the plugin has _ever_ been
25470   * initialized on this player.
25471   *
25472   * @method Player#usingPlugin
25473   * @param  {string} name
25474   *         The name of a plugin.
25475   *
25476   * @return {boolean}
25477   *         Whether or not this player is using the requested plugin.
25478   */
25479
25480  Component.registerComponent('Player', Player);
25481
25482  /**
25483   * @file plugin.js
25484   */
25485
25486  /**
25487   * The base plugin name.
25488   *
25489   * @private
25490   * @constant
25491   * @type {string}
25492   */
25493  var BASE_PLUGIN_NAME = 'plugin';
25494
25495  /**
25496   * The key on which a player's active plugins cache is stored.
25497   *
25498   * @private
25499   * @constant
25500   * @type     {string}
25501   */
25502  var PLUGIN_CACHE_KEY = 'activePlugins_';
25503
25504  /**
25505   * Stores registered plugins in a private space.
25506   *
25507   * @private
25508   * @type    {Object}
25509   */
25510  var pluginStorage = {};
25511
25512  /**
25513   * Reports whether or not a plugin has been registered.
25514   *
25515   * @private
25516   * @param   {string} name
25517   *          The name of a plugin.
25518   *
25519   * @returns {boolean}
25520   *          Whether or not the plugin has been registered.
25521   */
25522  var pluginExists = function pluginExists(name) {
25523    return pluginStorage.hasOwnProperty(name);
25524  };
25525
25526  /**
25527   * Get a single registered plugin by name.
25528   *
25529   * @private
25530   * @param   {string} name
25531   *          The name of a plugin.
25532   *
25533   * @returns {Function|undefined}
25534   *          The plugin (or undefined).
25535   */
25536  var getPlugin = function getPlugin(name) {
25537    return pluginExists(name) ? pluginStorage[name] : undefined;
25538  };
25539
25540  /**
25541   * Marks a plugin as "active" on a player.
25542   *
25543   * Also, ensures that the player has an object for tracking active plugins.
25544   *
25545   * @private
25546   * @param   {Player} player
25547   *          A Video.js player instance.
25548   *
25549   * @param   {string} name
25550   *          The name of a plugin.
25551   */
25552  var markPluginAsActive = function markPluginAsActive(player, name) {
25553    player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {};
25554    player[PLUGIN_CACHE_KEY][name] = true;
25555  };
25556
25557  /**
25558   * Triggers a pair of plugin setup events.
25559   *
25560   * @private
25561   * @param  {Player} player
25562   *         A Video.js player instance.
25563   *
25564   * @param  {Plugin~PluginEventHash} hash
25565   *         A plugin event hash.
25566   *
25567   * @param  {Boolean} [before]
25568   *         If true, prefixes the event name with "before". In other words,
25569   *         use this to trigger "beforepluginsetup" instead of "pluginsetup".
25570   */
25571  var triggerSetupEvent = function triggerSetupEvent(player, hash, before) {
25572    var eventName = (before ? 'before' : '') + 'pluginsetup';
25573
25574    player.trigger(eventName, hash);
25575    player.trigger(eventName + ':' + hash.name, hash);
25576  };
25577
25578  /**
25579   * Takes a basic plugin function and returns a wrapper function which marks
25580   * on the player that the plugin has been activated.
25581   *
25582   * @private
25583   * @param   {string} name
25584   *          The name of the plugin.
25585   *
25586   * @param   {Function} plugin
25587   *          The basic plugin.
25588   *
25589   * @returns {Function}
25590   *          A wrapper function for the given plugin.
25591   */
25592  var createBasicPlugin = function createBasicPlugin(name, plugin) {
25593    var basicPluginWrapper = function basicPluginWrapper() {
25594
25595      // We trigger the "beforepluginsetup" and "pluginsetup" events on the player
25596      // regardless, but we want the hash to be consistent with the hash provided
25597      // for advanced plugins.
25598      //
25599      // The only potentially counter-intuitive thing here is the `instance` in
25600      // the "pluginsetup" event is the value returned by the `plugin` function.
25601      triggerSetupEvent(this, { name: name, plugin: plugin, instance: null }, true);
25602
25603      var instance = plugin.apply(this, arguments);
25604
25605      markPluginAsActive(this, name);
25606      triggerSetupEvent(this, { name: name, plugin: plugin, instance: instance });
25607
25608      return instance;
25609    };
25610
25611    Object.keys(plugin).forEach(function (prop) {
25612      basicPluginWrapper[prop] = plugin[prop];
25613    });
25614
25615    return basicPluginWrapper;
25616  };
25617
25618  /**
25619   * Takes a plugin sub-class and returns a factory function for generating
25620   * instances of it.
25621   *
25622   * This factory function will replace itself with an instance of the requested
25623   * sub-class of Plugin.
25624   *
25625   * @private
25626   * @param   {string} name
25627   *          The name of the plugin.
25628   *
25629   * @param   {Plugin} PluginSubClass
25630   *          The advanced plugin.
25631   *
25632   * @returns {Function}
25633   */
25634  var createPluginFactory = function createPluginFactory(name, PluginSubClass) {
25635
25636    // Add a `name` property to the plugin prototype so that each plugin can
25637    // refer to itself by name.
25638    PluginSubClass.prototype.name = name;
25639
25640    return function () {
25641      triggerSetupEvent(this, { name: name, plugin: PluginSubClass, instance: null }, true);
25642
25643      for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
25644        args[_key] = arguments[_key];
25645      }
25646
25647      var instance = new (Function.prototype.bind.apply(PluginSubClass, [null].concat([this].concat(args))))();
25648
25649      // The plugin is replaced by a function that returns the current instance.
25650      this[name] = function () {
25651        return instance;
25652      };
25653
25654      triggerSetupEvent(this, instance.getEventHash());
25655
25656      return instance;
25657    };
25658  };
25659
25660  /**
25661   * Parent class for all advanced plugins.
25662   *
25663   * @mixes   module:evented~EventedMixin
25664   * @mixes   module:stateful~StatefulMixin
25665   * @fires   Player#beforepluginsetup
25666   * @fires   Player#beforepluginsetup:$name
25667   * @fires   Player#pluginsetup
25668   * @fires   Player#pluginsetup:$name
25669   * @listens Player#dispose
25670   * @throws  {Error}
25671   *          If attempting to instantiate the base {@link Plugin} class
25672   *          directly instead of via a sub-class.
25673   */
25674
25675  var Plugin = function () {
25676
25677    /**
25678     * Creates an instance of this class.
25679     *
25680     * Sub-classes should call `super` to ensure plugins are properly initialized.
25681     *
25682     * @param {Player} player
25683     *        A Video.js player instance.
25684     */
25685    function Plugin(player) {
25686      classCallCheck(this, Plugin);
25687
25688      if (this.constructor === Plugin) {
25689        throw new Error('Plugin must be sub-classed; not directly instantiated.');
25690      }
25691
25692      this.player = player;
25693
25694      // Make this object evented, but remove the added `trigger` method so we
25695      // use the prototype version instead.
25696      evented(this);
25697      delete this.trigger;
25698
25699      stateful(this, this.constructor.defaultState);
25700      markPluginAsActive(player, this.name);
25701
25702      // Auto-bind the dispose method so we can use it as a listener and unbind
25703      // it later easily.
25704      this.dispose = bind(this, this.dispose);
25705
25706      // If the player is disposed, dispose the plugin.
25707      player.on('dispose', this.dispose);
25708    }
25709
25710    /**
25711     * Get the version of the plugin that was set on <pluginName>.VERSION
25712     */
25713
25714
25715    Plugin.prototype.version = function version() {
25716      return this.constructor.VERSION;
25717    };
25718
25719    /**
25720     * Each event triggered by plugins includes a hash of additional data with
25721     * conventional properties.
25722     *
25723     * This returns that object or mutates an existing hash.
25724     *
25725     * @param   {Object} [hash={}]
25726     *          An object to be used as event an event hash.
25727     *
25728     * @returns {Plugin~PluginEventHash}
25729     *          An event hash object with provided properties mixed-in.
25730     */
25731
25732
25733    Plugin.prototype.getEventHash = function getEventHash() {
25734      var hash = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
25735
25736      hash.name = this.name;
25737      hash.plugin = this.constructor;
25738      hash.instance = this;
25739      return hash;
25740    };
25741
25742    /**
25743     * Triggers an event on the plugin object and overrides
25744     * {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}.
25745     *
25746     * @param   {string|Object} event
25747     *          An event type or an object with a type property.
25748     *
25749     * @param   {Object} [hash={}]
25750     *          Additional data hash to merge with a
25751     *          {@link Plugin~PluginEventHash|PluginEventHash}.
25752     *
25753     * @returns {boolean}
25754     *          Whether or not default was prevented.
25755     */
25756
25757
25758    Plugin.prototype.trigger = function trigger$$1(event) {
25759      var hash = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
25760
25761      return trigger(this.eventBusEl_, event, this.getEventHash(hash));
25762    };
25763
25764    /**
25765     * Handles "statechanged" events on the plugin. No-op by default, override by
25766     * subclassing.
25767     *
25768     * @abstract
25769     * @param    {Event} e
25770     *           An event object provided by a "statechanged" event.
25771     *
25772     * @param    {Object} e.changes
25773     *           An object describing changes that occurred with the "statechanged"
25774     *           event.
25775     */
25776
25777
25778    Plugin.prototype.handleStateChanged = function handleStateChanged(e) {};
25779
25780    /**
25781     * Disposes a plugin.
25782     *
25783     * Subclasses can override this if they want, but for the sake of safety,
25784     * it's probably best to subscribe the "dispose" event.
25785     *
25786     * @fires Plugin#dispose
25787     */
25788
25789
25790    Plugin.prototype.dispose = function dispose() {
25791      var name = this.name,
25792          player = this.player;
25793
25794      /**
25795       * Signals that a advanced plugin is about to be disposed.
25796       *
25797       * @event Plugin#dispose
25798       * @type  {EventTarget~Event}
25799       */
25800
25801      this.trigger('dispose');
25802      this.off();
25803      player.off('dispose', this.dispose);
25804
25805      // Eliminate any possible sources of leaking memory by clearing up
25806      // references between the player and the plugin instance and nulling out
25807      // the plugin's state and replacing methods with a function that throws.
25808      player[PLUGIN_CACHE_KEY][name] = false;
25809      this.player = this.state = null;
25810
25811      // Finally, replace the plugin name on the player with a new factory
25812      // function, so that the plugin is ready to be set up again.
25813      player[name] = createPluginFactory(name, pluginStorage[name]);
25814    };
25815
25816    /**
25817     * Determines if a plugin is a basic plugin (i.e. not a sub-class of `Plugin`).
25818     *
25819     * @param   {string|Function} plugin
25820     *          If a string, matches the name of a plugin. If a function, will be
25821     *          tested directly.
25822     *
25823     * @returns {boolean}
25824     *          Whether or not a plugin is a basic plugin.
25825     */
25826
25827
25828    Plugin.isBasic = function isBasic(plugin) {
25829      var p = typeof plugin === 'string' ? getPlugin(plugin) : plugin;
25830
25831      return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype);
25832    };
25833
25834    /**
25835     * Register a Video.js plugin.
25836     *
25837     * @param   {string} name
25838     *          The name of the plugin to be registered. Must be a string and
25839     *          must not match an existing plugin or a method on the `Player`
25840     *          prototype.
25841     *
25842     * @param   {Function} plugin
25843     *          A sub-class of `Plugin` or a function for basic plugins.
25844     *
25845     * @returns {Function}
25846     *          For advanced plugins, a factory function for that plugin. For
25847     *          basic plugins, a wrapper function that initializes the plugin.
25848     */
25849
25850
25851    Plugin.registerPlugin = function registerPlugin(name, plugin) {
25852      if (typeof name !== 'string') {
25853        throw new Error('Illegal plugin name, "' + name + '", must be a string, was ' + (typeof name === 'undefined' ? 'undefined' : _typeof(name)) + '.');
25854      }
25855
25856      if (pluginExists(name)) {
25857        log$1.warn('A plugin named "' + name + '" already exists. You may want to avoid re-registering plugins!');
25858      } else if (Player.prototype.hasOwnProperty(name)) {
25859        throw new Error('Illegal plugin name, "' + name + '", cannot share a name with an existing player method!');
25860      }
25861
25862      if (typeof plugin !== 'function') {
25863        throw new Error('Illegal plugin for "' + name + '", must be a function, was ' + (typeof plugin === 'undefined' ? 'undefined' : _typeof(plugin)) + '.');
25864      }
25865
25866      pluginStorage[name] = plugin;
25867
25868      // Add a player prototype method for all sub-classed plugins (but not for
25869      // the base Plugin class).
25870      if (name !== BASE_PLUGIN_NAME) {
25871        if (Plugin.isBasic(plugin)) {
25872          Player.prototype[name] = createBasicPlugin(name, plugin);
25873        } else {
25874          Player.prototype[name] = createPluginFactory(name, plugin);
25875        }
25876      }
25877
25878      return plugin;
25879    };
25880
25881    /**
25882     * De-register a Video.js plugin.
25883     *
25884     * @param {string} name
25885     *        The name of the plugin to be deregistered.
25886     */
25887
25888
25889    Plugin.deregisterPlugin = function deregisterPlugin(name) {
25890      if (name === BASE_PLUGIN_NAME) {
25891        throw new Error('Cannot de-register base plugin.');
25892      }
25893      if (pluginExists(name)) {
25894        delete pluginStorage[name];
25895        delete Player.prototype[name];
25896      }
25897    };
25898
25899    /**
25900     * Gets an object containing multiple Video.js plugins.
25901     *
25902     * @param   {Array} [names]
25903     *          If provided, should be an array of plugin names. Defaults to _all_
25904     *          plugin names.
25905     *
25906     * @returns {Object|undefined}
25907     *          An object containing plugin(s) associated with their name(s) or
25908     *          `undefined` if no matching plugins exist).
25909     */
25910
25911
25912    Plugin.getPlugins = function getPlugins() {
25913      var names = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Object.keys(pluginStorage);
25914
25915      var result = void 0;
25916
25917      names.forEach(function (name) {
25918        var plugin = getPlugin(name);
25919
25920        if (plugin) {
25921          result = result || {};
25922          result[name] = plugin;
25923        }
25924      });
25925
25926      return result;
25927    };
25928
25929    /**
25930     * Gets a plugin's version, if available
25931     *
25932     * @param   {string} name
25933     *          The name of a plugin.
25934     *
25935     * @returns {string}
25936     *          The plugin's version or an empty string.
25937     */
25938
25939
25940    Plugin.getPluginVersion = function getPluginVersion(name) {
25941      var plugin = getPlugin(name);
25942
25943      return plugin && plugin.VERSION || '';
25944    };
25945
25946    return Plugin;
25947  }();
25948
25949  /**
25950   * Gets a plugin by name if it exists.
25951   *
25952   * @static
25953   * @method   getPlugin
25954   * @memberOf Plugin
25955   * @param    {string} name
25956   *           The name of a plugin.
25957   *
25958   * @returns  {Function|undefined}
25959   *           The plugin (or `undefined`).
25960   */
25961
25962
25963  Plugin.getPlugin = getPlugin;
25964
25965  /**
25966   * The name of the base plugin class as it is registered.
25967   *
25968   * @type {string}
25969   */
25970  Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME;
25971
25972  Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin);
25973
25974  /**
25975   * Documented in player.js
25976   *
25977   * @ignore
25978   */
25979  Player.prototype.usingPlugin = function (name) {
25980    return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true;
25981  };
25982
25983  /**
25984   * Documented in player.js
25985   *
25986   * @ignore
25987   */
25988  Player.prototype.hasPlugin = function (name) {
25989    return !!pluginExists(name);
25990  };
25991
25992  /**
25993   * @file extend.js
25994   * @module extend
25995   */
25996
25997  /**
25998   * A combination of node inherits and babel's inherits (after transpile).
25999   * Both work the same but node adds `super_` to the subClass
26000   * and Bable adds the superClass as __proto__. Both seem useful.
26001   *
26002   * @param {Object} subClass
26003   *        The class to inherit to
26004   *
26005   * @param {Object} superClass
26006   *        The class to inherit from
26007   *
26008   * @private
26009   */
26010  var _inherits = function _inherits(subClass, superClass) {
26011    if (typeof superClass !== 'function' && superClass !== null) {
26012      throw new TypeError('Super expression must either be null or a function, not ' + (typeof superClass === 'undefined' ? 'undefined' : _typeof(superClass)));
26013    }
26014
26015    subClass.prototype = Object.create(superClass && superClass.prototype, {
26016      constructor: {
26017        value: subClass,
26018        enumerable: false,
26019        writable: true,
26020        configurable: true
26021      }
26022    });
26023
26024    if (superClass) {
26025      // node
26026      subClass.super_ = superClass;
26027    }
26028  };
26029
26030  /**
26031   * Function for subclassing using the same inheritance that
26032   * videojs uses internally
26033   *
26034   * @static
26035   * @const
26036   *
26037   * @param {Object} superClass
26038   *        The class to inherit from
26039   *
26040   * @param {Object} [subClassMethods={}]
26041   *        The class to inherit to
26042   *
26043   * @return {Object}
26044   *         The new object with subClassMethods that inherited superClass.
26045   */
26046  var extendFn = function extendFn(superClass) {
26047    var subClassMethods = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
26048
26049    var subClass = function subClass() {
26050      superClass.apply(this, arguments);
26051    };
26052
26053    var methods = {};
26054
26055    if ((typeof subClassMethods === 'undefined' ? 'undefined' : _typeof(subClassMethods)) === 'object') {
26056      if (subClassMethods.constructor !== Object.prototype.constructor) {
26057        subClass = subClassMethods.constructor;
26058      }
26059      methods = subClassMethods;
26060    } else if (typeof subClassMethods === 'function') {
26061      subClass = subClassMethods;
26062    }
26063
26064    _inherits(subClass, superClass);
26065
26066    // Extend subObj's prototype with functions and other properties from props
26067    for (var name in methods) {
26068      if (methods.hasOwnProperty(name)) {
26069        subClass.prototype[name] = methods[name];
26070      }
26071    }
26072
26073    return subClass;
26074  };
26075
26076  /**
26077   * @file video.js
26078   * @module videojs
26079   */
26080
26081  /**
26082   * Normalize an `id` value by trimming off a leading `#`
26083   *
26084   * @param   {string} id
26085   *          A string, maybe with a leading `#`.
26086   *
26087   * @returns {string}
26088   *          The string, without any leading `#`.
26089   */
26090  var normalizeId = function normalizeId(id) {
26091    return id.indexOf('#') === 0 ? id.slice(1) : id;
26092  };
26093
26094  /**
26095   * Doubles as the main function for users to create a player instance and also
26096   * the main library object.
26097   * The `videojs` function can be used to initialize or retrieve a player.
26098    *
26099   * @param {string|Element} id
26100   *        Video element or video element ID
26101   *
26102   * @param {Object} [options]
26103   *        Optional options object for config/settings
26104   *
26105   * @param {Component~ReadyCallback} [ready]
26106   *        Optional ready callback
26107   *
26108   * @return {Player}
26109   *         A player instance
26110   */
26111  function videojs$1(id, options, ready) {
26112    var player = videojs$1.getPlayer(id);
26113
26114    if (player) {
26115      if (options) {
26116        log$1.warn('Player "' + id + '" is already initialised. Options will not be applied.');
26117      }
26118      if (ready) {
26119        player.ready(ready);
26120      }
26121      return player;
26122    }
26123
26124    var el = typeof id === 'string' ? $('#' + normalizeId(id)) : id;
26125
26126    if (!isEl(el)) {
26127      throw new TypeError('The element or ID supplied is not valid. (videojs)');
26128    }
26129
26130    if (!document_1.body.contains(el)) {
26131      log$1.warn('The element supplied is not included in the DOM');
26132    }
26133
26134    options = options || {};
26135
26136    videojs$1.hooks('beforesetup').forEach(function (hookFunction) {
26137      var opts = hookFunction(el, mergeOptions(options));
26138
26139      if (!isObject(opts) || Array.isArray(opts)) {
26140        log$1.error('please return an object in beforesetup hooks');
26141        return;
26142      }
26143
26144      options = mergeOptions(options, opts);
26145    });
26146
26147    // We get the current "Player" component here in case an integration has
26148    // replaced it with a custom player.
26149    var PlayerComponent = Component.getComponent('Player');
26150
26151    player = new PlayerComponent(el, options, ready);
26152
26153    videojs$1.hooks('setup').forEach(function (hookFunction) {
26154      return hookFunction(player);
26155    });
26156
26157    return player;
26158  }
26159
26160  /**
26161   * An Object that contains lifecycle hooks as keys which point to an array
26162   * of functions that are run when a lifecycle is triggered
26163   */
26164  videojs$1.hooks_ = {};
26165
26166  /**
26167   * Get a list of hooks for a specific lifecycle
26168   * @function videojs.hooks
26169   *
26170   * @param {string} type
26171   *        the lifecyle to get hooks from
26172   *
26173   * @param {Function|Function[]} [fn]
26174   *        Optionally add a hook (or hooks) to the lifecycle that your are getting.
26175   *
26176   * @return {Array}
26177   *         an array of hooks, or an empty array if there are none.
26178   */
26179  videojs$1.hooks = function (type, fn) {
26180    videojs$1.hooks_[type] = videojs$1.hooks_[type] || [];
26181    if (fn) {
26182      videojs$1.hooks_[type] = videojs$1.hooks_[type].concat(fn);
26183    }
26184    return videojs$1.hooks_[type];
26185  };
26186
26187  /**
26188   * Add a function hook to a specific videojs lifecycle.
26189   *
26190   * @param {string} type
26191   *        the lifecycle to hook the function to.
26192   *
26193   * @param {Function|Function[]}
26194   *        The function or array of functions to attach.
26195   */
26196  videojs$1.hook = function (type, fn) {
26197    videojs$1.hooks(type, fn);
26198  };
26199
26200  /**
26201   * Add a function hook that will only run once to a specific videojs lifecycle.
26202   *
26203   * @param {string} type
26204   *        the lifecycle to hook the function to.
26205   *
26206   * @param {Function|Function[]}
26207   *        The function or array of functions to attach.
26208   */
26209  videojs$1.hookOnce = function (type, fn) {
26210    videojs$1.hooks(type, [].concat(fn).map(function (original) {
26211      var wrapper = function wrapper() {
26212        videojs$1.removeHook(type, wrapper);
26213        return original.apply(undefined, arguments);
26214      };
26215
26216      return wrapper;
26217    }));
26218  };
26219
26220  /**
26221   * Remove a hook from a specific videojs lifecycle.
26222   *
26223   * @param {string} type
26224   *        the lifecycle that the function hooked to
26225   *
26226   * @param {Function} fn
26227   *        The hooked function to remove
26228   *
26229   * @return {boolean}
26230   *         The function that was removed or undef
26231   */
26232  videojs$1.removeHook = function (type, fn) {
26233    var index = videojs$1.hooks(type).indexOf(fn);
26234
26235    if (index <= -1) {
26236      return false;
26237    }
26238
26239    videojs$1.hooks_[type] = videojs$1.hooks_[type].slice();
26240    videojs$1.hooks_[type].splice(index, 1);
26241
26242    return true;
26243  };
26244
26245  // Add default styles
26246  if (window_1.VIDEOJS_NO_DYNAMIC_STYLE !== true && isReal()) {
26247    var style$1 = $('.vjs-styles-defaults');
26248
26249    if (!style$1) {
26250      style$1 = createStyleElement('vjs-styles-defaults');
26251      var head = $('head');
26252
26253      if (head) {
26254        head.insertBefore(style$1, head.firstChild);
26255      }
26256      setTextContent(style$1, '\n      .video-js {\n        width: 300px;\n        height: 150px;\n      }\n\n      .vjs-fluid {\n        padding-top: 56.25%\n      }\n    ');
26257    }
26258  }
26259
26260  // Run Auto-load players
26261  // You have to wait at least once in case this script is loaded after your
26262  // video in the DOM (weird behavior only with minified version)
26263  autoSetupTimeout(1, videojs$1);
26264
26265  /**
26266   * Current software version. Follows semver.
26267   *
26268   * @type {string}
26269   */
26270  videojs$1.VERSION = version;
26271
26272  /**
26273   * The global options object. These are the settings that take effect
26274   * if no overrides are specified when the player is created.
26275   *
26276   * @type {Object}
26277   */
26278  videojs$1.options = Player.prototype.options_;
26279
26280  /**
26281   * Get an object with the currently created players, keyed by player ID
26282   *
26283   * @return {Object}
26284   *         The created players
26285   */
26286  videojs$1.getPlayers = function () {
26287    return Player.players;
26288  };
26289
26290  /**
26291   * Get a single player based on an ID or DOM element.
26292   *
26293   * This is useful if you want to check if an element or ID has an associated
26294   * Video.js player, but not create one if it doesn't.
26295   *
26296   * @param   {string|Element} id
26297   *          An HTML element - `<video>`, `<audio>`, or `<video-js>` -
26298   *          or a string matching the `id` of such an element.
26299   *
26300   * @returns {Player|undefined}
26301   *          A player instance or `undefined` if there is no player instance
26302   *          matching the argument.
26303   */
26304  videojs$1.getPlayer = function (id) {
26305    var players = Player.players;
26306    var tag = void 0;
26307
26308    if (typeof id === 'string') {
26309      var nId = normalizeId(id);
26310      var player = players[nId];
26311
26312      if (player) {
26313        return player;
26314      }
26315
26316      tag = $('#' + nId);
26317    } else {
26318      tag = id;
26319    }
26320
26321    if (isEl(tag)) {
26322      var _tag = tag,
26323          _player = _tag.player,
26324          playerId = _tag.playerId;
26325
26326      // Element may have a `player` property referring to an already created
26327      // player instance. If so, return that.
26328
26329      if (_player || players[playerId]) {
26330        return _player || players[playerId];
26331      }
26332    }
26333  };
26334
26335  /**
26336   * Returns an array of all current players.
26337   *
26338   * @return {Array}
26339   *         An array of all players. The array will be in the order that
26340   *         `Object.keys` provides, which could potentially vary between
26341   *         JavaScript engines.
26342   *
26343   */
26344  videojs$1.getAllPlayers = function () {
26345    return (
26346
26347      // Disposed players leave a key with a `null` value, so we need to make sure
26348      // we filter those out.
26349      Object.keys(Player.players).map(function (k) {
26350        return Player.players[k];
26351      }).filter(Boolean)
26352    );
26353  };
26354
26355  /**
26356   * Expose players object.
26357   *
26358   * @memberOf videojs
26359   * @property {Object} players
26360   */
26361  videojs$1.players = Player.players;
26362
26363  /**
26364   * Get a component class object by name
26365   *
26366   * @borrows Component.getComponent as videojs.getComponent
26367   */
26368  videojs$1.getComponent = Component.getComponent;
26369
26370  /**
26371   * Register a component so it can referred to by name. Used when adding to other
26372   * components, either through addChild `component.addChild('myComponent')` or through
26373   * default children options  `{ children: ['myComponent'] }`.
26374   *
26375   * > NOTE: You could also just initialize the component before adding.
26376   * `component.addChild(new MyComponent());`
26377   *
26378   * @param {string} name
26379   *        The class name of the component
26380   *
26381   * @param {Component} comp
26382   *        The component class
26383   *
26384   * @return {Component}
26385   *         The newly registered component
26386   */
26387  videojs$1.registerComponent = function (name$$1, comp) {
26388    if (Tech.isTech(comp)) {
26389      log$1.warn('The ' + name$$1 + ' tech was registered as a component. It should instead be registered using videojs.registerTech(name, tech)');
26390    }
26391
26392    Component.registerComponent.call(Component, name$$1, comp);
26393  };
26394
26395  /**
26396   * Get a Tech class object by name
26397   *
26398   * @borrows Tech.getTech as videojs.getTech
26399   */
26400  videojs$1.getTech = Tech.getTech;
26401
26402  /**
26403   * Register a Tech so it can referred to by name.
26404   * This is used in the tech order for the player.
26405   *
26406   * @borrows Tech.registerTech as videojs.registerTech
26407   */
26408  videojs$1.registerTech = Tech.registerTech;
26409
26410  /**
26411   * Register a middleware to a source type.
26412   *
26413   * @param {String} type A string representing a MIME type.
26414   * @param {function(player):object} middleware A middleware factory that takes a player.
26415   */
26416  videojs$1.use = use;
26417
26418  /**
26419   * An object that can be returned by a middleware to signify
26420   * that the middleware is being terminated.
26421   *
26422   * @type {object}
26423   * @memberOf {videojs}
26424   * @property {object} middleware.TERMINATOR
26425   */
26426  Object.defineProperty(videojs$1, 'middleware', {
26427    value: {},
26428    writeable: false,
26429    enumerable: true
26430  });
26431
26432  Object.defineProperty(videojs$1.middleware, 'TERMINATOR', {
26433    value: TERMINATOR,
26434    writeable: false,
26435    enumerable: true
26436  });
26437
26438  /**
26439   * A suite of browser and device tests from {@link browser}.
26440   *
26441   * @type {Object}
26442   * @private
26443   */
26444  videojs$1.browser = browser;
26445
26446  /**
26447   * Whether or not the browser supports touch events. Included for backward
26448   * compatibility with 4.x, but deprecated. Use `videojs.browser.TOUCH_ENABLED`
26449   * instead going forward.
26450   *
26451   * @deprecated since version 5.0
26452   * @type {boolean}
26453   */
26454  videojs$1.TOUCH_ENABLED = TOUCH_ENABLED;
26455
26456  /**
26457   * Subclass an existing class
26458   * Mimics ES6 subclassing with the `extend` keyword
26459   *
26460   * @borrows extend:extendFn as videojs.extend
26461   */
26462  videojs$1.extend = extendFn;
26463
26464  /**
26465   * Merge two options objects recursively
26466   * Performs a deep merge like lodash.merge but **only merges plain objects**
26467   * (not arrays, elements, anything else)
26468   * Other values will be copied directly from the second object.
26469   *
26470   * @borrows merge-options:mergeOptions as videojs.mergeOptions
26471   */
26472  videojs$1.mergeOptions = mergeOptions;
26473
26474  /**
26475   * Change the context (this) of a function
26476   *
26477   * > NOTE: as of v5.0 we require an ES5 shim, so you should use the native
26478   * `function() {}.bind(newContext);` instead of this.
26479   *
26480   * @borrows fn:bind as videojs.bind
26481   */
26482  videojs$1.bind = bind;
26483
26484  /**
26485   * Register a Video.js plugin.
26486   *
26487   * @borrows plugin:registerPlugin as videojs.registerPlugin
26488   * @method registerPlugin
26489   *
26490   * @param  {string} name
26491   *         The name of the plugin to be registered. Must be a string and
26492   *         must not match an existing plugin or a method on the `Player`
26493   *         prototype.
26494   *
26495   * @param  {Function} plugin
26496   *         A sub-class of `Plugin` or a function for basic plugins.
26497   *
26498   * @return {Function}
26499   *         For advanced plugins, a factory function for that plugin. For
26500   *         basic plugins, a wrapper function that initializes the plugin.
26501   */
26502  videojs$1.registerPlugin = Plugin.registerPlugin;
26503
26504  /**
26505   * Deprecated method to register a plugin with Video.js
26506   *
26507   * @deprecated
26508   *        videojs.plugin() is deprecated; use videojs.registerPlugin() instead
26509   *
26510   * @param {string} name
26511   *        The plugin name
26512   *
26513   * @param {Plugin|Function} plugin
26514   *         The plugin sub-class or function
26515   */
26516  videojs$1.plugin = function (name$$1, plugin) {
26517    log$1.warn('videojs.plugin() is deprecated; use videojs.registerPlugin() instead');
26518    return Plugin.registerPlugin(name$$1, plugin);
26519  };
26520
26521  /**
26522   * Gets an object containing multiple Video.js plugins.
26523   *
26524   * @param  {Array} [names]
26525   *         If provided, should be an array of plugin names. Defaults to _all_
26526   *         plugin names.
26527   *
26528   * @return {Object|undefined}
26529   *         An object containing plugin(s) associated with their name(s) or
26530   *         `undefined` if no matching plugins exist).
26531   */
26532  videojs$1.getPlugins = Plugin.getPlugins;
26533
26534  /**
26535   * Gets a plugin by name if it exists.
26536   *
26537   * @param  {string} name
26538   *         The name of a plugin.
26539   *
26540   * @return {Function|undefined}
26541   *         The plugin (or `undefined`).
26542   */
26543  videojs$1.getPlugin = Plugin.getPlugin;
26544
26545  /**
26546   * Gets a plugin's version, if available
26547   *
26548   * @param  {string} name
26549   *         The name of a plugin.
26550   *
26551   * @return {string}
26552   *         The plugin's version or an empty string.
26553   */
26554  videojs$1.getPluginVersion = Plugin.getPluginVersion;
26555
26556  /**
26557   * Adding languages so that they're available to all players.
26558   * Example: `videojs.addLanguage('es', { 'Hello': 'Hola' });`
26559   *
26560   * @param {string} code
26561   *        The language code or dictionary property
26562   *
26563   * @param {Object} data
26564   *        The data values to be translated
26565   *
26566   * @return {Object}
26567   *         The resulting language dictionary object
26568   */
26569  videojs$1.addLanguage = function (code, data) {
26570    var _mergeOptions;
26571
26572    code = ('' + code).toLowerCase();
26573
26574    videojs$1.options.languages = mergeOptions(videojs$1.options.languages, (_mergeOptions = {}, _mergeOptions[code] = data, _mergeOptions));
26575
26576    return videojs$1.options.languages[code];
26577  };
26578
26579  /**
26580   * Log messages
26581   *
26582   * @borrows log:log as videojs.log
26583   */
26584  videojs$1.log = log$1;
26585
26586  /**
26587   * Creates an emulated TimeRange object.
26588   *
26589   * @borrows time-ranges:createTimeRanges as videojs.createTimeRange
26590   */
26591  /**
26592   * @borrows time-ranges:createTimeRanges as videojs.createTimeRanges
26593   */
26594  videojs$1.createTimeRange = videojs$1.createTimeRanges = createTimeRanges;
26595
26596  /**
26597   * Format seconds as a time string, H:MM:SS or M:SS
26598   * Supplying a guide (in seconds) will force a number of leading zeros
26599   * to cover the length of the guide
26600   *
26601   * @borrows format-time:formatTime as videojs.formatTime
26602   */
26603  videojs$1.formatTime = formatTime;
26604
26605  /**
26606   * Replaces format-time with a custom implementation, to be used in place of the default.
26607   *
26608   * @borrows format-time:setFormatTime as videojs.setFormatTime
26609   *
26610   * @method setFormatTime
26611   *
26612   * @param {Function} customFn
26613   *        A custom format-time function which will be called with the current time and guide (in seconds) as arguments.
26614   *        Passed fn should return a string.
26615   */
26616  videojs$1.setFormatTime = setFormatTime;
26617
26618  /**
26619   * Resets format-time to the default implementation.
26620   *
26621   * @borrows format-time:resetFormatTime as videojs.resetFormatTime
26622   *
26623   * @method resetFormatTime
26624   */
26625  videojs$1.resetFormatTime = resetFormatTime;
26626
26627  /**
26628   * Resolve and parse the elements of a URL
26629   *
26630   * @borrows url:parseUrl as videojs.parseUrl
26631   *
26632   */
26633  videojs$1.parseUrl = parseUrl;
26634
26635  /**
26636   * Returns whether the url passed is a cross domain request or not.
26637   *
26638   * @borrows url:isCrossOrigin as videojs.isCrossOrigin
26639   */
26640  videojs$1.isCrossOrigin = isCrossOrigin;
26641
26642  /**
26643   * Event target class.
26644   *
26645   * @borrows EventTarget as videojs.EventTarget
26646   */
26647  videojs$1.EventTarget = EventTarget;
26648
26649  /**
26650   * Add an event listener to element
26651   * It stores the handler function in a separate cache object
26652   * and adds a generic handler to the element's event,
26653   * along with a unique id (guid) to the element.
26654   *
26655   * @borrows events:on as videojs.on
26656   */
26657  videojs$1.on = on;
26658
26659  /**
26660   * Trigger a listener only once for an event
26661   *
26662   * @borrows events:one as videojs.one
26663   */
26664  videojs$1.one = one;
26665
26666  /**
26667   * Removes event listeners from an element
26668   *
26669   * @borrows events:off as videojs.off
26670   */
26671  videojs$1.off = off;
26672
26673  /**
26674   * Trigger an event for an element
26675   *
26676   * @borrows events:trigger as videojs.trigger
26677   */
26678  videojs$1.trigger = trigger;
26679
26680  /**
26681   * A cross-browser XMLHttpRequest wrapper. Here's a simple example:
26682   *
26683   * @param {Object} options
26684   *        settings for the request.
26685   *
26686   * @return {XMLHttpRequest|XDomainRequest}
26687   *         The request object.
26688   *
26689   * @see https://github.com/Raynos/xhr
26690   */
26691  videojs$1.xhr = xhr;
26692
26693  /**
26694   * TextTrack class
26695   *
26696   * @borrows TextTrack as videojs.TextTrack
26697   */
26698  videojs$1.TextTrack = TextTrack;
26699
26700  /**
26701   * export the AudioTrack class so that source handlers can create
26702   * AudioTracks and then add them to the players AudioTrackList
26703   *
26704   * @borrows AudioTrack as videojs.AudioTrack
26705   */
26706  videojs$1.AudioTrack = AudioTrack;
26707
26708  /**
26709   * export the VideoTrack class so that source handlers can create
26710   * VideoTracks and then add them to the players VideoTrackList
26711   *
26712   * @borrows VideoTrack as videojs.VideoTrack
26713   */
26714  videojs$1.VideoTrack = VideoTrack;
26715
26716  /**
26717   * Determines, via duck typing, whether or not a value is a DOM element.
26718   *
26719   * @borrows dom:isEl as videojs.isEl
26720   * @deprecated Use videojs.dom.isEl() instead
26721   */
26722
26723  /**
26724   * Determines, via duck typing, whether or not a value is a text node.
26725   *
26726   * @borrows dom:isTextNode as videojs.isTextNode
26727   * @deprecated Use videojs.dom.isTextNode() instead
26728   */
26729
26730  /**
26731   * Creates an element and applies properties.
26732   *
26733   * @borrows dom:createEl as videojs.createEl
26734   * @deprecated Use videojs.dom.createEl() instead
26735   */
26736
26737  /**
26738   * Check if an element has a CSS class
26739   *
26740   * @borrows dom:hasElClass as videojs.hasClass
26741   * @deprecated Use videojs.dom.hasClass() instead
26742   */
26743
26744  /**
26745   * Add a CSS class name to an element
26746   *
26747   * @borrows dom:addElClass as videojs.addClass
26748   * @deprecated Use videojs.dom.addClass() instead
26749   */
26750
26751  /**
26752   * Remove a CSS class name from an element
26753   *
26754   * @borrows dom:removeElClass as videojs.removeClass
26755   * @deprecated Use videojs.dom.removeClass() instead
26756   */
26757
26758  /**
26759   * Adds or removes a CSS class name on an element depending on an optional
26760   * condition or the presence/absence of the class name.
26761   *
26762   * @borrows dom:toggleElClass as videojs.toggleClass
26763   * @deprecated Use videojs.dom.toggleClass() instead
26764   */
26765
26766  /**
26767   * Apply attributes to an HTML element.
26768   *
26769   * @borrows dom:setElAttributes as videojs.setAttribute
26770   * @deprecated Use videojs.dom.setAttributes() instead
26771   */
26772
26773  /**
26774   * Get an element's attribute values, as defined on the HTML tag
26775   * Attributes are not the same as properties. They're defined on the tag
26776   * or with setAttribute (which shouldn't be used with HTML)
26777   * This will return true or false for boolean attributes.
26778   *
26779   * @borrows dom:getElAttributes as videojs.getAttributes
26780   * @deprecated Use videojs.dom.getAttributes() instead
26781   */
26782
26783  /**
26784   * Empties the contents of an element.
26785   *
26786   * @borrows dom:emptyEl as videojs.emptyEl
26787   * @deprecated Use videojs.dom.emptyEl() instead
26788   */
26789
26790  /**
26791   * Normalizes and appends content to an element.
26792   *
26793   * The content for an element can be passed in multiple types and
26794   * combinations, whose behavior is as follows:
26795   *
26796   * - String
26797   *   Normalized into a text node.
26798   *
26799   * - Element, TextNode
26800   *   Passed through.
26801   *
26802   * - Array
26803   *   A one-dimensional array of strings, elements, nodes, or functions (which
26804   *   return single strings, elements, or nodes).
26805   *
26806   * - Function
26807   *   If the sole argument, is expected to produce a string, element,
26808   *   node, or array.
26809   *
26810   * @borrows dom:appendContents as videojs.appendContet
26811   * @deprecated Use videojs.dom.appendContent() instead
26812   */
26813
26814  /**
26815   * Normalizes and inserts content into an element; this is identical to
26816   * `appendContent()`, except it empties the element first.
26817   *
26818   * The content for an element can be passed in multiple types and
26819   * combinations, whose behavior is as follows:
26820   *
26821   * - String
26822   *   Normalized into a text node.
26823   *
26824   * - Element, TextNode
26825   *   Passed through.
26826   *
26827   * - Array
26828   *   A one-dimensional array of strings, elements, nodes, or functions (which
26829   *   return single strings, elements, or nodes).
26830   *
26831   * - Function
26832   *   If the sole argument, is expected to produce a string, element,
26833   *   node, or array.
26834   *
26835   * @borrows dom:insertContent as videojs.insertContent
26836   * @deprecated Use videojs.dom.insertContent() instead
26837   */
26838  ['isEl', 'isTextNode', 'createEl', 'hasClass', 'addClass', 'removeClass', 'toggleClass', 'setAttributes', 'getAttributes', 'emptyEl', 'appendContent', 'insertContent'].forEach(function (k) {
26839    videojs$1[k] = function () {
26840      log$1.warn('videojs.' + k + '() is deprecated; use videojs.dom.' + k + '() instead');
26841      return Dom[k].apply(null, arguments);
26842    };
26843  });
26844
26845  /**
26846   * A safe getComputedStyle.
26847   *
26848   * This is because in Firefox, if the player is loaded in an iframe with `display:none`,
26849   * then `getComputedStyle` returns `null`, so, we do a null-check to make sure
26850   * that the player doesn't break in these cases.
26851   * See https://bugzilla.mozilla.org/show_bug.cgi?id=548397 for more details.
26852   *
26853   * @borrows computed-style:computedStyle as videojs.computedStyle
26854   */
26855  videojs$1.computedStyle = computedStyle;
26856
26857  /**
26858   * Export the Dom utilities for use in external plugins
26859   * and Tech's
26860   */
26861  videojs$1.dom = Dom;
26862
26863  /**
26864   * Export the Url utilities for use in external plugins
26865   * and Tech's
26866   */
26867  videojs$1.url = Url;
26868
26869  var urlToolkit = createCommonjsModule(function (module, exports) {
26870    // see https://tools.ietf.org/html/rfc1808
26871
26872    /* jshint ignore:start */
26873    (function (root) {
26874      /* jshint ignore:end */
26875
26876      var URL_REGEX = /^((?:[a-zA-Z0-9+\-.]+:)?)(\/\/[^\/\;?#]*)?(.*?)??(;.*?)?(\?.*?)?(#.*?)?$/;
26877      var FIRST_SEGMENT_REGEX = /^([^\/;?#]*)(.*)$/;
26878      var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g;
26879      var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/).*?(?=\/)/g;
26880
26881      var URLToolkit = { // jshint ignore:line
26882        // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or //
26883        // E.g
26884        // With opts.alwaysNormalize = false (default, spec compliant)
26885        // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g
26886        // With opts.alwaysNormalize = true (not spec compliant)
26887        // http://a.com/b/cd + /e/f/../g => http://a.com/e/g
26888        buildAbsoluteURL: function buildAbsoluteURL(baseURL, relativeURL, opts) {
26889          opts = opts || {};
26890          // remove any remaining space and CRLF
26891          baseURL = baseURL.trim();
26892          relativeURL = relativeURL.trim();
26893          if (!relativeURL) {
26894            // 2a) If the embedded URL is entirely empty, it inherits the
26895            // entire base URL (i.e., is set equal to the base URL)
26896            // and we are done.
26897            if (!opts.alwaysNormalize) {
26898              return baseURL;
26899            }
26900            var basePartsForNormalise = URLToolkit.parseURL(baseURL);
26901            if (!basePartsForNormalise) {
26902              throw new Error('Error trying to parse base URL.');
26903            }
26904            basePartsForNormalise.path = URLToolkit.normalizePath(basePartsForNormalise.path);
26905            return URLToolkit.buildURLFromParts(basePartsForNormalise);
26906          }
26907          var relativeParts = URLToolkit.parseURL(relativeURL);
26908          if (!relativeParts) {
26909            throw new Error('Error trying to parse relative URL.');
26910          }
26911          if (relativeParts.scheme) {
26912            // 2b) If the embedded URL starts with a scheme name, it is
26913            // interpreted as an absolute URL and we are done.
vendor: 2,564 bytes, lines 26914-26963
26914            if (!opts.alwaysNormalize) {
26915              return relativeURL;
26916            }
26917            relativeParts.path = URLToolkit.normalizePath(relativeParts.path);
26918            return URLToolkit.buildURLFromParts(relativeParts);
26919          }
26920          var baseParts = URLToolkit.parseURL(baseURL);
26921          if (!baseParts) {
26922            throw new Error('Error trying to parse base URL.');
26923          }
26924          if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') {
26925            // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc
26926            // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a'
26927            var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path);
26928            baseParts.netLoc = pathParts[1];
26929            baseParts.path = pathParts[2];
26930          }
26931          if (baseParts.netLoc && !baseParts.path) {
26932            baseParts.path = '/';
26933          }
26934          var builtParts = {
26935            // 2c) Otherwise, the embedded URL inherits the scheme of
26936            // the base URL.
26937            scheme: baseParts.scheme,
26938            netLoc: relativeParts.netLoc,
26939            path: null,
26940            params: relativeParts.params,
26941            query: relativeParts.query,
26942            fragment: relativeParts.fragment
26943          };
26944          if (!relativeParts.netLoc) {
26945            // 3) If the embedded URL's <net_loc> is non-empty, we skip to
26946            // Step 7.  Otherwise, the embedded URL inherits the <net_loc>
26947            // (if any) of the base URL.
26948            builtParts.netLoc = baseParts.netLoc;
26949            // 4) If the embedded URL path is preceded by a slash "/", the
26950            // path is not relative and we skip to Step 7.
26951            if (relativeParts.path[0] !== '/') {
26952              if (!relativeParts.path) {
26953                // 5) If the embedded URL path is empty (and not preceded by a
26954                // slash), then the embedded URL inherits the base URL path
26955                builtParts.path = baseParts.path;
26956                // 5a) if the embedded URL's <params> is non-empty, we skip to
26957                // step 7; otherwise, it inherits the <params> of the base
26958                // URL (if any) and
26959                if (!relativeParts.params) {
26960                  builtParts.params = baseParts.params;
26961                  // 5b) if the embedded URL's <query> is non-empty, we skip to
26962                  // step 7; otherwise, it inherits the <query> of the base
26963                  // URL (if any) and we skip to step 7.
vendor: 63,872 bytes, lines 26964-28884
26964                  if (!relativeParts.query) {
26965                    builtParts.query = baseParts.query;
26966                  }
26967                }
26968              } else {
26969                // 6) The last segment of the base URL's path (anything
26970                // following the rightmost slash "/", or the entire path if no
26971                // slash is present) is removed and the embedded URL's path is
26972                // appended in its place.
26973                var baseURLPath = baseParts.path;
26974                var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path;
26975                builtParts.path = URLToolkit.normalizePath(newPath);
26976              }
26977            }
26978          }
26979          if (builtParts.path === null) {
26980            builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path;
26981          }
26982          return URLToolkit.buildURLFromParts(builtParts);
26983        },
26984        parseURL: function parseURL(url) {
26985          var parts = URL_REGEX.exec(url);
26986          if (!parts) {
26987            return null;
26988          }
26989          return {
26990            scheme: parts[1] || '',
26991            netLoc: parts[2] || '',
26992            path: parts[3] || '',
26993            params: parts[4] || '',
26994            query: parts[5] || '',
26995            fragment: parts[6] || ''
26996          };
26997        },
26998        normalizePath: function normalizePath(path) {
26999          // The following operations are
27000          // then applied, in order, to the new path:
27001          // 6a) All occurrences of "./", where "." is a complete path
27002          // segment, are removed.
27003          // 6b) If the path ends with "." as a complete path segment,
27004          // that "." is removed.
27005          path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, '');
27006          // 6c) All occurrences of "<segment>/../", where <segment> is a
27007          // complete path segment not equal to "..", are removed.
27008          // Removal of these path segments is performed iteratively,
27009          // removing the leftmost matching pattern on each iteration,
27010          // until no matching pattern remains.
27011          // 6d) If the path ends with "<segment>/..", where <segment> is a
27012          // complete path segment not equal to "..", that
27013          // "<segment>/.." is removed.
27014          while (path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length) {} // jshint ignore:line
27015          return path.split('').reverse().join('');
27016        },
27017        buildURLFromParts: function buildURLFromParts(parts) {
27018          return parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment;
27019        }
27020      };
27021
27022      /* jshint ignore:start */
27023      module.exports = URLToolkit;
27024    })(commonjsGlobal);
27025    /* jshint ignore:end */
27026  });
27027
27028  var classCallCheck$1 = function classCallCheck$$1(instance, Constructor) {
27029    if (!(instance instanceof Constructor)) {
27030      throw new TypeError("Cannot call a class as a function");
27031    }
27032  };
27033
27034  var _extends$1 = Object.assign || function (target) {
27035    for (var i = 1; i < arguments.length; i++) {
27036      var source = arguments[i];
27037
27038      for (var key in source) {
27039        if (Object.prototype.hasOwnProperty.call(source, key)) {
27040          target[key] = source[key];
27041        }
27042      }
27043    }
27044
27045    return target;
27046  };
27047
27048  var inherits$1 = function inherits$$1(subClass, superClass) {
27049    if (typeof superClass !== "function" && superClass !== null) {
27050      throw new TypeError("Super expression must either be null or a function, not " + (typeof superClass === "undefined" ? "undefined" : _typeof(superClass)));
27051    }
27052
27053    subClass.prototype = Object.create(superClass && superClass.prototype, {
27054      constructor: {
27055        value: subClass,
27056        enumerable: false,
27057        writable: true,
27058        configurable: true
27059      }
27060    });
27061    if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
27062  };
27063
27064  var possibleConstructorReturn$1 = function possibleConstructorReturn$$1(self, call) {
27065    if (!self) {
27066      throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
27067    }
27068
27069    return call && ((typeof call === "undefined" ? "undefined" : _typeof(call)) === "object" || typeof call === "function") ? call : self;
27070  };
27071
27072  /**
27073   * @file stream.js
27074   */
27075  /**
27076   * A lightweight readable stream implemention that handles event dispatching.
27077   *
27078   * @class Stream
27079   */
27080  var Stream = function () {
27081    function Stream() {
27082      classCallCheck$1(this, Stream);
27083
27084      this.listeners = {};
27085    }
27086
27087    /**
27088     * Add a listener for a specified event type.
27089     *
27090     * @param {String} type the event name
27091     * @param {Function} listener the callback to be invoked when an event of
27092     * the specified type occurs
27093     */
27094
27095    Stream.prototype.on = function on(type, listener) {
27096      if (!this.listeners[type]) {
27097        this.listeners[type] = [];
27098      }
27099      this.listeners[type].push(listener);
27100    };
27101
27102    /**
27103     * Remove a listener for a specified event type.
27104     *
27105     * @param {String} type the event name
27106     * @param {Function} listener  a function previously registered for this
27107     * type of event through `on`
27108     * @return {Boolean} if we could turn it off or not
27109     */
27110
27111    Stream.prototype.off = function off(type, listener) {
27112      if (!this.listeners[type]) {
27113        return false;
27114      }
27115
27116      var index = this.listeners[type].indexOf(listener);
27117
27118      this.listeners[type].splice(index, 1);
27119      return index > -1;
27120    };
27121
27122    /**
27123     * Trigger an event of the specified type on this stream. Any additional
27124     * arguments to this function are passed as parameters to event listeners.
27125     *
27126     * @param {String} type the event name
27127     */
27128
27129    Stream.prototype.trigger = function trigger(type) {
27130      var callbacks = this.listeners[type];
27131      var i = void 0;
27132      var length = void 0;
27133      var args = void 0;
27134
27135      if (!callbacks) {
27136        return;
27137      }
27138      // Slicing the arguments on every invocation of this method
27139      // can add a significant amount of overhead. Avoid the
27140      // intermediate object creation for the common case of a
27141      // single callback argument
27142      if (arguments.length === 2) {
27143        length = callbacks.length;
27144        for (i = 0; i < length; ++i) {
27145          callbacks[i].call(this, arguments[1]);
27146        }
27147      } else {
27148        args = Array.prototype.slice.call(arguments, 1);
27149        length = callbacks.length;
27150        for (i = 0; i < length; ++i) {
27151          callbacks[i].apply(this, args);
27152        }
27153      }
27154    };
27155
27156    /**
27157     * Destroys the stream and cleans up.
27158     */
27159
27160    Stream.prototype.dispose = function dispose() {
27161      this.listeners = {};
27162    };
27163    /**
27164     * Forwards all `data` events on this stream to the destination stream. The
27165     * destination stream should provide a method `push` to receive the data
27166     * events as they arrive.
27167     *
27168     * @param {Stream} destination the stream that will receive all `data` events
27169     * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
27170     */
27171
27172    Stream.prototype.pipe = function pipe(destination) {
27173      this.on('data', function (data) {
27174        destination.push(data);
27175      });
27176    };
27177
27178    return Stream;
27179  }();
27180
27181  /**
27182   * @file m3u8/line-stream.js
27183   */
27184  /**
27185   * A stream that buffers string input and generates a `data` event for each
27186   * line.
27187   *
27188   * @class LineStream
27189   * @extends Stream
27190   */
27191
27192  var LineStream = function (_Stream) {
27193    inherits$1(LineStream, _Stream);
27194
27195    function LineStream() {
27196      classCallCheck$1(this, LineStream);
27197
27198      var _this = possibleConstructorReturn$1(this, _Stream.call(this));
27199
27200      _this.buffer = '';
27201      return _this;
27202    }
27203
27204    /**
27205     * Add new data to be parsed.
27206     *
27207     * @param {String} data the text to process
27208     */
27209
27210    LineStream.prototype.push = function push(data) {
27211      var nextNewline = void 0;
27212
27213      this.buffer += data;
27214      nextNewline = this.buffer.indexOf('\n');
27215
27216      for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) {
27217        this.trigger('data', this.buffer.substring(0, nextNewline));
27218        this.buffer = this.buffer.substring(nextNewline + 1);
27219      }
27220    };
27221
27222    return LineStream;
27223  }(Stream);
27224
27225  /**
27226   * @file m3u8/parse-stream.js
27227   */
27228  /**
27229   * "forgiving" attribute list psuedo-grammar:
27230   * attributes -> keyvalue (',' keyvalue)*
27231   * keyvalue   -> key '=' value
27232   * key        -> [^=]*
27233   * value      -> '"' [^"]* '"' | [^,]*
27234   */
27235  var attributeSeparator = function attributeSeparator() {
27236    var key = '[^=]*';
27237    var value = '"[^"]*"|[^,]*';
27238    var keyvalue = '(?:' + key + ')=(?:' + value + ')';
27239
27240    return new RegExp('(?:^|,)(' + keyvalue + ')');
27241  };
27242
27243  /**
27244   * Parse attributes from a line given the seperator
27245   *
27246   * @param {String} attributes the attibute line to parse
27247   */
27248  var parseAttributes = function parseAttributes(attributes) {
27249    // split the string using attributes as the separator
27250    var attrs = attributes.split(attributeSeparator());
27251    var result = {};
27252    var i = attrs.length;
27253    var attr = void 0;
27254
27255    while (i--) {
27256      // filter out unmatched portions of the string
27257      if (attrs[i] === '') {
27258        continue;
27259      }
27260
27261      // split the key and value
27262      attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1);
27263      // trim whitespace and remove optional quotes around the value
27264      attr[0] = attr[0].replace(/^\s+|\s+$/g, '');
27265      attr[1] = attr[1].replace(/^\s+|\s+$/g, '');
27266      attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1');
27267      result[attr[0]] = attr[1];
27268    }
27269    return result;
27270  };
27271
27272  /**
27273   * A line-level M3U8 parser event stream. It expects to receive input one
27274   * line at a time and performs a context-free parse of its contents. A stream
27275   * interpretation of a manifest can be useful if the manifest is expected to
27276   * be too large to fit comfortably into memory or the entirety of the input
27277   * is not immediately available. Otherwise, it's probably much easier to work
27278   * with a regular `Parser` object.
27279   *
27280   * Produces `data` events with an object that captures the parser's
27281   * interpretation of the input. That object has a property `tag` that is one
27282   * of `uri`, `comment`, or `tag`. URIs only have a single additional
27283   * property, `line`, which captures the entirety of the input without
27284   * interpretation. Comments similarly have a single additional property
27285   * `text` which is the input without the leading `#`.
27286   *
27287   * Tags always have a property `tagType` which is the lower-cased version of
27288   * the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance,
27289   * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized
27290   * tags are given the tag type `unknown` and a single additional property
27291   * `data` with the remainder of the input.
27292   *
27293   * @class ParseStream
27294   * @extends Stream
27295   */
27296
27297  var ParseStream = function (_Stream) {
27298    inherits$1(ParseStream, _Stream);
27299
27300    function ParseStream() {
27301      classCallCheck$1(this, ParseStream);
27302
27303      var _this = possibleConstructorReturn$1(this, _Stream.call(this));
27304
27305      _this.customParsers = [];
27306      return _this;
27307    }
27308
27309    /**
27310     * Parses an additional line of input.
27311     *
27312     * @param {String} line a single line of an M3U8 file to parse
27313     */
27314
27315    ParseStream.prototype.push = function push(line) {
27316      var match = void 0;
27317      var event = void 0;
27318
27319      // strip whitespace
27320      line = line.replace(/^[\u0000\s]+|[\u0000\s]+$/g, '');
27321      if (line.length === 0) {
27322        // ignore empty lines
27323        return;
27324      }
27325
27326      // URIs
27327      if (line[0] !== '#') {
27328        this.trigger('data', {
27329          type: 'uri',
27330          uri: line
27331        });
27332        return;
27333      }
27334
27335      for (var i = 0; i < this.customParsers.length; i++) {
27336        if (this.customParsers[i].call(this, line)) {
27337          return;
27338        }
27339      }
27340
27341      // Comments
27342      if (line.indexOf('#EXT') !== 0) {
27343        this.trigger('data', {
27344          type: 'comment',
27345          text: line.slice(1)
27346        });
27347        return;
27348      }
27349
27350      // strip off any carriage returns here so the regex matching
27351      // doesn't have to account for them.
27352      line = line.replace('\r', '');
27353
27354      // Tags
27355      match = /^#EXTM3U/.exec(line);
27356      if (match) {
27357        this.trigger('data', {
27358          type: 'tag',
27359          tagType: 'm3u'
27360        });
27361        return;
27362      }
27363      match = /^#EXTINF:?([0-9\.]*)?,?(.*)?$/.exec(line);
27364      if (match) {
27365        event = {
27366          type: 'tag',
27367          tagType: 'inf'
27368        };
27369        if (match[1]) {
27370          event.duration = parseFloat(match[1]);
27371        }
27372        if (match[2]) {
27373          event.title = match[2];
27374        }
27375        this.trigger('data', event);
27376        return;
27377      }
27378      match = /^#EXT-X-TARGETDURATION:?([0-9.]*)?/.exec(line);
27379      if (match) {
27380        event = {
27381          type: 'tag',
27382          tagType: 'targetduration'
27383        };
27384        if (match[1]) {
27385          event.duration = parseInt(match[1], 10);
27386        }
27387        this.trigger('data', event);
27388        return;
27389      }
27390      match = /^#ZEN-TOTAL-DURATION:?([0-9.]*)?/.exec(line);
27391      if (match) {
27392        event = {
27393          type: 'tag',
27394          tagType: 'totalduration'
27395        };
27396        if (match[1]) {
27397          event.duration = parseInt(match[1], 10);
27398        }
27399        this.trigger('data', event);
27400        return;
27401      }
27402      match = /^#EXT-X-VERSION:?([0-9.]*)?/.exec(line);
27403      if (match) {
27404        event = {
27405          type: 'tag',
27406          tagType: 'version'
27407        };
27408        if (match[1]) {
27409          event.version = parseInt(match[1], 10);
27410        }
27411        this.trigger('data', event);
27412        return;
27413      }
27414      match = /^#EXT-X-MEDIA-SEQUENCE:?(\-?[0-9.]*)?/.exec(line);
27415      if (match) {
27416        event = {
27417          type: 'tag',
27418          tagType: 'media-sequence'
27419        };
27420        if (match[1]) {
27421          event.number = parseInt(match[1], 10);
27422        }
27423        this.trigger('data', event);
27424        return;
27425      }
27426      match = /^#EXT-X-DISCONTINUITY-SEQUENCE:?(\-?[0-9.]*)?/.exec(line);
27427      if (match) {
27428        event = {
27429          type: 'tag',
27430          tagType: 'discontinuity-sequence'
27431        };
27432        if (match[1]) {
27433          event.number = parseInt(match[1], 10);
27434        }
27435        this.trigger('data', event);
27436        return;
27437      }
27438      match = /^#EXT-X-PLAYLIST-TYPE:?(.*)?$/.exec(line);
27439      if (match) {
27440        event = {
27441          type: 'tag',
27442          tagType: 'playlist-type'
27443        };
27444        if (match[1]) {
27445          event.playlistType = match[1];
27446        }
27447        this.trigger('data', event);
27448        return;
27449      }
27450      match = /^#EXT-X-BYTERANGE:?([0-9.]*)?@?([0-9.]*)?/.exec(line);
27451      if (match) {
27452        event = {
27453          type: 'tag',
27454          tagType: 'byterange'
27455        };
27456        if (match[1]) {
27457          event.length = parseInt(match[1], 10);
27458        }
27459        if (match[2]) {
27460          event.offset = parseInt(match[2], 10);
27461        }
27462        this.trigger('data', event);
27463        return;
27464      }
27465      match = /^#EXT-X-ALLOW-CACHE:?(YES|NO)?/.exec(line);
27466      if (match) {
27467        event = {
27468          type: 'tag',
27469          tagType: 'allow-cache'
27470        };
27471        if (match[1]) {
27472          event.allowed = !/NO/.test(match[1]);
27473        }
27474        this.trigger('data', event);
27475        return;
27476      }
27477      match = /^#EXT-X-MAP:?(.*)$/.exec(line);
27478      if (match) {
27479        event = {
27480          type: 'tag',
27481          tagType: 'map'
27482        };
27483
27484        if (match[1]) {
27485          var attributes = parseAttributes(match[1]);
27486
27487          if (attributes.URI) {
27488            event.uri = attributes.URI;
27489          }
27490          if (attributes.BYTERANGE) {
27491            var _attributes$BYTERANGE = attributes.BYTERANGE.split('@'),
27492                length = _attributes$BYTERANGE[0],
27493                offset = _attributes$BYTERANGE[1];
27494
27495            event.byterange = {};
27496            if (length) {
27497              event.byterange.length = parseInt(length, 10);
27498            }
27499            if (offset) {
27500              event.byterange.offset = parseInt(offset, 10);
27501            }
27502          }
27503        }
27504
27505        this.trigger('data', event);
27506        return;
27507      }
27508      match = /^#EXT-X-STREAM-INF:?(.*)$/.exec(line);
27509      if (match) {
27510        event = {
27511          type: 'tag',
27512          tagType: 'stream-inf'
27513        };
27514        if (match[1]) {
27515          event.attributes = parseAttributes(match[1]);
27516
27517          if (event.attributes.RESOLUTION) {
27518            var split = event.attributes.RESOLUTION.split('x');
27519            var resolution = {};
27520
27521            if (split[0]) {
27522              resolution.width = parseInt(split[0], 10);
27523            }
27524            if (split[1]) {
27525              resolution.height = parseInt(split[1], 10);
27526            }
27527            event.attributes.RESOLUTION = resolution;
27528          }
27529          if (event.attributes.BANDWIDTH) {
27530            event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
27531          }
27532          if (event.attributes['PROGRAM-ID']) {
27533            event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10);
27534          }
27535        }
27536        this.trigger('data', event);
27537        return;
27538      }
27539      match = /^#EXT-X-MEDIA:?(.*)$/.exec(line);
27540      if (match) {
27541        event = {
27542          type: 'tag',
27543          tagType: 'media'
27544        };
27545        if (match[1]) {
27546          event.attributes = parseAttributes(match[1]);
27547        }
27548        this.trigger('data', event);
27549        return;
27550      }
27551      match = /^#EXT-X-ENDLIST/.exec(line);
27552      if (match) {
27553        this.trigger('data', {
27554          type: 'tag',
27555          tagType: 'endlist'
27556        });
27557        return;
27558      }
27559      match = /^#EXT-X-DISCONTINUITY/.exec(line);
27560      if (match) {
27561        this.trigger('data', {
27562          type: 'tag',
27563          tagType: 'discontinuity'
27564        });
27565        return;
27566      }
27567      match = /^#EXT-X-PROGRAM-DATE-TIME:?(.*)$/.exec(line);
27568      if (match) {
27569        event = {
27570          type: 'tag',
27571          tagType: 'program-date-time'
27572        };
27573        if (match[1]) {
27574          event.dateTimeString = match[1];
27575          event.dateTimeObject = new Date(match[1]);
27576        }
27577        this.trigger('data', event);
27578        return;
27579      }
27580      match = /^#EXT-X-KEY:?(.*)$/.exec(line);
27581      if (match) {
27582        event = {
27583          type: 'tag',
27584          tagType: 'key'
27585        };
27586        if (match[1]) {
27587          event.attributes = parseAttributes(match[1]);
27588          // parse the IV string into a Uint32Array
27589          if (event.attributes.IV) {
27590            if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') {
27591              event.attributes.IV = event.attributes.IV.substring(2);
27592            }
27593
27594            event.attributes.IV = event.attributes.IV.match(/.{8}/g);
27595            event.attributes.IV[0] = parseInt(event.attributes.IV[0], 16);
27596            event.attributes.IV[1] = parseInt(event.attributes.IV[1], 16);
27597            event.attributes.IV[2] = parseInt(event.attributes.IV[2], 16);
27598            event.attributes.IV[3] = parseInt(event.attributes.IV[3], 16);
27599            event.attributes.IV = new Uint32Array(event.attributes.IV);
27600          }
27601        }
27602        this.trigger('data', event);
27603        return;
27604      }
27605      match = /^#EXT-X-START:?(.*)$/.exec(line);
27606      if (match) {
27607        event = {
27608          type: 'tag',
27609          tagType: 'start'
27610        };
27611        if (match[1]) {
27612          event.attributes = parseAttributes(match[1]);
27613
27614          event.attributes['TIME-OFFSET'] = parseFloat(event.attributes['TIME-OFFSET']);
27615          event.attributes.PRECISE = /YES/.test(event.attributes.PRECISE);
27616        }
27617        this.trigger('data', event);
27618        return;
27619      }
27620      match = /^#EXT-X-CUE-OUT-CONT:?(.*)?$/.exec(line);
27621      if (match) {
27622        event = {
27623          type: 'tag',
27624          tagType: 'cue-out-cont'
27625        };
27626        if (match[1]) {
27627          event.data = match[1];
27628        } else {
27629          event.data = '';
27630        }
27631        this.trigger('data', event);
27632        return;
27633      }
27634      match = /^#EXT-X-CUE-OUT:?(.*)?$/.exec(line);
27635      if (match) {
27636        event = {
27637          type: 'tag',
27638          tagType: 'cue-out'
27639        };
27640        if (match[1]) {
27641          event.data = match[1];
27642        } else {
27643          event.data = '';
27644        }
27645        this.trigger('data', event);
27646        return;
27647      }
27648      match = /^#EXT-X-CUE-IN:?(.*)?$/.exec(line);
27649      if (match) {
27650        event = {
27651          type: 'tag',
27652          tagType: 'cue-in'
27653        };
27654        if (match[1]) {
27655          event.data = match[1];
27656        } else {
27657          event.data = '';
27658        }
27659        this.trigger('data', event);
27660        return;
27661      }
27662
27663      // unknown tag type
27664      this.trigger('data', {
27665        type: 'tag',
27666        data: line.slice(4)
27667      });
27668    };
27669
27670    /**
27671     * Add a parser for custom headers
27672     *
27673     * @param {Object}   options              a map of options for the added parser
27674     * @param {RegExp}   options.expression   a regular expression to match the custom header
27675     * @param {string}   options.customType   the custom type to register to the output
27676     * @param {Function} [options.dataParser] function to parse the line into an object
27677     * @param {boolean}  [options.segment]    should tag data be attached to the segment object
27678     */
27679
27680    ParseStream.prototype.addParser = function addParser(_ref) {
27681      var _this2 = this;
27682
27683      var expression = _ref.expression,
27684          customType = _ref.customType,
27685          dataParser = _ref.dataParser,
27686          segment = _ref.segment;
27687
27688      if (typeof dataParser !== 'function') {
27689        dataParser = function dataParser(line) {
27690          return line;
27691        };
27692      }
27693      this.customParsers.push(function (line) {
27694        var match = expression.exec(line);
27695
27696        if (match) {
27697          _this2.trigger('data', {
27698            type: 'custom',
27699            data: dataParser(line),
27700            customType: customType,
27701            segment: segment
27702          });
27703          return true;
27704        }
27705      });
27706    };
27707
27708    return ParseStream;
27709  }(Stream);
27710
27711  /**
27712   * @file m3u8/parser.js
27713   */
27714  /**
27715   * A parser for M3U8 files. The current interpretation of the input is
27716   * exposed as a property `manifest` on parser objects. It's just two lines to
27717   * create and parse a manifest once you have the contents available as a string:
27718   *
27719   * ```js
27720   * var parser = new m3u8.Parser();
27721   * parser.push(xhr.responseText);
27722   * ```
27723   *
27724   * New input can later be applied to update the manifest object by calling
27725   * `push` again.
27726   *
27727   * The parser attempts to create a usable manifest object even if the
27728   * underlying input is somewhat nonsensical. It emits `info` and `warning`
27729   * events during the parse if it encounters input that seems invalid or
27730   * requires some property of the manifest object to be defaulted.
27731   *
27732   * @class Parser
27733   * @extends Stream
27734   */
27735
27736  var Parser = function (_Stream) {
27737    inherits$1(Parser, _Stream);
27738
27739    function Parser() {
27740      classCallCheck$1(this, Parser);
27741
27742      var _this = possibleConstructorReturn$1(this, _Stream.call(this));
27743
27744      _this.lineStream = new LineStream();
27745      _this.parseStream = new ParseStream();
27746      _this.lineStream.pipe(_this.parseStream);
27747
27748      /* eslint-disable consistent-this */
27749      var self = _this;
27750      /* eslint-enable consistent-this */
27751      var uris = [];
27752      var currentUri = {};
27753      // if specified, the active EXT-X-MAP definition
27754      var currentMap = void 0;
27755      // if specified, the active decryption key
27756      var _key = void 0;
27757      var noop = function noop() {};
27758      var defaultMediaGroups = {
27759        'AUDIO': {},
27760        'VIDEO': {},
27761        'CLOSED-CAPTIONS': {},
27762        'SUBTITLES': {}
27763      };
27764      // group segments into numbered timelines delineated by discontinuities
27765      var currentTimeline = 0;
27766
27767      // the manifest is empty until the parse stream begins delivering data
27768      _this.manifest = {
27769        allowCache: true,
27770        discontinuityStarts: [],
27771        segments: []
27772      };
27773
27774      // update the manifest with the m3u8 entry from the parse stream
27775      _this.parseStream.on('data', function (entry) {
27776        var mediaGroup = void 0;
27777        var rendition = void 0;
27778
27779        ({
27780          tag: function tag() {
27781            // switch based on the tag type
27782            (({
27783              'allow-cache': function allowCache() {
27784                this.manifest.allowCache = entry.allowed;
27785                if (!('allowed' in entry)) {
27786                  this.trigger('info', {
27787                    message: 'defaulting allowCache to YES'
27788                  });
27789                  this.manifest.allowCache = true;
27790                }
27791              },
27792              byterange: function byterange() {
27793                var byterange = {};
27794
27795                if ('length' in entry) {
27796                  currentUri.byterange = byterange;
27797                  byterange.length = entry.length;
27798
27799                  if (!('offset' in entry)) {
27800                    this.trigger('info', {
27801                      message: 'defaulting offset to zero'
27802                    });
27803                    entry.offset = 0;
27804                  }
27805                }
27806                if ('offset' in entry) {
27807                  currentUri.byterange = byterange;
27808                  byterange.offset = entry.offset;
27809                }
27810              },
27811              endlist: function endlist() {
27812                this.manifest.endList = true;
27813              },
27814              inf: function inf() {
27815                if (!('mediaSequence' in this.manifest)) {
27816                  this.manifest.mediaSequence = 0;
27817                  this.trigger('info', {
27818                    message: 'defaulting media sequence to zero'
27819                  });
27820                }
27821                if (!('discontinuitySequence' in this.manifest)) {
27822                  this.manifest.discontinuitySequence = 0;
27823                  this.trigger('info', {
27824                    message: 'defaulting discontinuity sequence to zero'
27825                  });
27826                }
27827                if (entry.duration > 0) {
27828                  currentUri.duration = entry.duration;
27829                }
27830
27831                if (entry.duration === 0) {
27832                  currentUri.duration = 0.01;
27833                  this.trigger('info', {
27834                    message: 'updating zero segment duration to a small value'
27835                  });
27836                }
27837
27838                this.manifest.segments = uris;
27839              },
27840              key: function key() {
27841                if (!entry.attributes) {
27842                  this.trigger('warn', {
27843                    message: 'ignoring key declaration without attribute list'
27844                  });
27845                  return;
27846                }
27847                // clear the active encryption key
27848                if (entry.attributes.METHOD === 'NONE') {
27849                  _key = null;
27850                  return;
27851                }
27852                if (!entry.attributes.URI) {
27853                  this.trigger('warn', {
27854                    message: 'ignoring key declaration without URI'
27855                  });
27856                  return;
27857                }
27858                if (!entry.attributes.METHOD) {
27859                  this.trigger('warn', {
27860                    message: 'defaulting key method to AES-128'
27861                  });
27862                }
27863
27864                // setup an encryption key for upcoming segments
27865                _key = {
27866                  method: entry.attributes.METHOD || 'AES-128',
27867                  uri: entry.attributes.URI
27868                };
27869
27870                if (typeof entry.attributes.IV !== 'undefined') {
27871                  _key.iv = entry.attributes.IV;
27872                }
27873              },
27874              'media-sequence': function mediaSequence() {
27875                if (!isFinite(entry.number)) {
27876                  this.trigger('warn', {
27877                    message: 'ignoring invalid media sequence: ' + entry.number
27878                  });
27879                  return;
27880                }
27881                this.manifest.mediaSequence = entry.number;
27882              },
27883              'discontinuity-sequence': function discontinuitySequence() {
27884                if (!isFinite(entry.number)) {
27885                  this.trigger('warn', {
27886                    message: 'ignoring invalid discontinuity sequence: ' + entry.number
27887                  });
27888                  return;
27889                }
27890                this.manifest.discontinuitySequence = entry.number;
27891                currentTimeline = entry.number;
27892              },
27893              'playlist-type': function playlistType() {
27894                if (!/VOD|EVENT/.test(entry.playlistType)) {
27895                  this.trigger('warn', {
27896                    message: 'ignoring unknown playlist type: ' + entry.playlist
27897                  });
27898                  return;
27899                }
27900                this.manifest.playlistType = entry.playlistType;
27901              },
27902              map: function map() {
27903                currentMap = {};
27904                if (entry.uri) {
27905                  currentMap.uri = entry.uri;
27906                }
27907                if (entry.byterange) {
27908                  currentMap.byterange = entry.byterange;
27909                }
27910              },
27911              'stream-inf': function streamInf() {
27912                this.manifest.playlists = uris;
27913                this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
27914
27915                if (!entry.attributes) {
27916                  this.trigger('warn', {
27917                    message: 'ignoring empty stream-inf attributes'
27918                  });
27919                  return;
27920                }
27921
27922                if (!currentUri.attributes) {
27923                  currentUri.attributes = {};
27924                }
27925                _extends$1(currentUri.attributes, entry.attributes);
27926              },
27927              media: function media() {
27928                this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
27929
27930                if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) {
27931                  this.trigger('warn', {
27932                    message: 'ignoring incomplete or missing media group'
27933                  });
27934                  return;
27935                }
27936
27937                // find the media group, creating defaults as necessary
27938                var mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE];
27939
27940                mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {};
27941                mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']];
27942
27943                // collect the rendition metadata
27944                rendition = {
27945                  'default': /yes/i.test(entry.attributes.DEFAULT)
27946                };
27947                if (rendition['default']) {
27948                  rendition.autoselect = true;
27949                } else {
27950                  rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT);
27951                }
27952                if (entry.attributes.LANGUAGE) {
27953                  rendition.language = entry.attributes.LANGUAGE;
27954                }
27955                if (entry.attributes.URI) {
27956                  rendition.uri = entry.attributes.URI;
27957                }
27958                if (entry.attributes['INSTREAM-ID']) {
27959                  rendition.instreamId = entry.attributes['INSTREAM-ID'];
27960                }
27961                if (entry.attributes.CHARACTERISTICS) {
27962                  rendition.characteristics = entry.attributes.CHARACTERISTICS;
27963                }
27964                if (entry.attributes.FORCED) {
27965                  rendition.forced = /yes/i.test(entry.attributes.FORCED);
27966                }
27967
27968                // insert the new rendition
27969                mediaGroup[entry.attributes.NAME] = rendition;
27970              },
27971              discontinuity: function discontinuity() {
27972                currentTimeline += 1;
27973                currentUri.discontinuity = true;
27974                this.manifest.discontinuityStarts.push(uris.length);
27975              },
27976              'program-date-time': function programDateTime() {
27977                if (typeof this.manifest.dateTimeString === 'undefined') {
27978                  // PROGRAM-DATE-TIME is a media-segment tag, but for backwards
27979                  // compatibility, we add the first occurence of the PROGRAM-DATE-TIME tag
27980                  // to the manifest object
27981                  // TODO: Consider removing this in future major version
27982                  this.manifest.dateTimeString = entry.dateTimeString;
27983                  this.manifest.dateTimeObject = entry.dateTimeObject;
27984                }
27985
27986                currentUri.dateTimeString = entry.dateTimeString;
27987                currentUri.dateTimeObject = entry.dateTimeObject;
27988              },
27989              targetduration: function targetduration() {
27990                if (!isFinite(entry.duration) || entry.duration < 0) {
27991                  this.trigger('warn', {
27992                    message: 'ignoring invalid target duration: ' + entry.duration
27993                  });
27994                  return;
27995                }
27996                this.manifest.targetDuration = entry.duration;
27997              },
27998              totalduration: function totalduration() {
27999                if (!isFinite(entry.duration) || entry.duration < 0) {
28000                  this.trigger('warn', {
28001                    message: 'ignoring invalid total duration: ' + entry.duration
28002                  });
28003                  return;
28004                }
28005                this.manifest.totalDuration = entry.duration;
28006              },
28007              start: function start() {
28008                if (!entry.attributes || isNaN(entry.attributes['TIME-OFFSET'])) {
28009                  this.trigger('warn', {
28010                    message: 'ignoring start declaration without appropriate attribute list'
28011                  });
28012                  return;
28013                }
28014                this.manifest.start = {
28015                  timeOffset: entry.attributes['TIME-OFFSET'],
28016                  precise: entry.attributes.PRECISE
28017                };
28018              },
28019              'cue-out': function cueOut() {
28020                currentUri.cueOut = entry.data;
28021              },
28022              'cue-out-cont': function cueOutCont() {
28023                currentUri.cueOutCont = entry.data;
28024              },
28025              'cue-in': function cueIn() {
28026                currentUri.cueIn = entry.data;
28027              }
28028            })[entry.tagType] || noop).call(self);
28029          },
28030          uri: function uri() {
28031            currentUri.uri = entry.uri;
28032            uris.push(currentUri);
28033
28034            // if no explicit duration was declared, use the target duration
28035            if (this.manifest.targetDuration && !('duration' in currentUri)) {
28036              this.trigger('warn', {
28037                message: 'defaulting segment duration to the target duration'
28038              });
28039              currentUri.duration = this.manifest.targetDuration;
28040            }
28041            // annotate with encryption information, if necessary
28042            if (_key) {
28043              currentUri.key = _key;
28044            }
28045            currentUri.timeline = currentTimeline;
28046            // annotate with initialization segment information, if necessary
28047            if (currentMap) {
28048              currentUri.map = currentMap;
28049            }
28050
28051            // prepare for the next URI
28052            currentUri = {};
28053          },
28054          comment: function comment() {
28055            // comments are not important for playback
28056          },
28057          custom: function custom() {
28058            // if this is segment-level data attach the output to the segment
28059            if (entry.segment) {
28060              currentUri.custom = currentUri.custom || {};
28061              currentUri.custom[entry.customType] = entry.data;
28062              // if this is manifest-level data attach to the top level manifest object
28063            } else {
28064              this.manifest.custom = this.manifest.custom || {};
28065              this.manifest.custom[entry.customType] = entry.data;
28066            }
28067          }
28068        })[entry.type].call(self);
28069      });
28070      return _this;
28071    }
28072
28073    /**
28074     * Parse the input string and update the manifest object.
28075     *
28076     * @param {String} chunk a potentially incomplete portion of the manifest
28077     */
28078
28079    Parser.prototype.push = function push(chunk) {
28080      this.lineStream.push(chunk);
28081    };
28082
28083    /**
28084     * Flush any remaining input. This can be handy if the last line of an M3U8
28085     * manifest did not contain a trailing newline but the file has been
28086     * completely received.
28087     */
28088
28089    Parser.prototype.end = function end() {
28090      // flush any buffered input
28091      this.lineStream.push('\n');
28092    };
28093    /**
28094     * Add an additional parser for non-standard tags
28095     *
28096     * @param {Object}   options              a map of options for the added parser
28097     * @param {RegExp}   options.expression   a regular expression to match the custom header
28098     * @param {string}   options.type         the type to register to the output
28099     * @param {Function} [options.dataParser] function to parse the line into an object
28100     * @param {boolean}  [options.segment]    should tag data be attached to the segment object
28101     */
28102
28103    Parser.prototype.addParser = function addParser(options) {
28104      this.parseStream.addParser(options);
28105    };
28106
28107    return Parser;
28108  }(Stream);
28109
28110  /**
28111   * mpd-parser
28112   * @version 0.6.1
28113   * @copyright 2018 Brightcove, Inc
28114   * @license Apache-2.0
28115   */
28116
28117  var formatAudioPlaylist = function formatAudioPlaylist(_ref) {
28118    var _attributes;
28119
28120    var attributes = _ref.attributes,
28121        segments = _ref.segments;
28122
28123    var playlist = {
28124      attributes: (_attributes = {
28125        NAME: attributes.id,
28126        BANDWIDTH: attributes.bandwidth,
28127        CODECS: attributes.codecs
28128      }, _attributes['PROGRAM-ID'] = 1, _attributes),
28129      uri: '',
28130      endList: (attributes.type || 'static') === 'static',
28131      timeline: attributes.periodIndex,
28132      resolvedUri: '',
28133      targetDuration: attributes.duration,
28134      segments: segments,
28135      mediaSequence: segments.length ? segments[0].number : 1
28136    };
28137
28138    if (attributes.contentProtection) {
28139      playlist.contentProtection = attributes.contentProtection;
28140    }
28141
28142    return playlist;
28143  };
28144
28145  var formatVttPlaylist = function formatVttPlaylist(_ref2) {
28146    var _attributes2;
28147
28148    var attributes = _ref2.attributes,
28149        segments = _ref2.segments;
28150
28151    if (typeof segments === 'undefined') {
28152      // vtt tracks may use single file in BaseURL
28153      segments = [{
28154        uri: attributes.baseUrl,
28155        timeline: attributes.periodIndex,
28156        resolvedUri: attributes.baseUrl || '',
28157        duration: attributes.sourceDuration,
28158        number: 0
28159      }];
28160      // targetDuration should be the same duration as the only segment
28161      attributes.duration = attributes.sourceDuration;
28162    }
28163    return {
28164      attributes: (_attributes2 = {
28165        NAME: attributes.id,
28166        BANDWIDTH: attributes.bandwidth
28167      }, _attributes2['PROGRAM-ID'] = 1, _attributes2),
28168      uri: '',
28169      endList: (attributes.type || 'static') === 'static',
28170      timeline: attributes.periodIndex,
28171      resolvedUri: attributes.baseUrl || '',
28172      targetDuration: attributes.duration,
28173      segments: segments,
28174      mediaSequence: segments.length ? segments[0].number : 1
28175    };
28176  };
28177
28178  var organizeAudioPlaylists = function organizeAudioPlaylists(playlists) {
28179    return playlists.reduce(function (a, playlist) {
28180      var role = playlist.attributes.role && playlist.attributes.role.value || 'main';
28181      var language = playlist.attributes.lang || '';
28182
28183      var label = 'main';
28184
28185      if (language) {
28186        label = playlist.attributes.lang + ' (' + role + ')';
28187      }
28188
28189      // skip if we already have the highest quality audio for a language
28190      if (a[label] && a[label].playlists[0].attributes.BANDWIDTH > playlist.attributes.bandwidth) {
28191        return a;
28192      }
28193
28194      a[label] = {
28195        language: language,
28196        autoselect: true,
28197        'default': role === 'main',
28198        playlists: [formatAudioPlaylist(playlist)],
28199        uri: ''
28200      };
28201
28202      return a;
28203    }, {});
28204  };
28205
28206  var organizeVttPlaylists = function organizeVttPlaylists(playlists) {
28207    return playlists.reduce(function (a, playlist) {
28208      var label = playlist.attributes.lang || 'text';
28209
28210      // skip if we already have subtitles
28211      if (a[label]) {
28212        return a;
28213      }
28214
28215      a[label] = {
28216        language: label,
28217        'default': false,
28218        autoselect: false,
28219        playlists: [formatVttPlaylist(playlist)],
28220        uri: ''
28221      };
28222
28223      return a;
28224    }, {});
28225  };
28226
28227  var formatVideoPlaylist = function formatVideoPlaylist(_ref3) {
28228    var _attributes3;
28229
28230    var attributes = _ref3.attributes,
28231        segments = _ref3.segments;
28232
28233    var playlist = {
28234      attributes: (_attributes3 = {
28235        NAME: attributes.id,
28236        AUDIO: 'audio',
28237        SUBTITLES: 'subs',
28238        RESOLUTION: {
28239          width: attributes.width,
28240          height: attributes.height
28241        },
28242        CODECS: attributes.codecs,
28243        BANDWIDTH: attributes.bandwidth
28244      }, _attributes3['PROGRAM-ID'] = 1, _attributes3),
28245      uri: '',
28246      endList: (attributes.type || 'static') === 'static',
28247      timeline: attributes.periodIndex,
28248      resolvedUri: '',
28249      targetDuration: attributes.duration,
28250      segments: segments,
28251      mediaSequence: segments.length ? segments[0].number : 1
28252    };
28253
28254    if (attributes.contentProtection) {
28255      playlist.contentProtection = attributes.contentProtection;
28256    }
28257
28258    return playlist;
28259  };
28260
28261  var toM3u8 = function toM3u8(dashPlaylists) {
28262    var _mediaGroups;
28263
28264    if (!dashPlaylists.length) {
28265      return {};
28266    }
28267
28268    // grab all master attributes
28269    var _dashPlaylists$0$attr = dashPlaylists[0].attributes,
28270        duration = _dashPlaylists$0$attr.sourceDuration,
28271        _dashPlaylists$0$attr2 = _dashPlaylists$0$attr.minimumUpdatePeriod,
28272        minimumUpdatePeriod = _dashPlaylists$0$attr2 === undefined ? 0 : _dashPlaylists$0$attr2;
28273
28274    var videoOnly = function videoOnly(_ref4) {
28275      var attributes = _ref4.attributes;
28276      return attributes.mimeType === 'video/mp4' || attributes.contentType === 'video';
28277    };
28278    var audioOnly = function audioOnly(_ref5) {
28279      var attributes = _ref5.attributes;
28280      return attributes.mimeType === 'audio/mp4' || attributes.contentType === 'audio';
28281    };
28282    var vttOnly = function vttOnly(_ref6) {
28283      var attributes = _ref6.attributes;
28284      return attributes.mimeType === 'text/vtt' || attributes.contentType === 'text';
28285    };
28286
28287    var videoPlaylists = dashPlaylists.filter(videoOnly).map(formatVideoPlaylist);
28288    var audioPlaylists = dashPlaylists.filter(audioOnly);
28289    var vttPlaylists = dashPlaylists.filter(vttOnly);
28290
28291    var master = {
28292      allowCache: true,
28293      discontinuityStarts: [],
28294      segments: [],
28295      endList: true,
28296      mediaGroups: (_mediaGroups = {
28297        AUDIO: {},
28298        VIDEO: {}
28299      }, _mediaGroups['CLOSED-CAPTIONS'] = {}, _mediaGroups.SUBTITLES = {}, _mediaGroups),
28300      uri: '',
28301      duration: duration,
28302      playlists: videoPlaylists,
28303      minimumUpdatePeriod: minimumUpdatePeriod * 1000
28304    };
28305
28306    if (audioPlaylists.length) {
28307      master.mediaGroups.AUDIO.audio = organizeAudioPlaylists(audioPlaylists);
28308    }
28309
28310    if (vttPlaylists.length) {
28311      master.mediaGroups.SUBTITLES.subs = organizeVttPlaylists(vttPlaylists);
28312    }
28313
28314    return master;
28315  };
28316
28317  var _typeof$1 = typeof Symbol === "function" && _typeof(Symbol.iterator) === "symbol" ? function (obj) {
28318    return typeof obj === 'undefined' ? 'undefined' : _typeof(obj);
28319  } : function (obj) {
28320    return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj === 'undefined' ? 'undefined' : _typeof(obj);
28321  };
28322
28323  var isObject$1 = function isObject(obj) {
28324    return !!obj && (typeof obj === 'undefined' ? 'undefined' : _typeof$1(obj)) === 'object';
28325  };
28326
28327  var merge = function merge() {
28328    for (var _len = arguments.length, objects = Array(_len), _key = 0; _key < _len; _key++) {
28329      objects[_key] = arguments[_key];
28330    }
28331
28332    return objects.reduce(function (result, source) {
28333
28334      Object.keys(source).forEach(function (key) {
28335
28336        if (Array.isArray(result[key]) && Array.isArray(source[key])) {
28337          result[key] = result[key].concat(source[key]);
28338        } else if (isObject$1(result[key]) && isObject$1(source[key])) {
28339          result[key] = merge(result[key], source[key]);
28340        } else {
28341          result[key] = source[key];
28342        }
28343      });
28344      return result;
28345    }, {});
28346  };
28347
28348  var resolveUrl = function resolveUrl(baseUrl, relativeUrl) {
28349    // return early if we don't need to resolve
28350    if (/^[a-z]+:/i.test(relativeUrl)) {
28351      return relativeUrl;
28352    }
28353
28354    // if the base URL is relative then combine with the current location
28355    if (!/\/\//i.test(baseUrl)) {
28356      baseUrl = urlToolkit.buildAbsoluteURL(window_1.location.href, baseUrl);
28357    }
28358
28359    return urlToolkit.buildAbsoluteURL(baseUrl, relativeUrl);
28360  };
28361
28362  /**
28363   * @typedef {Object} SingleUri
28364   * @property {string} uri - relative location of segment
28365   * @property {string} resolvedUri - resolved location of segment
28366   * @property {Object} byterange - Object containing information on how to make byte range
28367   *   requests following byte-range-spec per RFC2616.
28368   * @property {String} byterange.length - length of range request
28369   * @property {String} byterange.offset - byte offset of range request
28370   *
28371   * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1
28372   */
28373
28374  /**
28375   * Converts a URLType node (5.3.9.2.3 Table 13) to a segment object
28376   * that conforms to how m3u8-parser is structured
28377   *
28378   * @see https://github.com/videojs/m3u8-parser
28379   *
28380   * @param {string} baseUrl - baseUrl provided by <BaseUrl> nodes
28381   * @param {string} source - source url for segment
28382   * @param {string} range - optional range used for range calls, follows
28383   * @return {SingleUri} full segment information transformed into a format similar
28384   *   to m3u8-parser
28385   */
28386  var urlTypeToSegment = function urlTypeToSegment(_ref) {
28387    var _ref$baseUrl = _ref.baseUrl,
28388        baseUrl = _ref$baseUrl === undefined ? '' : _ref$baseUrl,
28389        _ref$source = _ref.source,
28390        source = _ref$source === undefined ? '' : _ref$source,
28391        _ref$range = _ref.range,
28392        range = _ref$range === undefined ? '' : _ref$range;
28393
28394    var init = {
28395      uri: source,
28396      resolvedUri: resolveUrl(baseUrl || '', source)
28397    };
28398
28399    if (range) {
28400      var ranges = range.split('-');
28401      var startRange = parseInt(ranges[0], 10);
28402      var endRange = parseInt(ranges[1], 10);
28403
28404      init.byterange = {
28405        length: endRange - startRange,
28406        offset: startRange
28407      };
28408    }
28409
28410    return init;
28411  };
28412
28413  /**
28414   * Calculates the R (repetition) value for a live stream (for the final segment
28415   * in a manifest where the r value is negative 1)
28416   *
28417   * @param {Object} attributes
28418   *        Object containing all inherited attributes from parent elements with attribute
28419   *        names as keys
28420   * @param {number} time
28421   *        current time (typically the total time up until the final segment)
28422   * @param {number} duration
28423   *        duration property for the given <S />
28424   *
28425   * @return {number}
28426   *        R value to reach the end of the given period
28427   */
28428  var getLiveRValue = function getLiveRValue(attributes, time, duration) {
28429    var NOW = attributes.NOW,
28430        clientOffset = attributes.clientOffset,
28431        availabilityStartTime = attributes.availabilityStartTime,
28432        _attributes$timescale = attributes.timescale,
28433        timescale = _attributes$timescale === undefined ? 1 : _attributes$timescale,
28434        _attributes$start = attributes.start,
28435        start = _attributes$start === undefined ? 0 : _attributes$start,
28436        _attributes$minimumUp = attributes.minimumUpdatePeriod,
28437        minimumUpdatePeriod = _attributes$minimumUp === undefined ? 0 : _attributes$minimumUp;
28438
28439    var now = (NOW + clientOffset) / 1000;
28440    var periodStartWC = availabilityStartTime + start;
28441    var periodEndWC = now + minimumUpdatePeriod;
28442    var periodDuration = periodEndWC - periodStartWC;
28443
28444    return Math.ceil((periodDuration * timescale - time) / duration);
28445  };
28446
28447  /**
28448   * Uses information provided by SegmentTemplate.SegmentTimeline to determine segment
28449   * timing and duration
28450   *
28451   * @param {Object} attributes
28452   *        Object containing all inherited attributes from parent elements with attribute
28453   *        names as keys
28454   * @param {Object[]} segmentTimeline
28455   *        List of objects representing the attributes of each S element contained within
28456   *
28457   * @return {{number: number, duration: number, time: number, timeline: number}[]}
28458   *         List of Objects with segment timing and duration info
28459   */
28460  var parseByTimeline = function parseByTimeline(attributes, segmentTimeline) {
28461    var _attributes$type = attributes.type,
28462        type = _attributes$type === undefined ? 'static' : _attributes$type,
28463        _attributes$minimumUp2 = attributes.minimumUpdatePeriod,
28464        minimumUpdatePeriod = _attributes$minimumUp2 === undefined ? 0 : _attributes$minimumUp2,
28465        _attributes$media = attributes.media,
28466        media = _attributes$media === undefined ? '' : _attributes$media,
28467        sourceDuration = attributes.sourceDuration,
28468        _attributes$timescale2 = attributes.timescale,
28469        timescale = _attributes$timescale2 === undefined ? 1 : _attributes$timescale2,
28470        _attributes$startNumb = attributes.startNumber,
28471        startNumber = _attributes$startNumb === undefined ? 1 : _attributes$startNumb,
28472        timeline = attributes.periodIndex;
28473
28474    var segments = [];
28475    var time = -1;
28476
28477    for (var sIndex = 0; sIndex < segmentTimeline.length; sIndex++) {
28478      var S = segmentTimeline[sIndex];
28479      var duration = S.d;
28480      var repeat = S.r || 0;
28481      var segmentTime = S.t || 0;
28482
28483      if (time < 0) {
28484        // first segment
28485        time = segmentTime;
28486      }
28487
28488      if (segmentTime && segmentTime > time) {
28489        // discontinuity
28490
28491        // TODO: How to handle this type of discontinuity
28492        // timeline++ here would treat it like HLS discontuity and content would
28493        // get appended without gap
28494        // E.G.
28495        //  <S t="0" d="1" />
28496        //  <S d="1" />
28497        //  <S d="1" />
28498        //  <S t="5" d="1" />
28499        // would have $Time$ values of [0, 1, 2, 5]
28500        // should this be appened at time positions [0, 1, 2, 3],(#EXT-X-DISCONTINUITY)
28501        // or [0, 1, 2, gap, gap, 5]? (#EXT-X-GAP)
28502        // does the value of sourceDuration consider this when calculating arbitrary
28503        // negative @r repeat value?
28504        // E.G. Same elements as above with this added at the end
28505        //  <S d="1" r="-1" />
28506        //  with a sourceDuration of 10
28507        // Would the 2 gaps be included in the time duration calculations resulting in
28508        // 8 segments with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9] or 10 segments
28509        // with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9, 10, 11] ?
28510
28511        time = segmentTime;
28512      }
28513
28514      var count = void 0;
28515
28516      if (repeat < 0) {
28517        var nextS = sIndex + 1;
28518
28519        if (nextS === segmentTimeline.length) {
28520          // last segment
28521          if (type === 'dynamic' && minimumUpdatePeriod > 0 && media.indexOf('$Number$') > 0) {
28522            count = getLiveRValue(attributes, time, duration);
28523          } else {
28524            // TODO: This may be incorrect depending on conclusion of TODO above
28525            count = (sourceDuration * timescale - time) / duration;
28526          }
28527        } else {
28528          count = (segmentTimeline[nextS].t - time) / duration;
28529        }
28530      } else {
28531        count = repeat + 1;
28532      }
28533
28534      var end = startNumber + segments.length + count;
28535      var number = startNumber + segments.length;
28536
28537      while (number < end) {
28538        segments.push({ number: number, duration: duration / timescale, time: time, timeline: timeline });
28539        time += duration;
28540        number++;
28541      }
28542    }
28543
28544    return segments;
28545  };
28546
28547  var range = function range(start, end) {
28548    var result = [];
28549
28550    for (var i = start; i < end; i++) {
28551      result.push(i);
28552    }
28553
28554    return result;
28555  };
28556
28557  var flatten = function flatten(lists) {
28558    return lists.reduce(function (x, y) {
28559      return x.concat(y);
28560    }, []);
28561  };
28562
28563  var from = function from(list) {
28564    if (!list.length) {
28565      return [];
28566    }
28567
28568    var result = [];
28569
28570    for (var i = 0; i < list.length; i++) {
28571      result.push(list[i]);
28572    }
28573
28574    return result;
28575  };
28576
28577  /**
28578   * Functions for calculating the range of available segments in static and dynamic
28579   * manifests.
28580   */
28581  var segmentRange = {
28582    /**
28583     * Returns the entire range of available segments for a static MPD
28584     *
28585     * @param {Object} attributes
28586     *        Inheritied MPD attributes
28587     * @return {{ start: number, end: number }}
28588     *         The start and end numbers for available segments
28589     */
28590    'static': function _static(attributes) {
28591      var duration = attributes.duration,
28592          _attributes$timescale = attributes.timescale,
28593          timescale = _attributes$timescale === undefined ? 1 : _attributes$timescale,
28594          sourceDuration = attributes.sourceDuration;
28595
28596      return {
28597        start: 0,
28598        end: Math.ceil(sourceDuration / (duration / timescale))
28599      };
28600    },
28601
28602    /**
28603     * Returns the current live window range of available segments for a dynamic MPD
28604     *
28605     * @param {Object} attributes
28606     *        Inheritied MPD attributes
28607     * @return {{ start: number, end: number }}
28608     *         The start and end numbers for available segments
28609     */
28610    dynamic: function dynamic(attributes) {
28611      var NOW = attributes.NOW,
28612          clientOffset = attributes.clientOffset,
28613          availabilityStartTime = attributes.availabilityStartTime,
28614          _attributes$timescale2 = attributes.timescale,
28615          timescale = _attributes$timescale2 === undefined ? 1 : _attributes$timescale2,
28616          duration = attributes.duration,
28617          _attributes$start = attributes.start,
28618          start = _attributes$start === undefined ? 0 : _attributes$start,
28619          _attributes$minimumUp = attributes.minimumUpdatePeriod,
28620          minimumUpdatePeriod = _attributes$minimumUp === undefined ? 0 : _attributes$minimumUp,
28621          _attributes$timeShift = attributes.timeShiftBufferDepth,
28622          timeShiftBufferDepth = _attributes$timeShift === undefined ? Infinity : _attributes$timeShift;
28623
28624      var now = (NOW + clientOffset) / 1000;
28625      var periodStartWC = availabilityStartTime + start;
28626      var periodEndWC = now + minimumUpdatePeriod;
28627      var periodDuration = periodEndWC - periodStartWC;
28628      var segmentCount = Math.ceil(periodDuration * timescale / duration);
28629      var availableStart = Math.floor((now - periodStartWC - timeShiftBufferDepth) * timescale / duration);
28630      var availableEnd = Math.floor((now - periodStartWC) * timescale / duration);
28631
28632      return {
28633        start: Math.max(0, availableStart),
28634        end: Math.min(segmentCount, availableEnd)
28635      };
28636    }
28637  };
28638
28639  /**
28640   * Maps a range of numbers to objects with information needed to build the corresponding
28641   * segment list
28642   *
28643   * @name toSegmentsCallback
28644   * @function
28645   * @param {number} number
28646   *        Number of the segment
28647   * @param {number} index
28648   *        Index of the number in the range list
28649   * @return {{ number: Number, duration: Number, timeline: Number, time: Number }}
28650   *         Object with segment timing and duration info
28651   */
28652
28653  /**
28654   * Returns a callback for Array.prototype.map for mapping a range of numbers to
28655   * information needed to build the segment list.
28656   *
28657   * @param {Object} attributes
28658   *        Inherited MPD attributes
28659   * @return {toSegmentsCallback}
28660   *         Callback map function
28661   */
28662  var toSegments = function toSegments(attributes) {
28663    return function (number, index) {
28664      var duration = attributes.duration,
28665          _attributes$timescale3 = attributes.timescale,
28666          timescale = _attributes$timescale3 === undefined ? 1 : _attributes$timescale3,
28667          periodIndex = attributes.periodIndex,
28668          _attributes$startNumb = attributes.startNumber,
28669          startNumber = _attributes$startNumb === undefined ? 1 : _attributes$startNumb;
28670
28671      return {
28672        number: startNumber + number,
28673        duration: duration / timescale,
28674        timeline: periodIndex,
28675        time: index * duration
28676      };
28677    };
28678  };
28679
28680  /**
28681   * Returns a list of objects containing segment timing and duration info used for
28682   * building the list of segments. This uses the @duration attribute specified
28683   * in the MPD manifest to derive the range of segments.
28684   *
28685   * @param {Object} attributes
28686   *        Inherited MPD attributes
28687   * @return {{number: number, duration: number, time: number, timeline: number}[]}
28688   *         List of Objects with segment timing and duration info
28689   */
28690  var parseByDuration = function parseByDuration(attributes) {
28691    var _attributes$type = attributes.type,
28692        type = _attributes$type === undefined ? 'static' : _attributes$type,
28693        duration = attributes.duration,
28694        _attributes$timescale4 = attributes.timescale,
28695        timescale = _attributes$timescale4 === undefined ? 1 : _attributes$timescale4,
28696        sourceDuration = attributes.sourceDuration;
28697
28698    var _segmentRange$type = segmentRange[type](attributes),
28699        start = _segmentRange$type.start,
28700        end = _segmentRange$type.end;
28701
28702    var segments = range(start, end).map(toSegments(attributes));
28703
28704    if (type === 'static') {
28705      var index = segments.length - 1;
28706
28707      // final segment may be less than full segment duration
28708      segments[index].duration = sourceDuration - duration / timescale * index;
28709    }
28710
28711    return segments;
28712  };
28713
28714  var identifierPattern = /\$([A-z]*)(?:(%0)([0-9]+)d)?\$/g;
28715
28716  /**
28717   * Replaces template identifiers with corresponding values. To be used as the callback
28718   * for String.prototype.replace
28719   *
28720   * @name replaceCallback
28721   * @function
28722   * @param {string} match
28723   *        Entire match of identifier
28724   * @param {string} identifier
28725   *        Name of matched identifier
28726   * @param {string} format
28727   *        Format tag string. Its presence indicates that padding is expected
28728   * @param {string} width
28729   *        Desired length of the replaced value. Values less than this width shall be left
28730   *        zero padded
28731   * @return {string}
28732   *         Replacement for the matched identifier
28733   */
28734
28735  /**
28736   * Returns a function to be used as a callback for String.prototype.replace to replace
28737   * template identifiers
28738   *
28739   * @param {Obect} values
28740   *        Object containing values that shall be used to replace known identifiers
28741   * @param {number} values.RepresentationID
28742   *        Value of the Representation@id attribute
28743   * @param {number} values.Number
28744   *        Number of the corresponding segment
28745   * @param {number} values.Bandwidth
28746   *        Value of the Representation@bandwidth attribute.
28747   * @param {number} values.Time
28748   *        Timestamp value of the corresponding segment
28749   * @return {replaceCallback}
28750   *         Callback to be used with String.prototype.replace to replace identifiers
28751   */
28752  var identifierReplacement = function identifierReplacement(values) {
28753    return function (match, identifier, format, width) {
28754      if (match === '$$') {
28755        // escape sequence
28756        return '$';
28757      }
28758
28759      if (typeof values[identifier] === 'undefined') {
28760        return match;
28761      }
28762
28763      var value = '' + values[identifier];
28764
28765      if (identifier === 'RepresentationID') {
28766        // Format tag shall not be present with RepresentationID
28767        return value;
28768      }
28769
28770      if (!format) {
28771        width = 1;
28772      } else {
28773        width = parseInt(width, 10);
28774      }
28775
28776      if (value.length >= width) {
28777        return value;
28778      }
28779
28780      return '' + new Array(width - value.length + 1).join('0') + value;
28781    };
28782  };
28783
28784  /**
28785   * Constructs a segment url from a template string
28786   *
28787   * @param {string} url
28788   *        Template string to construct url from
28789   * @param {Obect} values
28790   *        Object containing values that shall be used to replace known identifiers
28791   * @param {number} values.RepresentationID
28792   *        Value of the Representation@id attribute
28793   * @param {number} values.Number
28794   *        Number of the corresponding segment
28795   * @param {number} values.Bandwidth
28796   *        Value of the Representation@bandwidth attribute.
28797   * @param {number} values.Time
28798   *        Timestamp value of the corresponding segment
28799   * @return {string}
28800   *         Segment url with identifiers replaced
28801   */
28802  var constructTemplateUrl = function constructTemplateUrl(url, values) {
28803    return url.replace(identifierPattern, identifierReplacement(values));
28804  };
28805
28806  /**
28807   * Generates a list of objects containing timing and duration information about each
28808   * segment needed to generate segment uris and the complete segment object
28809   *
28810   * @param {Object} attributes
28811   *        Object containing all inherited attributes from parent elements with attribute
28812   *        names as keys
28813   * @param {Object[]|undefined} segmentTimeline
28814   *        List of objects representing the attributes of each S element contained within
28815   *        the SegmentTimeline element
28816   * @return {{number: number, duration: number, time: number, timeline: number}[]}
28817   *         List of Objects with segment timing and duration info
28818   */
28819  var parseTemplateInfo = function parseTemplateInfo(attributes, segmentTimeline) {
28820    if (!attributes.duration && !segmentTimeline) {
28821      // if neither @duration or SegmentTimeline are present, then there shall be exactly
28822      // one media segment
28823      return [{
28824        number: attributes.startNumber || 1,
28825        duration: attributes.sourceDuration,
28826        time: 0,
28827        timeline: attributes.periodIndex
28828      }];
28829    }
28830
28831    if (attributes.duration) {
28832      return parseByDuration(attributes);
28833    }
28834
28835    return parseByTimeline(attributes, segmentTimeline);
28836  };
28837
28838  /**
28839   * Generates a list of segments using information provided by the SegmentTemplate element
28840   *
28841   * @param {Object} attributes
28842   *        Object containing all inherited attributes from parent elements with attribute
28843   *        names as keys
28844   * @param {Object[]|undefined} segmentTimeline
28845   *        List of objects representing the attributes of each S element contained within
28846   *        the SegmentTimeline element
28847   * @return {Object[]}
28848   *         List of segment objects
28849   */
28850  var segmentsFromTemplate = function segmentsFromTemplate(attributes, segmentTimeline) {
28851    var templateValues = {
28852      RepresentationID: attributes.id,
28853      Bandwidth: attributes.bandwidth || 0
28854    };
28855
28856    var _attributes$initializ = attributes.initialization,
28857        initialization = _attributes$initializ === undefined ? { sourceURL: '', range: '' } : _attributes$initializ;
28858
28859    var mapSegment = urlTypeToSegment({
28860      baseUrl: attributes.baseUrl,
28861      source: constructTemplateUrl(initialization.sourceURL, templateValues),
28862      range: initialization.range
28863    });
28864
28865    var segments = parseTemplateInfo(attributes, segmentTimeline);
28866
28867    return segments.map(function (segment) {
28868      templateValues.Number = segment.number;
28869      templateValues.Time = segment.time;
28870
28871      var uri = constructTemplateUrl(attributes.media || '', templateValues);
28872
28873      return {
28874        uri: uri,
28875        timeline: segment.timeline,
28876        duration: segment.duration,
28877        resolvedUri: resolveUrl(attributes.baseUrl || '', uri),
28878        map: mapSegment,
28879        number: segment.number
28880      };
28881    });
28882  };
28883
28884  var errors = {
28885    INVALID_NUMBER_OF_PERIOD: 'INVALID_NUMBER_OF_PERIOD',
28886    DASH_EMPTY_MANIFEST: 'DASH_EMPTY_MANIFEST',
28887    DASH_INVALID_XML: 'DASH_INVALID_XML',
28888    NO_BASE_URL: 'NO_BASE_URL',
28889    MISSING_SEGMENT_INFORMATION: 'MISSING_SEGMENT_INFORMATION',
28890    SEGMENT_TIME_UNSPECIFIED: 'SEGMENT_TIME_UNSPECIFIED',
28891    UNSUPPORTED_UTC_TIMING_SCHEME: 'UNSUPPORTED_UTC_TIMING_SCHEME'
28892  };
28893
28894  /**
28895   * Converts a <SegmentUrl> (of type URLType from the DASH spec 5.3.9.2 Table 14)
28896   * to an object that matches the output of a segment in videojs/mpd-parser
28897   *
28898   * @param {Object} attributes
28899   *   Object containing all inherited attributes from parent elements with attribute
28900   *   names as keys
28901   * @param {Object} segmentUrl
28902   *   <SegmentURL> node to translate into a segment object
28903   * @return {Object} translated segment object
28904   */
28905  var SegmentURLToSegmentObject = function SegmentURLToSegmentObject(attributes, segmentUrl) {
28906    var baseUrl = attributes.baseUrl,
28907        _attributes$initializ = attributes.initialization,
28908        initialization = _attributes$initializ === undefined ? {} : _attributes$initializ;
28909
28910    var initSegment = urlTypeToSegment({
28911      baseUrl: baseUrl,
28912      source: initialization.sourceURL,
28913      range: initialization.range
28914    });
28915
28916    var segment = urlTypeToSegment({
28917      baseUrl: baseUrl,
28918      source: segmentUrl.media,
28919      range: segmentUrl.mediaRange
28920    });
28921
28922    segment.map = initSegment;
28923
28924    return segment;
28925  };
28926
28927  /**
28928   * Generates a list of segments using information provided by the SegmentList element
28929   * SegmentList (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes.  Each
28930   * node should be translated into segment.
28931   *
28932   * @param {Object} attributes
28933   *   Object containing all inherited attributes from parent elements with attribute
28934   *   names as keys
28935   * @param {Object[]|undefined} segmentTimeline
28936   *        List of objects representing the attributes of each S element contained within
28937   *        the SegmentTimeline element
28938   * @return {Object.<Array>} list of segments
28939   */
vendor: 18,658 bytes, lines 28940-29493
28940  var segmentsFromList = function segmentsFromList(attributes, segmentTimeline) {
28941    var duration = attributes.duration,
28942        _attributes$segmentUr = attributes.segmentUrls,
28943        segmentUrls = _attributes$segmentUr === undefined ? [] : _attributes$segmentUr;
28944
28945    // Per spec (5.3.9.2.1) no way to determine segment duration OR
28946    // if both SegmentTimeline and @duration are defined, it is outside of spec.
28947
28948    if (!duration && !segmentTimeline || duration && segmentTimeline) {
28949      throw new Error(errors.SEGMENT_TIME_UNSPECIFIED);
28950    }
28951
28952    var segmentUrlMap = segmentUrls.map(function (segmentUrlObject) {
28953      return SegmentURLToSegmentObject(attributes, segmentUrlObject);
28954    });
28955    var segmentTimeInfo = void 0;
28956
28957    if (duration) {
28958      segmentTimeInfo = parseByDuration(attributes);
28959    }
28960
28961    if (segmentTimeline) {
28962      segmentTimeInfo = parseByTimeline(attributes, segmentTimeline);
28963    }
28964
28965    var segments = segmentTimeInfo.map(function (segmentTime, index) {
28966      if (segmentUrlMap[index]) {
28967        var segment = segmentUrlMap[index];
28968
28969        segment.timeline = segmentTime.timeline;
28970        segment.duration = segmentTime.duration;
28971        segment.number = segmentTime.number;
28972        return segment;
28973      }
28974      // Since we're mapping we should get rid of any blank segments (in case
28975      // the given SegmentTimeline is handling for more elements than we have
28976      // SegmentURLs for).
28977    }).filter(function (segment) {
28978      return segment;
28979    });
28980
28981    return segments;
28982  };
28983
28984  /**
28985   * Translates SegmentBase into a set of segments.
28986   * (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes.  Each
28987   * node should be translated into segment.
28988   *
28989   * @param {Object} attributes
28990   *   Object containing all inherited attributes from parent elements with attribute
28991   *   names as keys
28992   * @return {Object.<Array>} list of segments
28993   */
28994  var segmentsFromBase = function segmentsFromBase(attributes) {
28995    var baseUrl = attributes.baseUrl,
28996        _attributes$initializ = attributes.initialization,
28997        initialization = _attributes$initializ === undefined ? {} : _attributes$initializ,
28998        sourceDuration = attributes.sourceDuration,
28999        _attributes$timescale = attributes.timescale,
29000        timescale = _attributes$timescale === undefined ? 1 : _attributes$timescale,
29001        _attributes$indexRang = attributes.indexRange,
29002        indexRange = _attributes$indexRang === undefined ? '' : _attributes$indexRang,
29003        duration = attributes.duration;
29004
29005    // base url is required for SegmentBase to work, per spec (Section 5.3.9.2.1)
29006
29007    if (!baseUrl) {
29008      throw new Error(errors.NO_BASE_URL);
29009    }
29010
29011    var initSegment = urlTypeToSegment({
29012      baseUrl: baseUrl,
29013      source: initialization.sourceURL,
29014      range: initialization.range
29015    });
29016    var segment = urlTypeToSegment({ baseUrl: baseUrl, source: baseUrl, range: indexRange });
29017
29018    segment.map = initSegment;
29019
29020    // If there is a duration, use it, otherwise use the given duration of the source
29021    // (since SegmentBase is only for one total segment)
29022    if (duration) {
29023      var segmentTimeInfo = parseByDuration(attributes);
29024
29025      if (segmentTimeInfo.length) {
29026        segment.duration = segmentTimeInfo[0].duration;
29027        segment.timeline = segmentTimeInfo[0].timeline;
29028      }
29029    } else if (sourceDuration) {
29030      segment.duration = sourceDuration / timescale;
29031      segment.timeline = 0;
29032    }
29033
29034    // This is used for mediaSequence
29035    segment.number = 0;
29036
29037    return [segment];
29038  };
29039
29040  var generateSegments = function generateSegments(_ref) {
29041    var attributes = _ref.attributes,
29042        segmentInfo = _ref.segmentInfo;
29043
29044    var segmentAttributes = void 0;
29045    var segmentsFn = void 0;
29046
29047    if (segmentInfo.template) {
29048      segmentsFn = segmentsFromTemplate;
29049      segmentAttributes = merge(attributes, segmentInfo.template);
29050    } else if (segmentInfo.base) {
29051      segmentsFn = segmentsFromBase;
29052      segmentAttributes = merge(attributes, segmentInfo.base);
29053    } else if (segmentInfo.list) {
29054      segmentsFn = segmentsFromList;
29055      segmentAttributes = merge(attributes, segmentInfo.list);
29056    }
29057
29058    if (!segmentsFn) {
29059      return { attributes: attributes };
29060    }
29061
29062    var segments = segmentsFn(segmentAttributes, segmentInfo.timeline);
29063
29064    // The @duration attribute will be used to determin the playlist's targetDuration which
29065    // must be in seconds. Since we've generated the segment list, we no longer need
29066    // @duration to be in @timescale units, so we can convert it here.
29067    if (segmentAttributes.duration) {
29068      var _segmentAttributes = segmentAttributes,
29069          duration = _segmentAttributes.duration,
29070          _segmentAttributes$ti = _segmentAttributes.timescale,
29071          timescale = _segmentAttributes$ti === undefined ? 1 : _segmentAttributes$ti;
29072
29073      segmentAttributes.duration = duration / timescale;
29074    } else if (segments.length) {
29075      // if there is no @duration attribute, use the largest segment duration as
29076      // as target duration
29077      segmentAttributes.duration = segments.reduce(function (max, segment) {
29078        return Math.max(max, Math.ceil(segment.duration));
29079      }, 0);
29080    } else {
29081      segmentAttributes.duration = 0;
29082    }
29083
29084    return {
29085      attributes: segmentAttributes,
29086      segments: segments
29087    };
29088  };
29089
29090  var toPlaylists = function toPlaylists(representations) {
29091    return representations.map(generateSegments);
29092  };
29093
29094  var findChildren = function findChildren(element, name) {
29095    return from(element.childNodes).filter(function (_ref) {
29096      var tagName = _ref.tagName;
29097      return tagName === name;
29098    });
29099  };
29100
29101  var getContent = function getContent(element) {
29102    return element.textContent.trim();
29103  };
29104
29105  var parseDuration = function parseDuration(str) {
29106    var SECONDS_IN_YEAR = 365 * 24 * 60 * 60;
29107    var SECONDS_IN_MONTH = 30 * 24 * 60 * 60;
29108    var SECONDS_IN_DAY = 24 * 60 * 60;
29109    var SECONDS_IN_HOUR = 60 * 60;
29110    var SECONDS_IN_MIN = 60;
29111
29112    // P10Y10M10DT10H10M10.1S
29113    var durationRegex = /P(?:(\d*)Y)?(?:(\d*)M)?(?:(\d*)D)?(?:T(?:(\d*)H)?(?:(\d*)M)?(?:([\d.]*)S)?)?/;
29114    var match = durationRegex.exec(str);
29115
29116    if (!match) {
29117      return 0;
29118    }
29119
29120    var _match$slice = match.slice(1),
29121        year = _match$slice[0],
29122        month = _match$slice[1],
29123        day = _match$slice[2],
29124        hour = _match$slice[3],
29125        minute = _match$slice[4],
29126        second = _match$slice[5];
29127
29128    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);
29129  };
29130
29131  var parseDate = function parseDate(str) {
29132    // Date format without timezone according to ISO 8601
29133    // YYY-MM-DDThh:mm:ss.ssssss
29134    var dateRegex = /^\d+-\d+-\d+T\d+:\d+:\d+(\.\d+)?$/;
29135
29136    // If the date string does not specifiy a timezone, we must specifiy UTC. This is
29137    // expressed by ending with 'Z'
29138    if (dateRegex.test(str)) {
29139      str += 'Z';
29140    }
29141
29142    return Date.parse(str);
29143  };
29144
29145  // TODO: maybe order these in some way that makes it easy to find specific attributes
29146  var parsers = {
29147    /**
29148     * Specifies the duration of the entire Media Presentation. Format is a duration string
29149     * as specified in ISO 8601
29150     *
29151     * @param {string} value
29152     *        value of attribute as a string
29153     * @return {number}
29154     *         The duration in seconds
29155     */
29156    mediaPresentationDuration: function mediaPresentationDuration(value) {
29157      return parseDuration(value);
29158    },
29159
29160    /**
29161     * Specifies the Segment availability start time for all Segments referred to in this
29162     * MPD. For a dynamic manifest, it specifies the anchor for the earliest availability
29163     * time. Format is a date string as specified in ISO 8601
29164     *
29165     * @param {string} value
29166     *        value of attribute as a string
29167     * @return {number}
29168     *         The date as seconds from unix epoch
29169     */
29170    availabilityStartTime: function availabilityStartTime(value) {
29171      return parseDate(value) / 1000;
29172    },
29173
29174    /**
29175     * Specifies the smallest period between potential changes to the MPD. Format is a
29176     * duration string as specified in ISO 8601
29177     *
29178     * @param {string} value
29179     *        value of attribute as a string
29180     * @return {number}
29181     *         The duration in seconds
29182     */
29183    minimumUpdatePeriod: function minimumUpdatePeriod(value) {
29184      return parseDuration(value);
29185    },
29186
29187    /**
29188     * Specifies the duration of the smallest time shifting buffer for any Representation
29189     * in the MPD. Format is a duration string as specified in ISO 8601
29190     *
29191     * @param {string} value
29192     *        value of attribute as a string
29193     * @return {number}
29194     *         The duration in seconds
29195     */
29196    timeShiftBufferDepth: function timeShiftBufferDepth(value) {
29197      return parseDuration(value);
29198    },
29199
29200    /**
29201     * Specifies the PeriodStart time of the Period relative to the availabilityStarttime.
29202     * Format is a duration string as specified in ISO 8601
29203     *
29204     * @param {string} value
29205     *        value of attribute as a string
29206     * @return {number}
29207     *         The duration in seconds
29208     */
29209    start: function start(value) {
29210      return parseDuration(value);
29211    },
29212
29213    /**
29214     * Specifies the width of the visual presentation
29215     *
29216     * @param {string} value
29217     *        value of attribute as a string
29218     * @return {number}
29219     *         The parsed width
29220     */
29221    width: function width(value) {
29222      return parseInt(value, 10);
29223    },
29224
29225    /**
29226     * Specifies the height of the visual presentation
29227     *
29228     * @param {string} value
29229     *        value of attribute as a string
29230     * @return {number}
29231     *         The parsed height
29232     */
29233    height: function height(value) {
29234      return parseInt(value, 10);
29235    },
29236
29237    /**
29238     * Specifies the bitrate of the representation
29239     *
29240     * @param {string} value
29241     *        value of attribute as a string
29242     * @return {number}
29243     *         The parsed bandwidth
29244     */
29245    bandwidth: function bandwidth(value) {
29246      return parseInt(value, 10);
29247    },
29248
29249    /**
29250     * Specifies the number of the first Media Segment in this Representation in the Period
29251     *
29252     * @param {string} value
29253     *        value of attribute as a string
29254     * @return {number}
29255     *         The parsed number
29256     */
29257    startNumber: function startNumber(value) {
29258      return parseInt(value, 10);
29259    },
29260
29261    /**
29262     * Specifies the timescale in units per seconds
29263     *
29264     * @param {string} value
29265     *        value of attribute as a string
29266     * @return {number}
29267     *         The aprsed timescale
29268     */
29269    timescale: function timescale(value) {
29270      return parseInt(value, 10);
29271    },
29272
29273    /**
29274     * Specifies the constant approximate Segment duration
29275     * NOTE: The <Period> element also contains an @duration attribute. This duration
29276     *       specifies the duration of the Period. This attribute is currently not
29277     *       supported by the rest of the parser, however we still check for it to prevent
29278     *       errors.
29279     *
29280     * @param {string} value
29281     *        value of attribute as a string
29282     * @return {number}
29283     *         The parsed duration
29284     */
29285    duration: function duration(value) {
29286      var parsedValue = parseInt(value, 10);
29287
29288      if (isNaN(parsedValue)) {
29289        return parseDuration(value);
29290      }
29291
29292      return parsedValue;
29293    },
29294
29295    /**
29296     * Specifies the Segment duration, in units of the value of the @timescale.
29297     *
29298     * @param {string} value
29299     *        value of attribute as a string
29300     * @return {number}
29301     *         The parsed duration
29302     */
29303    d: function d(value) {
29304      return parseInt(value, 10);
29305    },
29306
29307    /**
29308     * Specifies the MPD start time, in @timescale units, the first Segment in the series
29309     * starts relative to the beginning of the Period
29310     *
29311     * @param {string} value
29312     *        value of attribute as a string
29313     * @return {number}
29314     *         The parsed time
29315     */
29316    t: function t(value) {
29317      return parseInt(value, 10);
29318    },
29319
29320    /**
29321     * Specifies the repeat count of the number of following contiguous Segments with the
29322     * same duration expressed by the value of @d
29323     *
29324     * @param {string} value
29325     *        value of attribute as a string
29326     * @return {number}
29327     *         The parsed number
29328     */
29329    r: function r(value) {
29330      return parseInt(value, 10);
29331    },
29332
29333    /**
29334     * Default parser for all other attributes. Acts as a no-op and just returns the value
29335     * as a string
29336     *
29337     * @param {string} value
29338     *        value of attribute as a string
29339     * @return {string}
29340     *         Unparsed value
29341     */
29342    DEFAULT: function DEFAULT(value) {
29343      return value;
29344    }
29345  };
29346
29347  /**
29348   * Gets all the attributes and values of the provided node, parses attributes with known
29349   * types, and returns an object with attribute names mapped to values.
29350   *
29351   * @param {Node} el
29352   *        The node to parse attributes from
29353   * @return {Object}
29354   *         Object with all attributes of el parsed
29355   */
29356  var parseAttributes$1 = function parseAttributes(el) {
29357    if (!(el && el.attributes)) {
29358      return {};
29359    }
29360
29361    return from(el.attributes).reduce(function (a, e) {
29362      var parseFn = parsers[e.name] || parsers.DEFAULT;
29363
29364      a[e.name] = parseFn(e.value);
29365
29366      return a;
29367    }, {});
29368  };
29369
29370  function decodeB64ToUint8Array(b64Text) {
29371    var decodedString = window_1.atob(b64Text);
29372    var array = new Uint8Array(decodedString.length);
29373
29374    for (var i = 0; i < decodedString.length; i++) {
29375      array[i] = decodedString.charCodeAt(i);
29376    }
29377    return array;
29378  }
29379
29380  var keySystemsMap = {
29381    'urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b': 'org.w3.clearkey',
29382    'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed': 'com.widevine.alpha',
29383    'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95': 'com.microsoft.playready',
29384    'urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb': 'com.adobe.primetime'
29385  };
29386
29387  /**
29388   * Builds a list of urls that is the product of the reference urls and BaseURL values
29389   *
29390   * @param {string[]} referenceUrls
29391   *        List of reference urls to resolve to
29392   * @param {Node[]} baseUrlElements
29393   *        List of BaseURL nodes from the mpd
29394   * @return {string[]}
29395   *         List of resolved urls
29396   */
29397  var buildBaseUrls = function buildBaseUrls(referenceUrls, baseUrlElements) {
29398    if (!baseUrlElements.length) {
29399      return referenceUrls;
29400    }
29401
29402    return flatten(referenceUrls.map(function (reference) {
29403      return baseUrlElements.map(function (baseUrlElement) {
29404        return resolveUrl(reference, getContent(baseUrlElement));
29405      });
29406    }));
29407  };
29408
29409  /**
29410   * Contains all Segment information for its containing AdaptationSet
29411   *
29412   * @typedef {Object} SegmentInformation
29413   * @property {Object|undefined} template
29414   *           Contains the attributes for the SegmentTemplate node
29415   * @property {Object[]|undefined} timeline
29416   *           Contains a list of atrributes for each S node within the SegmentTimeline node
29417   * @property {Object|undefined} list
29418   *           Contains the attributes for the SegmentList node
29419   * @property {Object|undefined} base
29420   *           Contains the attributes for the SegmentBase node
29421   */
29422
29423  /**
29424   * Returns all available Segment information contained within the AdaptationSet node
29425   *
29426   * @param {Node} adaptationSet
29427   *        The AdaptationSet node to get Segment information from
29428   * @return {SegmentInformation}
29429   *         The Segment information contained within the provided AdaptationSet
29430   */
29431  var getSegmentInformation = function getSegmentInformation(adaptationSet) {
29432    var segmentTemplate = findChildren(adaptationSet, 'SegmentTemplate')[0];
29433    var segmentList = findChildren(adaptationSet, 'SegmentList')[0];
29434    var segmentUrls = segmentList && findChildren(segmentList, 'SegmentURL').map(function (s) {
29435      return merge({ tag: 'SegmentURL' }, parseAttributes$1(s));
29436    });
29437    var segmentBase = findChildren(adaptationSet, 'SegmentBase')[0];
29438    var segmentTimelineParentNode = segmentList || segmentTemplate;
29439    var segmentTimeline = segmentTimelineParentNode && findChildren(segmentTimelineParentNode, 'SegmentTimeline')[0];
29440    var segmentInitializationParentNode = segmentList || segmentBase || segmentTemplate;
29441    var segmentInitialization = segmentInitializationParentNode && findChildren(segmentInitializationParentNode, 'Initialization')[0];
29442
29443    // SegmentTemplate is handled slightly differently, since it can have both
29444    // @initialization and an <Initialization> node.  @initialization can be templated,
29445    // while the node can have a url and range specified.  If the <SegmentTemplate> has
29446    // both @initialization and an <Initialization> subelement we opt to override with
29447    // the node, as this interaction is not defined in the spec.
29448    var template = segmentTemplate && parseAttributes$1(segmentTemplate);
29449
29450    if (template && segmentInitialization) {
29451      template.initialization = segmentInitialization && parseAttributes$1(segmentInitialization);
29452    } else if (template && template.initialization) {
29453      // If it is @initialization we convert it to an object since this is the format that
29454      // later functions will rely on for the initialization segment.  This is only valid
29455      // for <SegmentTemplate>
29456      template.initialization = { sourceURL: template.initialization };
29457    }
29458
29459    var segmentInfo = {
29460      template: template,
29461      timeline: segmentTimeline && findChildren(segmentTimeline, 'S').map(function (s) {
29462        return parseAttributes$1(s);
29463      }),
29464      list: segmentList && merge(parseAttributes$1(segmentList), {
29465        segmentUrls: segmentUrls,
29466        initialization: parseAttributes$1(segmentInitialization)
29467      }),
29468      base: segmentBase && merge(parseAttributes$1(segmentBase), {
29469        initialization: parseAttributes$1(segmentInitialization)
29470      })
29471    };
29472
29473    Object.keys(segmentInfo).forEach(function (key) {
29474      if (!segmentInfo[key]) {
29475        delete segmentInfo[key];
29476      }
29477    });
29478
29479    return segmentInfo;
29480  };
29481
29482  /**
29483   * Contains Segment information and attributes needed to construct a Playlist object
29484   * from a Representation
29485   *
29486   * @typedef {Object} RepresentationInformation
29487   * @property {SegmentInformation} segmentInfo
29488   *           Segment information for this Representation
29489   * @property {Object} attributes
29490   *           Inherited attributes for this Representation
29491   */
29492
29493  /**
29494   * Maps a Representation node to an object containing Segment information and attributes
29495   *
29496   * @name inheritBaseUrlsCallback
29497   * @function
29498   * @param {Node} representation
29499   *        Representation node from the mpd
29500   * @return {RepresentationInformation}
29501   *         Representation information needed to construct a Playlist object
29502   */
29503
29504  /**
29505   * Returns a callback for Array.prototype.map for mapping Representation nodes to
29506   * Segment information and attributes using inherited BaseURL nodes.
29507   *
29508   * @param {Object} adaptationSetAttributes
29509   *        Contains attributes inherited by the AdaptationSet
29510   * @param {string[]} adaptationSetBaseUrls
29511   *        Contains list of resolved base urls inherited by the AdaptationSet
29512   * @param {SegmentInformation} adaptationSetSegmentInfo
29513   *        Contains Segment information for the AdaptationSet
29514   * @return {inheritBaseUrlsCallback}
29515   *         Callback map function
29516   */
29517  var inheritBaseUrls = function inheritBaseUrls(adaptationSetAttributes, adaptationSetBaseUrls, adaptationSetSegmentInfo) {
29518    return function (representation) {
29519      var repBaseUrlElements = findChildren(representation, 'BaseURL');
29520      var repBaseUrls = buildBaseUrls(adaptationSetBaseUrls, repBaseUrlElements);
29521      var attributes = merge(adaptationSetAttributes, parseAttributes$1(representation));
29522      var representationSegmentInfo = getSegmentInformation(representation);
29523
29524      return repBaseUrls.map(function (baseUrl) {
29525        return {
29526          segmentInfo: merge(adaptationSetSegmentInfo, representationSegmentInfo),
29527          attributes: merge(attributes, { baseUrl: baseUrl })
29528        };
29529      });
29530    };
29531  };
29532
29533  /**
29534   * Tranforms a series of content protection nodes to
vendor: 10,747 bytes, lines 29535-29843
29535   * an object containing pssh data by key system
29536   *
29537   * @param {Node[]} contentProtectionNodes
29538   *        Content protection nodes
29539   * @return {Object}
29540   *        Object containing pssh data by key system
29541   */
29542  var generateKeySystemInformation = function generateKeySystemInformation(contentProtectionNodes) {
29543    return contentProtectionNodes.reduce(function (acc, node) {
29544      var attributes = parseAttributes$1(node);
29545      var keySystem = keySystemsMap[attributes.schemeIdUri];
29546
29547      if (keySystem) {
29548        acc[keySystem] = { attributes: attributes };
29549
29550        var psshNode = findChildren(node, 'cenc:pssh')[0];
29551
29552        if (psshNode) {
29553          var pssh = getContent(psshNode);
29554          var psshBuffer = pssh && decodeB64ToUint8Array(pssh);
29555
29556          acc[keySystem].pssh = psshBuffer;
29557        }
29558      }
29559
29560      return acc;
29561    }, {});
29562  };
29563
29564  /**
29565   * Maps an AdaptationSet node to a list of Representation information objects
29566   *
29567   * @name toRepresentationsCallback
29568   * @function
29569   * @param {Node} adaptationSet
29570   *        AdaptationSet node from the mpd
29571   * @return {RepresentationInformation[]}
29572   *         List of objects containing Representaion information
29573   */
29574
29575  /**
29576   * Returns a callback for Array.prototype.map for mapping AdaptationSet nodes to a list of
29577   * Representation information objects
29578   *
29579   * @param {Object} periodAttributes
29580   *        Contains attributes inherited by the Period
29581   * @param {string[]} periodBaseUrls
29582   *        Contains list of resolved base urls inherited by the Period
29583   * @param {string[]} periodSegmentInfo
29584   *        Contains Segment Information at the period level
29585   * @return {toRepresentationsCallback}
29586   *         Callback map function
29587   */
29588  var toRepresentations = function toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo) {
29589    return function (adaptationSet) {
29590      var adaptationSetAttributes = parseAttributes$1(adaptationSet);
29591      var adaptationSetBaseUrls = buildBaseUrls(periodBaseUrls, findChildren(adaptationSet, 'BaseURL'));
29592      var role = findChildren(adaptationSet, 'Role')[0];
29593      var roleAttributes = { role: parseAttributes$1(role) };
29594
29595      var attrs = merge(periodAttributes, adaptationSetAttributes, roleAttributes);
29596
29597      var contentProtection = generateKeySystemInformation(findChildren(adaptationSet, 'ContentProtection'));
29598
29599      if (Object.keys(contentProtection).length) {
29600        attrs = merge(attrs, { contentProtection: contentProtection });
29601      }
29602
29603      var segmentInfo = getSegmentInformation(adaptationSet);
29604      var representations = findChildren(adaptationSet, 'Representation');
29605      var adaptationSetSegmentInfo = merge(periodSegmentInfo, segmentInfo);
29606
29607      return flatten(representations.map(inheritBaseUrls(attrs, adaptationSetBaseUrls, adaptationSetSegmentInfo)));
29608    };
29609  };
29610
29611  /**
29612   * Maps an Period node to a list of Representation inforamtion objects for all
29613   * AdaptationSet nodes contained within the Period
29614   *
29615   * @name toAdaptationSetsCallback
29616   * @function
29617   * @param {Node} period
29618   *        Period node from the mpd
29619   * @param {number} periodIndex
29620   *        Index of the Period within the mpd
29621   * @return {RepresentationInformation[]}
29622   *         List of objects containing Representaion information
29623   */
29624
29625  /**
29626   * Returns a callback for Array.prototype.map for mapping Period nodes to a list of
29627   * Representation information objects
29628   *
29629   * @param {Object} mpdAttributes
29630   *        Contains attributes inherited by the mpd
29631   * @param {string[]} mpdBaseUrls
29632   *        Contains list of resolved base urls inherited by the mpd
29633   * @return {toAdaptationSetsCallback}
29634   *         Callback map function
29635   */
29636  var toAdaptationSets = function toAdaptationSets(mpdAttributes, mpdBaseUrls) {
29637    return function (period, periodIndex) {
29638      var periodBaseUrls = buildBaseUrls(mpdBaseUrls, findChildren(period, 'BaseURL'));
29639      var periodAtt = parseAttributes$1(period);
29640      var periodAttributes = merge(mpdAttributes, periodAtt, { periodIndex: periodIndex });
29641      var adaptationSets = findChildren(period, 'AdaptationSet');
29642      var periodSegmentInfo = getSegmentInformation(period);
29643
29644      return flatten(adaptationSets.map(toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo)));
29645    };
29646  };
29647
29648  /**
29649   * Traverses the mpd xml tree to generate a list of Representation information objects
29650   * that have inherited attributes from parent nodes
29651   *
29652   * @param {Node} mpd
29653   *        The root node of the mpd
29654   * @param {Object} options
29655   *        Available options for inheritAttributes
29656   * @param {string} options.manifestUri
29657   *        The uri source of the mpd
29658   * @param {number} options.NOW
29659   *        Current time per DASH IOP.  Default is current time in ms since epoch
29660   * @param {number} options.clientOffset
29661   *        Client time difference from NOW (in milliseconds)
29662   * @return {RepresentationInformation[]}
29663   *         List of objects containing Representation information
29664   */
29665  var inheritAttributes = function inheritAttributes(mpd) {
29666    var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
29667    var _options$manifestUri = options.manifestUri,
29668        manifestUri = _options$manifestUri === undefined ? '' : _options$manifestUri,
29669        _options$NOW = options.NOW,
29670        NOW = _options$NOW === undefined ? Date.now() : _options$NOW,
29671        _options$clientOffset = options.clientOffset,
29672        clientOffset = _options$clientOffset === undefined ? 0 : _options$clientOffset;
29673
29674    var periods = findChildren(mpd, 'Period');
29675
29676    if (periods.length !== 1) {
29677      // TODO add support for multiperiod
29678      throw new Error(errors.INVALID_NUMBER_OF_PERIOD);
29679    }
29680
29681    var mpdAttributes = parseAttributes$1(mpd);
29682    var mpdBaseUrls = buildBaseUrls([manifestUri], findChildren(mpd, 'BaseURL'));
29683
29684    mpdAttributes.sourceDuration = mpdAttributes.mediaPresentationDuration || 0;
29685    mpdAttributes.NOW = NOW;
29686    mpdAttributes.clientOffset = clientOffset;
29687
29688    return flatten(periods.map(toAdaptationSets(mpdAttributes, mpdBaseUrls)));
29689  };
29690
29691  var stringToMpdXml = function stringToMpdXml(manifestString) {
29692    if (manifestString === '') {
29693      throw new Error(errors.DASH_EMPTY_MANIFEST);
29694    }
29695
29696    var parser = new window_1.DOMParser();
29697    var xml = parser.parseFromString(manifestString, 'application/xml');
29698    var mpd = xml && xml.documentElement.tagName === 'MPD' ? xml.documentElement : null;
29699
29700    if (!mpd || mpd && mpd.getElementsByTagName('parsererror').length > 0) {
29701      throw new Error(errors.DASH_INVALID_XML);
29702    }
29703
29704    return mpd;
29705  };
29706
29707  /**
29708   * Parses the manifest for a UTCTiming node, returning the nodes attributes if found
29709   *
29710   * @param {string} mpd
29711   *        XML string of the MPD manifest
29712   * @return {Object|null}
29713   *         Attributes of UTCTiming node specified in the manifest. Null if none found
29714   */
29715  var parseUTCTimingScheme = function parseUTCTimingScheme(mpd) {
29716    var UTCTimingNode = findChildren(mpd, 'UTCTiming')[0];
29717
29718    if (!UTCTimingNode) {
29719      return null;
29720    }
29721
29722    var attributes = parseAttributes$1(UTCTimingNode);
29723
29724    switch (attributes.schemeIdUri) {
29725      case 'urn:mpeg:dash:utc:http-head:2014':
29726      case 'urn:mpeg:dash:utc:http-head:2012':
29727        attributes.method = 'HEAD';
29728        break;
29729      case 'urn:mpeg:dash:utc:http-xsdate:2014':
29730      case 'urn:mpeg:dash:utc:http-iso:2014':
29731      case 'urn:mpeg:dash:utc:http-xsdate:2012':
29732      case 'urn:mpeg:dash:utc:http-iso:2012':
29733        attributes.method = 'GET';
29734        break;
29735      case 'urn:mpeg:dash:utc:direct:2014':
29736      case 'urn:mpeg:dash:utc:direct:2012':
29737        attributes.method = 'DIRECT';
29738        attributes.value = Date.parse(attributes.value);
29739        break;
29740      case 'urn:mpeg:dash:utc:http-ntp:2014':
29741      case 'urn:mpeg:dash:utc:ntp:2014':
29742      case 'urn:mpeg:dash:utc:sntp:2014':
29743      default:
29744        throw new Error(errors.UNSUPPORTED_UTC_TIMING_SCHEME);
29745    }
29746
29747    return attributes;
29748  };
29749
29750  var parse = function parse(manifestString, options) {
29751    return toM3u8(toPlaylists(inheritAttributes(stringToMpdXml(manifestString), options)));
29752  };
29753
29754  /**
29755   * Parses the manifest for a UTCTiming node, returning the nodes attributes if found
29756   *
29757   * @param {string} manifestString
29758   *        XML string of the MPD manifest
29759   * @return {Object|null}
29760   *         Attributes of UTCTiming node specified in the manifest. Null if none found
29761   */
29762  var parseUTCTiming = function parseUTCTiming(manifestString) {
29763    return parseUTCTimingScheme(stringToMpdXml(manifestString));
29764  };
29765
29766  var toUnsigned = function toUnsigned(value) {
29767    return value >>> 0;
29768  };
29769
29770  var bin = {
29771    toUnsigned: toUnsigned
29772  };
29773  var bin_1 = bin.toUnsigned;
29774
29775  var bin$1 = /*#__PURE__*/Object.freeze({
29776    default: bin,
29777    __moduleExports: bin,
29778    toUnsigned: bin_1
29779  });
29780
29781  var require$$0$1 = ( bin$1 && bin ) || bin$1;
29782
29783  var toUnsigned$1 = require$$0$1.toUnsigned;
29784  var _findBox, parseType, timescale, startTime;
29785
29786  // Find the data for a box specified by its path
29787  _findBox = function findBox(data, path) {
29788    var results = [],
29789        i,
29790        size,
29791        type,
29792        end,
29793        subresults;
29794
29795    if (!path.length) {
29796      // short-circuit the search for empty paths
29797      return null;
29798    }
29799
29800    for (i = 0; i < data.byteLength;) {
29801      size = toUnsigned$1(data[i] << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]);
29802
29803      type = parseType(data.subarray(i + 4, i + 8));
29804
29805      end = size > 1 ? i + size : data.byteLength;
29806
29807      if (type === path[0]) {
29808        if (path.length === 1) {
29809          // this is the end of the path and we've found the box we were
29810          // looking for
29811          results.push(data.subarray(i + 8, end));
29812        } else {
29813          // recursively search for the next box along the path
29814          subresults = _findBox(data.subarray(i + 8, end), path.slice(1));
29815          if (subresults.length) {
29816            results = results.concat(subresults);
29817          }
29818        }
29819      }
29820      i = end;
29821    }
29822
29823    // we've finished searching all of data
29824    return results;
29825  };
29826
29827  /**
29828   * Returns the string representation of an ASCII encoded four byte buffer.
29829   * @param buffer {Uint8Array} a four-byte buffer to translate
29830   * @return {string} the corresponding string
29831   */
29832  parseType = function parseType(buffer) {
29833    var result = '';
29834    result += String.fromCharCode(buffer[0]);
29835    result += String.fromCharCode(buffer[1]);
29836    result += String.fromCharCode(buffer[2]);
29837    result += String.fromCharCode(buffer[3]);
29838    return result;
29839  };
29840
29841  /**
29842   * Parses an MP4 initialization segment and extracts the timescale
29843   * values for any declared tracks. Timescale values indicate the
29844   * number of clock ticks per second to assume for time-based values
29845   * elsewhere in the MP4.
29846   *
29847   * To determine the start time of an MP4, you need two pieces of
29848   * information: the timescale unit and the earliest base media decode
29849   * time. Multiple timescales can be specified within an MP4 but the
29850   * base media decode time is always expressed in the timescale from
29851   * the media header box for the track:
29852   * ```
29853   * moov > trak > mdia > mdhd.timescale
29854   * ```
29855   * @param init {Uint8Array} the bytes of the init segment
29856   * @return {object} a hash of track ids to timescale values or null if
29857   * the init segment is malformed.
29858   */
29859  timescale = function timescale(init) {
29860    var result = {},
29861        traks = _findBox(init, ['moov', 'trak']);
29862
29863    // mdhd timescale
29864    return traks.reduce(function (result, trak) {
29865      var tkhd, version, index, id, mdhd;
29866
29867      tkhd = _findBox(trak, ['tkhd'])[0];
29868      if (!tkhd) {
29869        return null;
29870      }
29871      version = tkhd[0];
29872      index = version === 0 ? 12 : 20;
29873      id = toUnsigned$1(tkhd[index] << 24 | tkhd[index + 1] << 16 | tkhd[index + 2] << 8 | tkhd[index + 3]);
29874
29875      mdhd = _findBox(trak, ['mdia', 'mdhd'])[0];
29876      if (!mdhd) {
29877        return null;
29878      }
29879      version = mdhd[0];
29880      index = version === 0 ? 12 : 20;
29881      result[id] = toUnsigned$1(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]);
29882      return result;
29883    }, result);
29884  };
29885
29886  /**
29887   * Determine the base media decode start time, in seconds, for an MP4
29888   * fragment. If multiple fragments are specified, the earliest time is
29889   * returned.
29890   *
29891   * The base media decode time can be parsed from track fragment
29892   * metadata:
29893   * ```
29894   * moof > traf > tfdt.baseMediaDecodeTime
29895   * ```
29896   * It requires the timescale value from the mdhd to interpret.
29897   *
29898   * @param timescale {object} a hash of track ids to timescale values.
29899   * @return {number} the earliest base media decode start time for the
29900   * fragment, in seconds
29901   */
29902  startTime = function startTime(timescale, fragment) {
29903    var trafs, baseTimes, result;
29904
29905    // we need info from two childrend of each track fragment box
29906    trafs = _findBox(fragment, ['moof', 'traf']);
29907
29908    // determine the start times for each track
29909    baseTimes = [].concat.apply([], trafs.map(function (traf) {
29910      return _findBox(traf, ['tfhd']).map(function (tfhd) {
29911        var id, scale, baseTime;
29912
29913        // get the track id from the tfhd
29914        id = toUnsigned$1(tfhd[4] << 24 | tfhd[5] << 16 | tfhd[6] << 8 | tfhd[7]);
29915        // assume a 90kHz clock if no timescale was specified
29916        scale = timescale[id] || 90e3;
29917
29918        // get the base media decode time from the tfdt
29919        baseTime = _findBox(traf, ['tfdt']).map(function (tfdt) {
29920          var version, result;
29921
29922          version = tfdt[0];
29923          result = toUnsigned$1(tfdt[4] << 24 | tfdt[5] << 16 | tfdt[6] << 8 | tfdt[7]);
29924          if (version === 1) {
29925            result *= Math.pow(2, 32);
29926            result += toUnsigned$1(tfdt[8] << 24 | tfdt[9] << 16 | tfdt[10] << 8 | tfdt[11]);
29927          }
29928          return result;
29929        })[0];
29930        baseTime = baseTime || Infinity;
29931
29932        // convert base time to seconds
29933        return baseTime / scale;
29934      });
29935    }));
29936
29937    // return the minimum
29938    result = Math.min.apply(null, baseTimes);
29939    return isFinite(result) ? result : 0;
29940  };
29941
29942  var probe = {
29943    parseType: parseType,
29944    timescale: timescale,
29945    startTime: startTime
29946  };
29947
29948  var streamTypes = {
29949    H264_STREAM_TYPE: 0x1B,
29950    ADTS_STREAM_TYPE: 0x0F,
29951    METADATA_STREAM_TYPE: 0x15
29952  };
29953  var streamTypes_1 = streamTypes.H264_STREAM_TYPE;
29954  var streamTypes_2 = streamTypes.ADTS_STREAM_TYPE;
29955  var streamTypes_3 = streamTypes.METADATA_STREAM_TYPE;
29956
29957  var streamTypes$1 = /*#__PURE__*/Object.freeze({
29958    default: streamTypes,
29959    __moduleExports: streamTypes,
29960    H264_STREAM_TYPE: streamTypes_1,
29961    ADTS_STREAM_TYPE: streamTypes_2,
29962    METADATA_STREAM_TYPE: streamTypes_3
29963  });
29964
29965  /**
29966   * mux.js
29967   *
29968   * Copyright (c) 2014 Brightcove
29969   * All rights reserved.
29970   *
29971   * A lightweight readable stream implemention that handles event dispatching.
29972   * Objects that inherit from streams should call init in their constructors.
29973   */
29974
29975  var Stream$1 = function Stream() {
29976    this.init = function () {
29977      var listeners = {};
29978      /**
29979       * Add a listener for a specified event type.
29980       * @param type {string} the event name
29981       * @param listener {function} the callback to be invoked when an event of
29982       * the specified type occurs
29983       */
29984      this.on = function (type, listener) {
29985        if (!listeners[type]) {
29986          listeners[type] = [];
29987        }
29988        listeners[type] = listeners[type].concat(listener);
29989      };
29990      /**
29991       * Remove a listener for a specified event type.
29992       * @param type {string} the event name
29993       * @param listener {function} a function previously registered for this
29994       * type of event through `on`
29995       */
29996      this.off = function (type, listener) {
29997        var index;
29998        if (!listeners[type]) {
29999          return false;
vendor: 20,700 bytes, lines 30000-30619
30000        }
30001        index = listeners[type].indexOf(listener);
30002        listeners[type] = listeners[type].slice();
30003        listeners[type].splice(index, 1);
30004        return index > -1;
30005      };
30006      /**
30007       * Trigger an event of the specified type on this stream. Any additional
30008       * arguments to this function are passed as parameters to event listeners.
30009       * @param type {string} the event name
30010       */
30011      this.trigger = function (type) {
30012        var callbacks, i, length, args;
30013        callbacks = listeners[type];
30014        if (!callbacks) {
30015          return;
30016        }
30017        // Slicing the arguments on every invocation of this method
30018        // can add a significant amount of overhead. Avoid the
30019        // intermediate object creation for the common case of a
30020        // single callback argument
30021        if (arguments.length === 2) {
30022          length = callbacks.length;
30023          for (i = 0; i < length; ++i) {
30024            callbacks[i].call(this, arguments[1]);
30025          }
30026        } else {
30027          args = [];
30028          i = arguments.length;
30029          for (i = 1; i < arguments.length; ++i) {
30030            args.push(arguments[i]);
30031          }
30032          length = callbacks.length;
30033          for (i = 0; i < length; ++i) {
30034            callbacks[i].apply(this, args);
30035          }
30036        }
30037      };
30038      /**
30039       * Destroys the stream and cleans up.
30040       */
30041      this.dispose = function () {
30042        listeners = {};
30043      };
30044    };
30045  };
30046
30047  /**
30048   * Forwards all `data` events on this stream to the destination stream. The
30049   * destination stream should provide a method `push` to receive the data
30050   * events as they arrive.
30051   * @param destination {stream} the stream that will receive all `data` events
30052   * @param autoFlush {boolean} if false, we will not call `flush` on the destination
30053   *                            when the current stream emits a 'done' event
30054   * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
30055   */
30056  Stream$1.prototype.pipe = function (destination) {
30057    this.on('data', function (data) {
30058      destination.push(data);
30059    });
30060
30061    this.on('done', function (flushSource) {
30062      destination.flush(flushSource);
30063    });
30064
30065    return destination;
30066  };
30067
30068  // Default stream functions that are expected to be overridden to perform
30069  // actual work. These are provided by the prototype as a sort of no-op
30070  // implementation so that we don't have to check for their existence in the
30071  // `pipe` function above.
30072  Stream$1.prototype.push = function (data) {
30073    this.trigger('data', data);
30074  };
30075
30076  Stream$1.prototype.flush = function (flushSource) {
30077    this.trigger('done', flushSource);
30078  };
30079
30080  var stream = Stream$1;
30081
30082  var stream$1 = /*#__PURE__*/Object.freeze({
30083    default: stream,
30084    __moduleExports: stream
30085  });
30086
30087  var Stream$2 = ( stream$1 && stream ) || stream$1;
30088
30089  var MAX_TS = 8589934592;
30090
30091  var RO_THRESH = 4294967296;
30092
30093  var handleRollover = function handleRollover(value, reference) {
30094    var direction = 1;
30095
30096    if (value > reference) {
30097      // If the current timestamp value is greater than our reference timestamp and we detect a
30098      // timestamp rollover, this means the roll over is happening in the opposite direction.
30099      // Example scenario: Enter a long stream/video just after a rollover occurred. The reference
30100      // point will be set to a small number, e.g. 1. The user then seeks backwards over the
30101      // rollover point. In loading this segment, the timestamp values will be very large,
30102      // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust
30103      // the time stamp to be `value - 2^33`.
30104      direction = -1;
30105    }
30106
30107    // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will
30108    // cause an incorrect adjustment.
30109    while (Math.abs(reference - value) > RO_THRESH) {
30110      value += direction * MAX_TS;
30111    }
30112
30113    return value;
30114  };
30115
30116  var TimestampRolloverStream = function TimestampRolloverStream(type) {
30117    var lastDTS, referenceDTS;
30118
30119    TimestampRolloverStream.prototype.init.call(this);
30120
30121    this.type_ = type;
30122
30123    this.push = function (data) {
30124      if (data.type !== this.type_) {
30125        return;
30126      }
30127
30128      if (referenceDTS === undefined) {
30129        referenceDTS = data.dts;
30130      }
30131
30132      data.dts = handleRollover(data.dts, referenceDTS);
30133      data.pts = handleRollover(data.pts, referenceDTS);
30134
30135      lastDTS = data.dts;
30136
30137      this.trigger('data', data);
30138    };
30139
30140    this.flush = function () {
30141      referenceDTS = lastDTS;
30142      this.trigger('done');
30143    };
30144
30145    this.discontinuity = function () {
30146      referenceDTS = void 0;
30147      lastDTS = void 0;
30148    };
30149  };
30150
30151  TimestampRolloverStream.prototype = new Stream$2();
30152
30153  var timestampRolloverStream = {
30154    TimestampRolloverStream: TimestampRolloverStream,
30155    handleRollover: handleRollover
30156  };
30157  var timestampRolloverStream_1 = timestampRolloverStream.TimestampRolloverStream;
30158  var timestampRolloverStream_2 = timestampRolloverStream.handleRollover;
30159
30160  var timestampRolloverStream$1 = /*#__PURE__*/Object.freeze({
30161    default: timestampRolloverStream,
30162    __moduleExports: timestampRolloverStream,
30163    TimestampRolloverStream: timestampRolloverStream_1,
30164    handleRollover: timestampRolloverStream_2
30165  });
30166
30167  var StreamTypes = ( streamTypes$1 && streamTypes ) || streamTypes$1;
30168
30169  var parsePid = function parsePid(packet) {
30170    var pid = packet[1] & 0x1f;
30171    pid <<= 8;
30172    pid |= packet[2];
30173    return pid;
30174  };
30175
30176  var parsePayloadUnitStartIndicator = function parsePayloadUnitStartIndicator(packet) {
30177    return !!(packet[1] & 0x40);
30178  };
30179
30180  var parseAdaptionField = function parseAdaptionField(packet) {
30181    var offset = 0;
30182    // if an adaption field is present, its length is specified by the
30183    // fifth byte of the TS packet header. The adaptation field is
30184    // used to add stuffing to PES packets that don't fill a complete
30185    // TS packet, and to specify some forms of timing and control data
30186    // that we do not currently use.
30187    if ((packet[3] & 0x30) >>> 4 > 0x01) {
30188      offset += packet[4] + 1;
30189    }
30190    return offset;
30191  };
30192
30193  var parseType$1 = function parseType(packet, pmtPid) {
30194    var pid = parsePid(packet);
30195    if (pid === 0) {
30196      return 'pat';
30197    } else if (pid === pmtPid) {
30198      return 'pmt';
30199    } else if (pmtPid) {
30200      return 'pes';
30201    }
30202    return null;
30203  };
30204
30205  var parsePat = function parsePat(packet) {
30206    var pusi = parsePayloadUnitStartIndicator(packet);
30207    var offset = 4 + parseAdaptionField(packet);
30208
30209    if (pusi) {
30210      offset += packet[offset] + 1;
30211    }
30212
30213    return (packet[offset + 10] & 0x1f) << 8 | packet[offset + 11];
30214  };
30215
30216  var parsePmt = function parsePmt(packet) {
30217    var programMapTable = {};
30218    var pusi = parsePayloadUnitStartIndicator(packet);
30219    var payloadOffset = 4 + parseAdaptionField(packet);
30220
30221    if (pusi) {
30222      payloadOffset += packet[payloadOffset] + 1;
30223    }
30224
30225    // PMTs can be sent ahead of the time when they should actually
30226    // take effect. We don't believe this should ever be the case
30227    // for HLS but we'll ignore "forward" PMT declarations if we see
30228    // them. Future PMT declarations have the current_next_indicator
30229    // set to zero.
30230    if (!(packet[payloadOffset + 5] & 0x01)) {
30231      return;
30232    }
30233
30234    var sectionLength, tableEnd, programInfoLength;
30235    // the mapping table ends at the end of the current section
30236    sectionLength = (packet[payloadOffset + 1] & 0x0f) << 8 | packet[payloadOffset + 2];
30237    tableEnd = 3 + sectionLength - 4;
30238
30239    // to determine where the table is, we have to figure out how
30240    // long the program info descriptors are
30241    programInfoLength = (packet[payloadOffset + 10] & 0x0f) << 8 | packet[payloadOffset + 11];
30242
30243    // advance the offset to the first entry in the mapping table
30244    var offset = 12 + programInfoLength;
30245    while (offset < tableEnd) {
30246      var i = payloadOffset + offset;
30247      // add an entry that maps the elementary_pid to the stream_type
30248      programMapTable[(packet[i + 1] & 0x1F) << 8 | packet[i + 2]] = packet[i];
30249
30250      // move to the next table entry
30251      // skip past the elementary stream descriptors, if present
30252      offset += ((packet[i + 3] & 0x0F) << 8 | packet[i + 4]) + 5;
30253    }
30254    return programMapTable;
30255  };
30256
30257  var parsePesType = function parsePesType(packet, programMapTable) {
30258    var pid = parsePid(packet);
30259    var type = programMapTable[pid];
30260    switch (type) {
30261      case StreamTypes.H264_STREAM_TYPE:
30262        return 'video';
30263      case StreamTypes.ADTS_STREAM_TYPE:
30264        return 'audio';
30265      case StreamTypes.METADATA_STREAM_TYPE:
30266        return 'timed-metadata';
30267      default:
30268        return null;
30269    }
30270  };
30271
30272  var parsePesTime = function parsePesTime(packet) {
30273    var pusi = parsePayloadUnitStartIndicator(packet);
30274    if (!pusi) {
30275      return null;
30276    }
30277
30278    var offset = 4 + parseAdaptionField(packet);
30279
30280    if (offset >= packet.byteLength) {
30281      // From the H 222.0 MPEG-TS spec
30282      // "For transport stream packets carrying PES packets, stuffing is needed when there
30283      //  is insufficient PES packet data to completely fill the transport stream packet
30284      //  payload bytes. Stuffing is accomplished by defining an adaptation field longer than
30285      //  the sum of the lengths of the data elements in it, so that the payload bytes
30286      //  remaining after the adaptation field exactly accommodates the available PES packet
30287      //  data."
30288      //
30289      // If the offset is >= the length of the packet, then the packet contains no data
30290      // and instead is just adaption field stuffing bytes
30291      return null;
30292    }
30293
30294    var pes = null;
30295    var ptsDtsFlags;
30296
30297    // PES packets may be annotated with a PTS value, or a PTS value
30298    // and a DTS value. Determine what combination of values is
30299    // available to work with.
30300    ptsDtsFlags = packet[offset + 7];
30301
30302    // PTS and DTS are normally stored as a 33-bit number.  Javascript
30303    // performs all bitwise operations on 32-bit integers but javascript
30304    // supports a much greater range (52-bits) of integer using standard
30305    // mathematical operations.
30306    // We construct a 31-bit value using bitwise operators over the 31
30307    // most significant bits and then multiply by 4 (equal to a left-shift
30308    // of 2) before we add the final 2 least significant bits of the
30309    // timestamp (equal to an OR.)
30310    if (ptsDtsFlags & 0xC0) {
30311      pes = {};
30312      // the PTS and DTS are not written out directly. For information
30313      // on how they are encoded, see
30314      // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
30315      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;
30316      pes.pts *= 4; // Left shift by 2
30317      pes.pts += (packet[offset + 13] & 0x06) >>> 1; // OR by the two LSBs
30318      pes.dts = pes.pts;
30319      if (ptsDtsFlags & 0x40) {
30320        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;
30321        pes.dts *= 4; // Left shift by 2
30322        pes.dts += (packet[offset + 18] & 0x06) >>> 1; // OR by the two LSBs
30323      }
30324    }
30325    return pes;
30326  };
30327
30328  var parseNalUnitType = function parseNalUnitType(type) {
30329    switch (type) {
30330      case 0x05:
30331        return 'slice_layer_without_partitioning_rbsp_idr';
30332      case 0x06:
30333        return 'sei_rbsp';
30334      case 0x07:
30335        return 'seq_parameter_set_rbsp';
30336      case 0x08:
30337        return 'pic_parameter_set_rbsp';
30338      case 0x09:
30339        return 'access_unit_delimiter_rbsp';
30340      default:
30341        return null;
30342    }
30343  };
30344
30345  var videoPacketContainsKeyFrame = function videoPacketContainsKeyFrame(packet) {
30346    var offset = 4 + parseAdaptionField(packet);
30347    var frameBuffer = packet.subarray(offset);
30348    var frameI = 0;
30349    var frameSyncPoint = 0;
30350    var foundKeyFrame = false;
30351    var nalType;
30352
30353    // advance the sync point to a NAL start, if necessary
30354    for (; frameSyncPoint < frameBuffer.byteLength - 3; frameSyncPoint++) {
30355      if (frameBuffer[frameSyncPoint + 2] === 1) {
30356        // the sync point is properly aligned
30357        frameI = frameSyncPoint + 5;
30358        break;
30359      }
30360    }
30361
30362    while (frameI < frameBuffer.byteLength) {
30363      // look at the current byte to determine if we've hit the end of
30364      // a NAL unit boundary
30365      switch (frameBuffer[frameI]) {
30366        case 0:
30367          // skip past non-sync sequences
30368          if (frameBuffer[frameI - 1] !== 0) {
30369            frameI += 2;
30370            break;
30371          } else if (frameBuffer[frameI - 2] !== 0) {
30372            frameI++;
30373            break;
30374          }
30375
30376          if (frameSyncPoint + 3 !== frameI - 2) {
30377            nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
30378            if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
30379              foundKeyFrame = true;
30380            }
30381          }
30382
30383          // drop trailing zeroes
30384          do {
30385            frameI++;
30386          } while (frameBuffer[frameI] !== 1 && frameI < frameBuffer.length);
30387          frameSyncPoint = frameI - 2;
30388          frameI += 3;
30389          break;
30390        case 1:
30391          // skip past non-sync sequences
30392          if (frameBuffer[frameI - 1] !== 0 || frameBuffer[frameI - 2] !== 0) {
30393            frameI += 3;
30394            break;
30395          }
30396
30397          nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
30398          if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
30399            foundKeyFrame = true;
30400          }
30401          frameSyncPoint = frameI - 2;
30402          frameI += 3;
30403          break;
30404        default:
30405          // the current byte isn't a one or zero, so it cannot be part
30406          // of a sync sequence
30407          frameI += 3;
30408          break;
30409      }
30410    }
30411    frameBuffer = frameBuffer.subarray(frameSyncPoint);
30412    frameI -= frameSyncPoint;
30413    frameSyncPoint = 0;
30414    // parse the final nal
30415    if (frameBuffer && frameBuffer.byteLength > 3) {
30416      nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f);
30417      if (nalType === 'slice_layer_without_partitioning_rbsp_idr') {
30418        foundKeyFrame = true;
30419      }
30420    }
30421
30422    return foundKeyFrame;
30423  };
30424
30425  var probe$1 = {
30426    parseType: parseType$1,
30427    parsePat: parsePat,
30428    parsePmt: parsePmt,
30429    parsePayloadUnitStartIndicator: parsePayloadUnitStartIndicator,
30430    parsePesType: parsePesType,
30431    parsePesTime: parsePesTime,
30432    videoPacketContainsKeyFrame: videoPacketContainsKeyFrame
30433  };
30434  var probe_1$1 = probe$1.parseType;
30435  var probe_2$1 = probe$1.parsePat;
30436  var probe_3$1 = probe$1.parsePmt;
30437  var probe_4 = probe$1.parsePayloadUnitStartIndicator;
30438  var probe_5 = probe$1.parsePesType;
30439  var probe_6 = probe$1.parsePesTime;
30440  var probe_7 = probe$1.videoPacketContainsKeyFrame;
30441
30442  var probe$2 = /*#__PURE__*/Object.freeze({
30443    default: probe$1,
30444    __moduleExports: probe$1,
30445    parseType: probe_1$1,
30446    parsePat: probe_2$1,
30447    parsePmt: probe_3$1,
30448    parsePayloadUnitStartIndicator: probe_4,
30449    parsePesType: probe_5,
30450    parsePesTime: probe_6,
30451    videoPacketContainsKeyFrame: probe_7
30452  });
30453
30454  /**
30455   * mux.js
30456   *
30457   * Copyright (c) 2016 Brightcove
30458   * All rights reserved.
30459   *
30460   * Utilities to detect basic properties and metadata about Aac data.
30461   */
30462
30463  var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
30464
30465  var parseSyncSafeInteger = function parseSyncSafeInteger(data) {
30466    return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
30467  };
30468
30469  // return a percent-encoded representation of the specified byte range
30470  // @see http://en.wikipedia.org/wiki/Percent-encoding
30471  var percentEncode = function percentEncode(bytes, start, end) {
30472    var i,
30473        result = '';
30474    for (i = start; i < end; i++) {
30475      result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
30476    }
30477    return result;
30478  };
30479
30480  // return the string representation of the specified byte range,
30481  // interpreted as ISO-8859-1.
30482  var parseIso88591 = function parseIso88591(bytes, start, end) {
30483    return unescape(percentEncode(bytes, start, end)); // jshint ignore:line
30484  };
30485
30486  var parseId3TagSize = function parseId3TagSize(header, byteIndex) {
30487    var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9],
30488        flags = header[byteIndex + 5],
30489        footerPresent = (flags & 16) >> 4;
30490
30491    if (footerPresent) {
30492      return returnSize + 20;
30493    }
30494    return returnSize + 10;
30495  };
30496
30497  var parseAdtsSize = function parseAdtsSize(header, byteIndex) {
30498    var lowThree = (header[byteIndex + 5] & 0xE0) >> 5,
30499        middle = header[byteIndex + 4] << 3,
30500        highTwo = header[byteIndex + 3] & 0x3 << 11;
30501
30502    return highTwo | middle | lowThree;
30503  };
30504
30505  var parseType$2 = function parseType(header, byteIndex) {
30506    if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) {
30507      return 'timed-metadata';
30508    } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) {
30509      return 'audio';
30510    }
30511    return null;
30512  };
30513
30514  var parseSampleRate = function parseSampleRate(packet) {
30515    var i = 0;
30516
30517    while (i + 5 < packet.length) {
30518      if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) {
30519        // If a valid header was not found,  jump one forward and attempt to
30520        // find a valid ADTS header starting at the next byte
30521        i++;
30522        continue;
30523      }
30524      return ADTS_SAMPLING_FREQUENCIES[(packet[i + 2] & 0x3c) >>> 2];
30525    }
30526
30527    return null;
30528  };
30529
30530  var parseAacTimestamp = function parseAacTimestamp(packet) {
30531    var frameStart, frameSize, frame, frameHeader;
30532
30533    // find the start of the first frame and the end of the tag
30534    frameStart = 10;
30535    if (packet[5] & 0x40) {
30536      // advance the frame start past the extended header
30537      frameStart += 4; // header size field
30538      frameStart += parseSyncSafeInteger(packet.subarray(10, 14));
30539    }
30540
30541    // parse one or more ID3 frames
30542    // http://id3.org/id3v2.3.0#ID3v2_frame_overview
30543    do {
30544      // determine the number of bytes in this frame
30545      frameSize = parseSyncSafeInteger(packet.subarray(frameStart + 4, frameStart + 8));
30546      if (frameSize < 1) {
30547        return null;
30548      }
30549      frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]);
30550
30551      if (frameHeader === 'PRIV') {
30552        frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10);
30553
30554        for (var i = 0; i < frame.byteLength; i++) {
30555          if (frame[i] === 0) {
30556            var owner = parseIso88591(frame, 0, i);
30557            if (owner === 'com.apple.streaming.transportStreamTimestamp') {
30558              var d = frame.subarray(i + 1);
30559              var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
30560              size *= 4;
30561              size += d[7] & 0x03;
30562
30563              return size;
30564            }
30565            break;
30566          }
30567        }
30568      }
30569
30570      frameStart += 10; // advance past the frame header
30571      frameStart += frameSize; // advance past the frame body
30572    } while (frameStart < packet.byteLength);
30573    return null;
30574  };
30575
30576  var probe$3 = {
30577    parseId3TagSize: parseId3TagSize,
30578    parseAdtsSize: parseAdtsSize,
30579    parseType: parseType$2,
30580    parseSampleRate: parseSampleRate,
30581    parseAacTimestamp: parseAacTimestamp
30582  };
30583  var probe_1$2 = probe$3.parseId3TagSize;
30584  var probe_2$2 = probe$3.parseAdtsSize;
30585  var probe_3$2 = probe$3.parseType;
30586  var probe_4$1 = probe$3.parseSampleRate;
30587  var probe_5$1 = probe$3.parseAacTimestamp;
30588
30589  var probe$4 = /*#__PURE__*/Object.freeze({
30590    default: probe$3,
30591    __moduleExports: probe$3,
30592    parseId3TagSize: probe_1$2,
30593    parseAdtsSize: probe_2$2,
30594    parseType: probe_3$2,
30595    parseSampleRate: probe_4$1,
30596    parseAacTimestamp: probe_5$1
30597  });
30598
30599  var require$$0$2 = ( timestampRolloverStream$1 && timestampRolloverStream ) || timestampRolloverStream$1;
30600
30601  var require$$1$1 = ( probe$2 && probe$1 ) || probe$2;
30602
30603  var require$$2$1 = ( probe$4 && probe$3 ) || probe$4;
30604
30605  var handleRollover$1 = require$$0$2.handleRollover;
30606  var probe$5 = {};
30607  probe$5.ts = require$$1$1;
30608  probe$5.aac = require$$2$1;
30609
30610  var PES_TIMESCALE = 90000,
30611      MP2T_PACKET_LENGTH = 188,
30612      // bytes
30613  SYNC_BYTE = 0x47;
30614
30615  var isLikelyAacData = function isLikelyAacData(data) {
30616    if (data[0] === 'I'.charCodeAt(0) && data[1] === 'D'.charCodeAt(0) && data[2] === '3'.charCodeAt(0)) {
30617      return true;
30618    }
30619    return false;
vendor: 9,174 bytes, lines 30620-30914
30620  };
30621
30622  /**
30623   * walks through segment data looking for pat and pmt packets to parse out
30624   * program map table information
30625   */
30626  var parsePsi_ = function parsePsi_(bytes, pmt) {
30627    var startIndex = 0,
30628        endIndex = MP2T_PACKET_LENGTH,
30629        packet,
30630        type;
30631
30632    while (endIndex < bytes.byteLength) {
30633      // Look for a pair of start and end sync bytes in the data..
30634      if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
30635        // We found a packet
30636        packet = bytes.subarray(startIndex, endIndex);
30637        type = probe$5.ts.parseType(packet, pmt.pid);
30638
30639        switch (type) {
30640          case 'pat':
30641            if (!pmt.pid) {
30642              pmt.pid = probe$5.ts.parsePat(packet);
30643            }
30644            break;
30645          case 'pmt':
30646            if (!pmt.table) {
30647              pmt.table = probe$5.ts.parsePmt(packet);
30648            }
30649            break;
30650          default:
30651            break;
30652        }
30653
30654        // Found the pat and pmt, we can stop walking the segment
30655        if (pmt.pid && pmt.table) {
30656          return;
30657        }
30658
30659        startIndex += MP2T_PACKET_LENGTH;
30660        endIndex += MP2T_PACKET_LENGTH;
30661        continue;
30662      }
30663
30664      // If we get here, we have somehow become de-synchronized and we need to step
30665      // forward one byte at a time until we find a pair of sync bytes that denote
30666      // a packet
30667      startIndex++;
30668      endIndex++;
30669    }
30670  };
30671
30672  /**
30673   * walks through the segment data from the start and end to get timing information
30674   * for the first and last audio pes packets
30675   */
30676  var parseAudioPes_ = function parseAudioPes_(bytes, pmt, result) {
30677    var startIndex = 0,
30678        endIndex = MP2T_PACKET_LENGTH,
30679        packet,
30680        type,
30681        pesType,
30682        pusi,
30683        parsed;
30684
30685    var endLoop = false;
30686
30687    // Start walking from start of segment to get first audio packet
30688    while (endIndex < bytes.byteLength) {
30689      // Look for a pair of start and end sync bytes in the data..
30690      if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
30691        // We found a packet
30692        packet = bytes.subarray(startIndex, endIndex);
30693        type = probe$5.ts.parseType(packet, pmt.pid);
30694
30695        switch (type) {
30696          case 'pes':
30697            pesType = probe$5.ts.parsePesType(packet, pmt.table);
30698            pusi = probe$5.ts.parsePayloadUnitStartIndicator(packet);
30699            if (pesType === 'audio' && pusi) {
30700              parsed = probe$5.ts.parsePesTime(packet);
30701              if (parsed) {
30702                parsed.type = 'audio';
30703                result.audio.push(parsed);
30704                endLoop = true;
30705              }
30706            }
30707            break;
30708          default:
30709            break;
30710        }
30711
30712        if (endLoop) {
30713          break;
30714        }
30715
30716        startIndex += MP2T_PACKET_LENGTH;
30717        endIndex += MP2T_PACKET_LENGTH;
30718        continue;
30719      }
30720
30721      // If we get here, we have somehow become de-synchronized and we need to step
30722      // forward one byte at a time until we find a pair of sync bytes that denote
30723      // a packet
30724      startIndex++;
30725      endIndex++;
30726    }
30727
30728    // Start walking from end of segment to get last audio packet
30729    endIndex = bytes.byteLength;
30730    startIndex = endIndex - MP2T_PACKET_LENGTH;
30731    endLoop = false;
30732    while (startIndex >= 0) {
30733      // Look for a pair of start and end sync bytes in the data..
30734      if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
30735        // We found a packet
30736        packet = bytes.subarray(startIndex, endIndex);
30737        type = probe$5.ts.parseType(packet, pmt.pid);
30738
30739        switch (type) {
30740          case 'pes':
30741            pesType = probe$5.ts.parsePesType(packet, pmt.table);
30742            pusi = probe$5.ts.parsePayloadUnitStartIndicator(packet);
30743            if (pesType === 'audio' && pusi) {
30744              parsed = probe$5.ts.parsePesTime(packet);
30745              if (parsed) {
30746                parsed.type = 'audio';
30747                result.audio.push(parsed);
30748                endLoop = true;
30749              }
30750            }
30751            break;
30752          default:
30753            break;
30754        }
30755
30756        if (endLoop) {
30757          break;
30758        }
30759
30760        startIndex -= MP2T_PACKET_LENGTH;
30761        endIndex -= MP2T_PACKET_LENGTH;
30762        continue;
30763      }
30764
30765      // If we get here, we have somehow become de-synchronized and we need to step
30766      // forward one byte at a time until we find a pair of sync bytes that denote
30767      // a packet
30768      startIndex--;
30769      endIndex--;
30770    }
30771  };
30772
30773  /**
30774   * walks through the segment data from the start and end to get timing information
30775   * for the first and last video pes packets as well as timing information for the first
30776   * key frame.
30777   */
30778  var parseVideoPes_ = function parseVideoPes_(bytes, pmt, result) {
30779    var startIndex = 0,
30780        endIndex = MP2T_PACKET_LENGTH,
30781        packet,
30782        type,
30783        pesType,
30784        pusi,
30785        parsed,
30786        frame,
30787        i,
30788        pes;
30789
30790    var endLoop = false;
30791
30792    var currentFrame = {
30793      data: [],
30794      size: 0
30795    };
30796
30797    // Start walking from start of segment to get first video packet
30798    while (endIndex < bytes.byteLength) {
30799      // Look for a pair of start and end sync bytes in the data..
30800      if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
30801        // We found a packet
30802        packet = bytes.subarray(startIndex, endIndex);
30803        type = probe$5.ts.parseType(packet, pmt.pid);
30804
30805        switch (type) {
30806          case 'pes':
30807            pesType = probe$5.ts.parsePesType(packet, pmt.table);
30808            pusi = probe$5.ts.parsePayloadUnitStartIndicator(packet);
30809            if (pesType === 'video') {
30810              if (pusi && !endLoop) {
30811                parsed = probe$5.ts.parsePesTime(packet);
30812                if (parsed) {
30813                  parsed.type = 'video';
30814                  result.video.push(parsed);
30815                  endLoop = true;
30816                }
30817              }
30818              if (!result.firstKeyFrame) {
30819                if (pusi) {
30820                  if (currentFrame.size !== 0) {
30821                    frame = new Uint8Array(currentFrame.size);
30822                    i = 0;
30823                    while (currentFrame.data.length) {
30824                      pes = currentFrame.data.shift();
30825                      frame.set(pes, i);
30826                      i += pes.byteLength;
30827                    }
30828                    if (probe$5.ts.videoPacketContainsKeyFrame(frame)) {
30829                      result.firstKeyFrame = probe$5.ts.parsePesTime(frame);
30830                      result.firstKeyFrame.type = 'video';
30831                    }
30832                    currentFrame.size = 0;
30833                  }
30834                }
30835                currentFrame.data.push(packet);
30836                currentFrame.size += packet.byteLength;
30837              }
30838            }
30839            break;
30840          default:
30841            break;
30842        }
30843
30844        if (endLoop && result.firstKeyFrame) {
30845          break;
30846        }
30847
30848        startIndex += MP2T_PACKET_LENGTH;
30849        endIndex += MP2T_PACKET_LENGTH;
30850        continue;
30851      }
30852
30853      // If we get here, we have somehow become de-synchronized and we need to step
30854      // forward one byte at a time until we find a pair of sync bytes that denote
30855      // a packet
30856      startIndex++;
30857      endIndex++;
30858    }
30859
30860    // Start walking from end of segment to get last video packet
30861    endIndex = bytes.byteLength;
30862    startIndex = endIndex - MP2T_PACKET_LENGTH;
30863    endLoop = false;
30864    while (startIndex >= 0) {
30865      // Look for a pair of start and end sync bytes in the data..
30866      if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) {
30867        // We found a packet
30868        packet = bytes.subarray(startIndex, endIndex);
30869        type = probe$5.ts.parseType(packet, pmt.pid);
30870
30871        switch (type) {
30872          case 'pes':
30873            pesType = probe$5.ts.parsePesType(packet, pmt.table);
30874            pusi = probe$5.ts.parsePayloadUnitStartIndicator(packet);
30875            if (pesType === 'video' && pusi) {
30876              parsed = probe$5.ts.parsePesTime(packet);
30877              if (parsed) {
30878                parsed.type = 'video';
30879                result.video.push(parsed);
30880                endLoop = true;
30881              }
30882            }
30883            break;
30884          default:
30885            break;
30886        }
30887
30888        if (endLoop) {
30889          break;
30890        }
30891
30892        startIndex -= MP2T_PACKET_LENGTH;
30893        endIndex -= MP2T_PACKET_LENGTH;
30894        continue;
30895      }
30896
30897      // If we get here, we have somehow become de-synchronized and we need to step
30898      // forward one byte at a time until we find a pair of sync bytes that denote
30899      // a packet
30900      startIndex--;
30901      endIndex--;
30902    }
30903  };
30904
30905  /**
30906   * Adjusts the timestamp information for the segment to account for
30907   * rollover and convert to seconds based on pes packet timescale (90khz clock)
30908   */
30909  var adjustTimestamp_ = function adjustTimestamp_(segmentInfo, baseTimestamp) {
30910    if (segmentInfo.audio && segmentInfo.audio.length) {
30911      var audioBaseTimestamp = baseTimestamp;
30912      if (typeof audioBaseTimestamp === 'undefined') {
30913        audioBaseTimestamp = segmentInfo.audio[0].dts;
30914      }
vendor: 5,675 bytes, lines 30915-31113
30915      segmentInfo.audio.forEach(function (info) {
30916        info.dts = handleRollover$1(info.dts, audioBaseTimestamp);
30917        info.pts = handleRollover$1(info.pts, audioBaseTimestamp);
30918        // time in seconds
30919        info.dtsTime = info.dts / PES_TIMESCALE;
30920        info.ptsTime = info.pts / PES_TIMESCALE;
30921      });
30922    }
30923
30924    if (segmentInfo.video && segmentInfo.video.length) {
30925      var videoBaseTimestamp = baseTimestamp;
30926      if (typeof videoBaseTimestamp === 'undefined') {
30927        videoBaseTimestamp = segmentInfo.video[0].dts;
30928      }
30929      segmentInfo.video.forEach(function (info) {
30930        info.dts = handleRollover$1(info.dts, videoBaseTimestamp);
30931        info.pts = handleRollover$1(info.pts, videoBaseTimestamp);
30932        // time in seconds
30933        info.dtsTime = info.dts / PES_TIMESCALE;
30934        info.ptsTime = info.pts / PES_TIMESCALE;
30935      });
30936      if (segmentInfo.firstKeyFrame) {
30937        var frame = segmentInfo.firstKeyFrame;
30938        frame.dts = handleRollover$1(frame.dts, videoBaseTimestamp);
30939        frame.pts = handleRollover$1(frame.pts, videoBaseTimestamp);
30940        // time in seconds
30941        frame.dtsTime = frame.dts / PES_TIMESCALE;
30942        frame.ptsTime = frame.dts / PES_TIMESCALE;
30943      }
30944    }
30945  };
30946
30947  /**
30948   * inspects the aac data stream for start and end time information
30949   */
30950  var inspectAac_ = function inspectAac_(bytes) {
30951    var endLoop = false,
30952        audioCount = 0,
30953        sampleRate = null,
30954        timestamp = null,
30955        frameSize = 0,
30956        byteIndex = 0,
30957        packet;
30958
30959    while (bytes.length - byteIndex >= 3) {
30960      var type = probe$5.aac.parseType(bytes, byteIndex);
30961      switch (type) {
30962        case 'timed-metadata':
30963          // Exit early because we don't have enough to parse
30964          // the ID3 tag header
30965          if (bytes.length - byteIndex < 10) {
30966            endLoop = true;
30967            break;
30968          }
30969
30970          frameSize = probe$5.aac.parseId3TagSize(bytes, byteIndex);
30971
30972          // Exit early if we don't have enough in the buffer
30973          // to emit a full packet
30974          if (frameSize > bytes.length) {
30975            endLoop = true;
30976            break;
30977          }
30978          if (timestamp === null) {
30979            packet = bytes.subarray(byteIndex, byteIndex + frameSize);
30980            timestamp = probe$5.aac.parseAacTimestamp(packet);
30981          }
30982          byteIndex += frameSize;
30983          break;
30984        case 'audio':
30985          // Exit early because we don't have enough to parse
30986          // the ADTS frame header
30987          if (bytes.length - byteIndex < 7) {
30988            endLoop = true;
30989            break;
30990          }
30991
30992          frameSize = probe$5.aac.parseAdtsSize(bytes, byteIndex);
30993
30994          // Exit early if we don't have enough in the buffer
30995          // to emit a full packet
30996          if (frameSize > bytes.length) {
30997            endLoop = true;
30998            break;
30999          }
31000          if (sampleRate === null) {
31001            packet = bytes.subarray(byteIndex, byteIndex + frameSize);
31002            sampleRate = probe$5.aac.parseSampleRate(packet);
31003          }
31004          audioCount++;
31005          byteIndex += frameSize;
31006          break;
31007        default:
31008          byteIndex++;
31009          break;
31010      }
31011      if (endLoop) {
31012        return null;
31013      }
31014    }
31015    if (sampleRate === null || timestamp === null) {
31016      return null;
31017    }
31018
31019    var audioTimescale = PES_TIMESCALE / sampleRate;
31020
31021    var result = {
31022      audio: [{
31023        type: 'audio',
31024        dts: timestamp,
31025        pts: timestamp
31026      }, {
31027        type: 'audio',
31028        dts: timestamp + audioCount * 1024 * audioTimescale,
31029        pts: timestamp + audioCount * 1024 * audioTimescale
31030      }]
31031    };
31032
31033    return result;
31034  };
31035
31036  /**
31037   * inspects the transport stream segment data for start and end time information
31038   * of the audio and video tracks (when present) as well as the first key frame's
31039   * start time.
31040   */
31041  var inspectTs_ = function inspectTs_(bytes) {
31042    var pmt = {
31043      pid: null,
31044      table: null
31045    };
31046
31047    var result = {};
31048
31049    parsePsi_(bytes, pmt);
31050
31051    for (var pid in pmt.table) {
31052      if (pmt.table.hasOwnProperty(pid)) {
31053        var type = pmt.table[pid];
31054        switch (type) {
31055          case StreamTypes.H264_STREAM_TYPE:
31056            result.video = [];
31057            parseVideoPes_(bytes, pmt, result);
31058            if (result.video.length === 0) {
31059              delete result.video;
31060            }
31061            break;
31062          case StreamTypes.ADTS_STREAM_TYPE:
31063            result.audio = [];
31064            parseAudioPes_(bytes, pmt, result);
31065            if (result.audio.length === 0) {
31066              delete result.audio;
31067            }
31068            break;
31069          default:
31070            break;
31071        }
31072      }
31073    }
31074    return result;
31075  };
31076
31077  /**
31078   * Inspects segment byte data and returns an object with start and end timing information
31079   *
31080   * @param {Uint8Array} bytes The segment byte data
31081   * @param {Number} baseTimestamp Relative reference timestamp used when adjusting frame
31082   *  timestamps for rollover. This value must be in 90khz clock.
31083   * @return {Object} Object containing start and end frame timing info of segment.
31084   */
31085  var inspect = function inspect(bytes, baseTimestamp) {
31086    var isAacData = isLikelyAacData(bytes);
31087
31088    var result;
31089
31090    if (isAacData) {
31091      result = inspectAac_(bytes);
31092    } else {
31093      result = inspectTs_(bytes);
31094    }
31095
31096    if (!result || !result.audio && !result.video) {
31097      return null;
31098    }
31099
31100    adjustTimestamp_(result, baseTimestamp);
31101
31102    return result;
31103  };
31104
31105  var tsInspector = {
31106    inspect: inspect
31107  };
31108
31109  /*
31110   * pkcs7.pad
31111   * https://github.com/brightcove/pkcs7
31112   *
31113   * Copyright (c) 2014 Brightcove
vendor: 121,872 bytes, lines 31114-34780
31114   * Licensed under the apache2 license.
31115   */
31116
31117  /**
31118   * Returns the subarray of a Uint8Array without PKCS#7 padding.
31119   * @param padded {Uint8Array} unencrypted bytes that have been padded
31120   * @return {Uint8Array} the unpadded bytes
31121   * @see http://tools.ietf.org/html/rfc5652
31122   */
31123  function unpad(padded) {
31124    return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]);
31125  }
31126
31127  var classCallCheck$2 = function classCallCheck$$1(instance, Constructor) {
31128    if (!(instance instanceof Constructor)) {
31129      throw new TypeError("Cannot call a class as a function");
31130    }
31131  };
31132
31133  var createClass$1 = function () {
31134    function defineProperties(target, props) {
31135      for (var i = 0; i < props.length; i++) {
31136        var descriptor = props[i];
31137        descriptor.enumerable = descriptor.enumerable || false;
31138        descriptor.configurable = true;
31139        if ("value" in descriptor) descriptor.writable = true;
31140        Object.defineProperty(target, descriptor.key, descriptor);
31141      }
31142    }
31143
31144    return function (Constructor, protoProps, staticProps) {
31145      if (protoProps) defineProperties(Constructor.prototype, protoProps);
31146      if (staticProps) defineProperties(Constructor, staticProps);
31147      return Constructor;
31148    };
31149  }();
31150
31151  var inherits$2 = function inherits$$1(subClass, superClass) {
31152    if (typeof superClass !== "function" && superClass !== null) {
31153      throw new TypeError("Super expression must either be null or a function, not " + (typeof superClass === "undefined" ? "undefined" : _typeof(superClass)));
31154    }
31155
31156    subClass.prototype = Object.create(superClass && superClass.prototype, {
31157      constructor: {
31158        value: subClass,
31159        enumerable: false,
31160        writable: true,
31161        configurable: true
31162      }
31163    });
31164    if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
31165  };
31166
31167  var possibleConstructorReturn$2 = function possibleConstructorReturn$$1(self, call) {
31168    if (!self) {
31169      throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
31170    }
31171
31172    return call && ((typeof call === "undefined" ? "undefined" : _typeof(call)) === "object" || typeof call === "function") ? call : self;
31173  };
31174
31175  /**
31176   * @file aes.js
31177   *
31178   * This file contains an adaptation of the AES decryption algorithm
31179   * from the Standford Javascript Cryptography Library. That work is
31180   * covered by the following copyright and permissions notice:
31181   *
31182   * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
31183   * All rights reserved.
31184   *
31185   * Redistribution and use in source and binary forms, with or without
31186   * modification, are permitted provided that the following conditions are
31187   * met:
31188   *
31189   * 1. Redistributions of source code must retain the above copyright
31190   *    notice, this list of conditions and the following disclaimer.
31191   *
31192   * 2. Redistributions in binary form must reproduce the above
31193   *    copyright notice, this list of conditions and the following
31194   *    disclaimer in the documentation and/or other materials provided
31195   *    with the distribution.
31196   *
31197   * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
31198   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
31199   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
31200   * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
31201   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31202   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
31203   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
31204   * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
31205   * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
31206   * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
31207   * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31208   *
31209   * The views and conclusions contained in the software and documentation
31210   * are those of the authors and should not be interpreted as representing
31211   * official policies, either expressed or implied, of the authors.
31212   */
31213
31214  /**
31215   * Expand the S-box tables.
31216   *
31217   * @private
31218   */
31219  var precompute = function precompute() {
31220    var tables = [[[], [], [], [], []], [[], [], [], [], []]];
31221    var encTable = tables[0];
31222    var decTable = tables[1];
31223    var sbox = encTable[4];
31224    var sboxInv = decTable[4];
31225    var i = void 0;
31226    var x = void 0;
31227    var xInv = void 0;
31228    var d = [];
31229    var th = [];
31230    var x2 = void 0;
31231    var x4 = void 0;
31232    var x8 = void 0;
31233    var s = void 0;
31234    var tEnc = void 0;
31235    var tDec = void 0;
31236
31237    // Compute double and third tables
31238    for (i = 0; i < 256; i++) {
31239      th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i;
31240    }
31241
31242    for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
31243      // Compute sbox
31244      s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4;
31245      s = s >> 8 ^ s & 255 ^ 99;
31246      sbox[x] = s;
31247      sboxInv[s] = x;
31248
31249      // Compute MixColumns
31250      x8 = d[x4 = d[x2 = d[x]]];
31251      tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
31252      tEnc = d[s] * 0x101 ^ s * 0x1010100;
31253
31254      for (i = 0; i < 4; i++) {
31255        encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8;
31256        decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8;
31257      }
31258    }
31259
31260    // Compactify. Considerable speedup on Firefox.
31261    for (i = 0; i < 5; i++) {
31262      encTable[i] = encTable[i].slice(0);
31263      decTable[i] = decTable[i].slice(0);
31264    }
31265    return tables;
31266  };
31267  var aesTables = null;
31268
31269  /**
31270   * Schedule out an AES key for both encryption and decryption. This
31271   * is a low-level class. Use a cipher mode to do bulk encryption.
31272   *
31273   * @class AES
31274   * @param key {Array} The key as an array of 4, 6 or 8 words.
31275   */
31276
31277  var AES = function () {
31278    function AES(key) {
31279      classCallCheck$2(this, AES);
31280
31281      /**
31282       * The expanded S-box and inverse S-box tables. These will be computed
31283       * on the client so that we don't have to send them down the wire.
31284       *
31285       * There are two tables, _tables[0] is for encryption and
31286       * _tables[1] is for decryption.
31287       *
31288       * The first 4 sub-tables are the expanded S-box with MixColumns. The
31289       * last (_tables[01][4]) is the S-box itself.
31290       *
31291       * @private
31292       */
31293      // if we have yet to precompute the S-box tables
31294      // do so now
31295      if (!aesTables) {
31296        aesTables = precompute();
31297      }
31298      // then make a copy of that object for use
31299      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()]];
31300      var i = void 0;
31301      var j = void 0;
31302      var tmp = void 0;
31303      var encKey = void 0;
31304      var decKey = void 0;
31305      var sbox = this._tables[0][4];
31306      var decTable = this._tables[1];
31307      var keyLen = key.length;
31308      var rcon = 1;
31309
31310      if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
31311        throw new Error('Invalid aes key size');
31312      }
31313
31314      encKey = key.slice(0);
31315      decKey = [];
31316      this._key = [encKey, decKey];
31317
31318      // schedule encryption keys
31319      for (i = keyLen; i < 4 * keyLen + 28; i++) {
31320        tmp = encKey[i - 1];
31321
31322        // apply sbox
31323        if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) {
31324          tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255];
31325
31326          // shift rows and add rcon
31327          if (i % keyLen === 0) {
31328            tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24;
31329            rcon = rcon << 1 ^ (rcon >> 7) * 283;
31330          }
31331        }
31332
31333        encKey[i] = encKey[i - keyLen] ^ tmp;
31334      }
31335
31336      // schedule decryption keys
31337      for (j = 0; i; j++, i--) {
31338        tmp = encKey[j & 3 ? i : i - 4];
31339        if (i <= 4 || j < 4) {
31340          decKey[j] = tmp;
31341        } else {
31342          decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]];
31343        }
31344      }
31345    }
31346
31347    /**
31348     * Decrypt 16 bytes, specified as four 32-bit words.
31349     *
31350     * @param {Number} encrypted0 the first word to decrypt
31351     * @param {Number} encrypted1 the second word to decrypt
31352     * @param {Number} encrypted2 the third word to decrypt
31353     * @param {Number} encrypted3 the fourth word to decrypt
31354     * @param {Int32Array} out the array to write the decrypted words
31355     * into
31356     * @param {Number} offset the offset into the output array to start
31357     * writing results
31358     * @return {Array} The plaintext.
31359     */
31360
31361    AES.prototype.decrypt = function decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
31362      var key = this._key[1];
31363      // state variables a,b,c,d are loaded with pre-whitened data
31364      var a = encrypted0 ^ key[0];
31365      var b = encrypted3 ^ key[1];
31366      var c = encrypted2 ^ key[2];
31367      var d = encrypted1 ^ key[3];
31368      var a2 = void 0;
31369      var b2 = void 0;
31370      var c2 = void 0;
31371
31372      // key.length === 2 ?
31373      var nInnerRounds = key.length / 4 - 2;
31374      var i = void 0;
31375      var kIndex = 4;
31376      var table = this._tables[1];
31377
31378      // load up the tables
31379      var table0 = table[0];
31380      var table1 = table[1];
31381      var table2 = table[2];
31382      var table3 = table[3];
31383      var sbox = table[4];
31384
31385      // Inner rounds. Cribbed from OpenSSL.
31386      for (i = 0; i < nInnerRounds; i++) {
31387        a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex];
31388        b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1];
31389        c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2];
31390        d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3];
31391        kIndex += 4;
31392        a = a2;b = b2;c = c2;
31393      }
31394
31395      // Last round.
31396      for (i = 0; i < 4; i++) {
31397        out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++];
31398        a2 = a;a = b;b = c;c = d;d = a2;
31399      }
31400    };
31401
31402    return AES;
31403  }();
31404
31405  /**
31406   * @file stream.js
31407   */
31408  /**
31409   * A lightweight readable stream implemention that handles event dispatching.
31410   *
31411   * @class Stream
31412   */
31413  var Stream$3 = function () {
31414    function Stream() {
31415      classCallCheck$2(this, Stream);
31416
31417      this.listeners = {};
31418    }
31419
31420    /**
31421     * Add a listener for a specified event type.
31422     *
31423     * @param {String} type the event name
31424     * @param {Function} listener the callback to be invoked when an event of
31425     * the specified type occurs
31426     */
31427
31428    Stream.prototype.on = function on(type, listener) {
31429      if (!this.listeners[type]) {
31430        this.listeners[type] = [];
31431      }
31432      this.listeners[type].push(listener);
31433    };
31434
31435    /**
31436     * Remove a listener for a specified event type.
31437     *
31438     * @param {String} type the event name
31439     * @param {Function} listener  a function previously registered for this
31440     * type of event through `on`
31441     * @return {Boolean} if we could turn it off or not
31442     */
31443
31444    Stream.prototype.off = function off(type, listener) {
31445      if (!this.listeners[type]) {
31446        return false;
31447      }
31448
31449      var index = this.listeners[type].indexOf(listener);
31450
31451      this.listeners[type].splice(index, 1);
31452      return index > -1;
31453    };
31454
31455    /**
31456     * Trigger an event of the specified type on this stream. Any additional
31457     * arguments to this function are passed as parameters to event listeners.
31458     *
31459     * @param {String} type the event name
31460     */
31461
31462    Stream.prototype.trigger = function trigger(type) {
31463      var callbacks = this.listeners[type];
31464
31465      if (!callbacks) {
31466        return;
31467      }
31468
31469      // Slicing the arguments on every invocation of this method
31470      // can add a significant amount of overhead. Avoid the
31471      // intermediate object creation for the common case of a
31472      // single callback argument
31473      if (arguments.length === 2) {
31474        var length = callbacks.length;
31475
31476        for (var i = 0; i < length; ++i) {
31477          callbacks[i].call(this, arguments[1]);
31478        }
31479      } else {
31480        var args = Array.prototype.slice.call(arguments, 1);
31481        var _length = callbacks.length;
31482
31483        for (var _i = 0; _i < _length; ++_i) {
31484          callbacks[_i].apply(this, args);
31485        }
31486      }
31487    };
31488
31489    /**
31490     * Destroys the stream and cleans up.
31491     */
31492
31493    Stream.prototype.dispose = function dispose() {
31494      this.listeners = {};
31495    };
31496    /**
31497     * Forwards all `data` events on this stream to the destination stream. The
31498     * destination stream should provide a method `push` to receive the data
31499     * events as they arrive.
31500     *
31501     * @param {Stream} destination the stream that will receive all `data` events
31502     * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
31503     */
31504
31505    Stream.prototype.pipe = function pipe(destination) {
31506      this.on('data', function (data) {
31507        destination.push(data);
31508      });
31509    };
31510
31511    return Stream;
31512  }();
31513
31514  /**
31515   * @file async-stream.js
31516   */
31517  /**
31518   * A wrapper around the Stream class to use setTiemout
31519   * and run stream "jobs" Asynchronously
31520   *
31521   * @class AsyncStream
31522   * @extends Stream
31523   */
31524
31525  var AsyncStream = function (_Stream) {
31526    inherits$2(AsyncStream, _Stream);
31527
31528    function AsyncStream() {
31529      classCallCheck$2(this, AsyncStream);
31530
31531      var _this = possibleConstructorReturn$2(this, _Stream.call(this, Stream$3));
31532
31533      _this.jobs = [];
31534      _this.delay = 1;
31535      _this.timeout_ = null;
31536      return _this;
31537    }
31538
31539    /**
31540     * process an async job
31541     *
31542     * @private
31543     */
31544
31545    AsyncStream.prototype.processJob_ = function processJob_() {
31546      this.jobs.shift()();
31547      if (this.jobs.length) {
31548        this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
31549      } else {
31550        this.timeout_ = null;
31551      }
31552    };
31553
31554    /**
31555     * push a job into the stream
31556     *
31557     * @param {Function} job the job to push into the stream
31558     */
31559
31560    AsyncStream.prototype.push = function push(job) {
31561      this.jobs.push(job);
31562      if (!this.timeout_) {
31563        this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
31564      }
31565    };
31566
31567    return AsyncStream;
31568  }(Stream$3);
31569
31570  /**
31571   * @file decrypter.js
31572   *
31573   * An asynchronous implementation of AES-128 CBC decryption with
31574   * PKCS#7 padding.
31575   */
31576
31577  /**
31578   * Convert network-order (big-endian) bytes into their little-endian
31579   * representation.
31580   */
31581  var ntoh = function ntoh(word) {
31582    return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24;
31583  };
31584
31585  /**
31586   * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
31587   *
31588   * @param {Uint8Array} encrypted the encrypted bytes
31589   * @param {Uint32Array} key the bytes of the decryption key
31590   * @param {Uint32Array} initVector the initialization vector (IV) to
31591   * use for the first round of CBC.
31592   * @return {Uint8Array} the decrypted bytes
31593   *
31594   * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
31595   * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
31596   * @see https://tools.ietf.org/html/rfc2315
31597   */
31598  var decrypt = function decrypt(encrypted, key, initVector) {
31599    // word-level access to the encrypted bytes
31600    var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2);
31601
31602    var decipher = new AES(Array.prototype.slice.call(key));
31603
31604    // byte and word-level access for the decrypted output
31605    var decrypted = new Uint8Array(encrypted.byteLength);
31606    var decrypted32 = new Int32Array(decrypted.buffer);
31607
31608    // temporary variables for working with the IV, encrypted, and
31609    // decrypted data
31610    var init0 = void 0;
31611    var init1 = void 0;
31612    var init2 = void 0;
31613    var init3 = void 0;
31614    var encrypted0 = void 0;
31615    var encrypted1 = void 0;
31616    var encrypted2 = void 0;
31617    var encrypted3 = void 0;
31618
31619    // iteration variable
31620    var wordIx = void 0;
31621
31622    // pull out the words of the IV to ensure we don't modify the
31623    // passed-in reference and easier access
31624    init0 = initVector[0];
31625    init1 = initVector[1];
31626    init2 = initVector[2];
31627    init3 = initVector[3];
31628
31629    // decrypt four word sequences, applying cipher-block chaining (CBC)
31630    // to each decrypted block
31631    for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
31632      // convert big-endian (network order) words into little-endian
31633      // (javascript order)
31634      encrypted0 = ntoh(encrypted32[wordIx]);
31635      encrypted1 = ntoh(encrypted32[wordIx + 1]);
31636      encrypted2 = ntoh(encrypted32[wordIx + 2]);
31637      encrypted3 = ntoh(encrypted32[wordIx + 3]);
31638
31639      // decrypt the block
31640      decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx);
31641
31642      // XOR with the IV, and restore network byte-order to obtain the
31643      // plaintext
31644      decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
31645      decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
31646      decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
31647      decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3);
31648
31649      // setup the IV for the next round
31650      init0 = encrypted0;
31651      init1 = encrypted1;
31652      init2 = encrypted2;
31653      init3 = encrypted3;
31654    }
31655
31656    return decrypted;
31657  };
31658
31659  /**
31660   * The `Decrypter` class that manages decryption of AES
31661   * data through `AsyncStream` objects and the `decrypt`
31662   * function
31663   *
31664   * @param {Uint8Array} encrypted the encrypted bytes
31665   * @param {Uint32Array} key the bytes of the decryption key
31666   * @param {Uint32Array} initVector the initialization vector (IV) to
31667   * @param {Function} done the function to run when done
31668   * @class Decrypter
31669   */
31670
31671  var Decrypter = function () {
31672    function Decrypter(encrypted, key, initVector, done) {
31673      classCallCheck$2(this, Decrypter);
31674
31675      var step = Decrypter.STEP;
31676      var encrypted32 = new Int32Array(encrypted.buffer);
31677      var decrypted = new Uint8Array(encrypted.byteLength);
31678      var i = 0;
31679
31680      this.asyncStream_ = new AsyncStream();
31681
31682      // split up the encryption job and do the individual chunks asynchronously
31683      this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
31684      for (i = step; i < encrypted32.length; i += step) {
31685        initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]);
31686        this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
31687      }
31688      // invoke the done() callback when everything is finished
31689      this.asyncStream_.push(function () {
31690        // remove pkcs#7 padding from the decrypted bytes
31691        done(null, unpad(decrypted));
31692      });
31693    }
31694
31695    /**
31696     * a getter for step the maximum number of bytes to process at one time
31697     *
31698     * @return {Number} the value of step 32000
31699     */
31700
31701    /**
31702     * @private
31703     */
31704    Decrypter.prototype.decryptChunk_ = function decryptChunk_(encrypted, key, initVector, decrypted) {
31705      return function () {
31706        var bytes = decrypt(encrypted, key, initVector);
31707
31708        decrypted.set(bytes, encrypted.byteOffset);
31709      };
31710    };
31711
31712    createClass$1(Decrypter, null, [{
31713      key: 'STEP',
31714      get: function get$$1() {
31715        // 4 * 8000;
31716        return 32000;
31717      }
31718    }]);
31719    return Decrypter;
31720  }();
31721
31722  /**
31723   * @videojs/http-streaming
31724   * @version 1.1.0
31725   * @copyright 2018 Brightcove, Inc
31726   * @license Apache-2.0
31727   */
31728
31729  /**
31730   * @file resolve-url.js
31731   */
31732
31733  var resolveUrl$1 = function resolveUrl(baseURL, relativeURL) {
31734    // return early if we don't need to resolve
31735    if (/^[a-z]+:/i.test(relativeURL)) {
31736      return relativeURL;
31737    }
31738
31739    // if the base URL is relative then combine with the current location
31740    if (!/\/\//i.test(baseURL)) {
31741      baseURL = urlToolkit.buildAbsoluteURL(window_1.location.href, baseURL);
31742    }
31743
31744    return urlToolkit.buildAbsoluteURL(baseURL, relativeURL);
31745  };
31746
31747  var classCallCheck$3 = function classCallCheck$$1(instance, Constructor) {
31748    if (!(instance instanceof Constructor)) {
31749      throw new TypeError("Cannot call a class as a function");
31750    }
31751  };
31752
31753  var createClass$2 = function () {
31754    function defineProperties(target, props) {
31755      for (var i = 0; i < props.length; i++) {
31756        var descriptor = props[i];
31757        descriptor.enumerable = descriptor.enumerable || false;
31758        descriptor.configurable = true;
31759        if ("value" in descriptor) descriptor.writable = true;
31760        Object.defineProperty(target, descriptor.key, descriptor);
31761      }
31762    }
31763
31764    return function (Constructor, protoProps, staticProps) {
31765      if (protoProps) defineProperties(Constructor.prototype, protoProps);
31766      if (staticProps) defineProperties(Constructor, staticProps);
31767      return Constructor;
31768    };
31769  }();
31770
31771  var get$2 = function get$$1(object, property, receiver) {
31772    if (object === null) object = Function.prototype;
31773    var desc = Object.getOwnPropertyDescriptor(object, property);
31774
31775    if (desc === undefined) {
31776      var parent = Object.getPrototypeOf(object);
31777
31778      if (parent === null) {
31779        return undefined;
31780      } else {
31781        return get$$1(parent, property, receiver);
31782      }
31783    } else if ("value" in desc) {
31784      return desc.value;
31785    } else {
31786      var getter = desc.get;
31787
31788      if (getter === undefined) {
31789        return undefined;
31790      }
31791
31792      return getter.call(receiver);
31793    }
31794  };
31795
31796  var inherits$3 = function inherits$$1(subClass, superClass) {
31797    if (typeof superClass !== "function" && superClass !== null) {
31798      throw new TypeError("Super expression must either be null or a function, not " + (typeof superClass === 'undefined' ? 'undefined' : _typeof(superClass)));
31799    }
31800
31801    subClass.prototype = Object.create(superClass && superClass.prototype, {
31802      constructor: {
31803        value: subClass,
31804        enumerable: false,
31805        writable: true,
31806        configurable: true
31807      }
31808    });
31809    if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
31810  };
31811
31812  var possibleConstructorReturn$3 = function possibleConstructorReturn$$1(self, call) {
31813    if (!self) {
31814      throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
31815    }
31816
31817    return call && ((typeof call === 'undefined' ? 'undefined' : _typeof(call)) === "object" || typeof call === "function") ? call : self;
31818  };
31819
31820  var slicedToArray$1 = function () {
31821    function sliceIterator(arr, i) {
31822      var _arr = [];
31823      var _n = true;
31824      var _d = false;
31825      var _e = undefined;
31826
31827      try {
31828        for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
31829          _arr.push(_s.value);
31830
31831          if (i && _arr.length === i) break;
31832        }
31833      } catch (err) {
31834        _d = true;
31835        _e = err;
31836      } finally {
31837        try {
31838          if (!_n && _i["return"]) _i["return"]();
31839        } finally {
31840          if (_d) throw _e;
31841        }
31842      }
31843
31844      return _arr;
31845    }
31846
31847    return function (arr, i) {
31848      if (Array.isArray(arr)) {
31849        return arr;
31850      } else if (Symbol.iterator in Object(arr)) {
31851        return sliceIterator(arr, i);
31852      } else {
31853        throw new TypeError("Invalid attempt to destructure non-iterable instance");
31854      }
31855    };
31856  }();
31857
31858  /**
31859   * @file playlist-loader.js
31860   *
31861   * A state machine that manages the loading, caching, and updating of
31862   * M3U8 playlists.
31863   *
31864   */
31865
31866  var mergeOptions$1 = videojs$1.mergeOptions,
31867      EventTarget$1 = videojs$1.EventTarget,
31868      log$2 = videojs$1.log;
31869
31870  /**
31871   * Loops through all supported media groups in master and calls the provided
31872   * callback for each group
31873   *
31874   * @param {Object} master
31875   *        The parsed master manifest object
31876   * @param {Function} callback
31877   *        Callback to call for each media group
31878   */
31879
31880  var forEachMediaGroup = function forEachMediaGroup(master, callback) {
31881    ['AUDIO', 'SUBTITLES'].forEach(function (mediaType) {
31882      for (var groupKey in master.mediaGroups[mediaType]) {
31883        for (var labelKey in master.mediaGroups[mediaType][groupKey]) {
31884          var mediaProperties = master.mediaGroups[mediaType][groupKey][labelKey];
31885
31886          callback(mediaProperties, mediaType, groupKey, labelKey);
31887        }
31888      }
31889    });
31890  };
31891
31892  /**
31893    * Returns a new array of segments that is the result of merging
31894    * properties from an older list of segments onto an updated
31895    * list. No properties on the updated playlist will be overridden.
31896    *
31897    * @param {Array} original the outdated list of segments
31898    * @param {Array} update the updated list of segments
31899    * @param {Number=} offset the index of the first update
31900    * segment in the original segment list. For non-live playlists,
31901    * this should always be zero and does not need to be
31902    * specified. For live playlists, it should be the difference
31903    * between the media sequence numbers in the original and updated
31904    * playlists.
31905    * @return a list of merged segment objects
31906    */
31907  var updateSegments = function updateSegments(original, update, offset) {
31908    var result = update.slice();
31909
31910    offset = offset || 0;
31911    var length = Math.min(original.length, update.length + offset);
31912
31913    for (var i = offset; i < length; i++) {
31914      result[i - offset] = mergeOptions$1(original[i], result[i - offset]);
31915    }
31916    return result;
31917  };
31918
31919  var resolveSegmentUris = function resolveSegmentUris(segment, baseUri) {
31920    if (!segment.resolvedUri) {
31921      segment.resolvedUri = resolveUrl$1(baseUri, segment.uri);
31922    }
31923    if (segment.key && !segment.key.resolvedUri) {
31924      segment.key.resolvedUri = resolveUrl$1(baseUri, segment.key.uri);
31925    }
31926    if (segment.map && !segment.map.resolvedUri) {
31927      segment.map.resolvedUri = resolveUrl$1(baseUri, segment.map.uri);
31928    }
31929  };
31930
31931  /**
31932    * Returns a new master playlist that is the result of merging an
31933    * updated media playlist into the original version. If the
31934    * updated media playlist does not match any of the playlist
31935    * entries in the original master playlist, null is returned.
31936    *
31937    * @param {Object} master a parsed master M3U8 object
31938    * @param {Object} media a parsed media M3U8 object
31939    * @return {Object} a new object that represents the original
31940    * master playlist with the updated media playlist merged in, or
31941    * null if the merge produced no change.
31942    */
31943  var updateMaster = function updateMaster(master, media) {
31944    var result = mergeOptions$1(master, {});
31945    var playlist = result.playlists[media.uri];
31946
31947    if (!playlist) {
31948      return null;
31949    }
31950
31951    // consider the playlist unchanged if the number of segments is equal and the media
31952    // sequence number is unchanged
31953    if (playlist.segments && media.segments && playlist.segments.length === media.segments.length && playlist.mediaSequence === media.mediaSequence) {
31954      return null;
31955    }
31956
31957    var mergedPlaylist = mergeOptions$1(playlist, media);
31958
31959    // if the update could overlap existing segment information, merge the two segment lists
31960    if (playlist.segments) {
31961      mergedPlaylist.segments = updateSegments(playlist.segments, media.segments, media.mediaSequence - playlist.mediaSequence);
31962    }
31963
31964    // resolve any segment URIs to prevent us from having to do it later
31965    mergedPlaylist.segments.forEach(function (segment) {
31966      resolveSegmentUris(segment, mergedPlaylist.resolvedUri);
31967    });
31968
31969    // TODO Right now in the playlists array there are two references to each playlist, one
31970    // that is referenced by index, and one by URI. The index reference may no longer be
31971    // necessary.
31972    for (var i = 0; i < result.playlists.length; i++) {
31973      if (result.playlists[i].uri === media.uri) {
31974        result.playlists[i] = mergedPlaylist;
31975      }
31976    }
31977    result.playlists[media.uri] = mergedPlaylist;
31978
31979    return result;
31980  };
31981
31982  var setupMediaPlaylists = function setupMediaPlaylists(master) {
31983    // setup by-URI lookups and resolve media playlist URIs
31984    var i = master.playlists.length;
31985
31986    while (i--) {
31987      var playlist = master.playlists[i];
31988
31989      master.playlists[playlist.uri] = playlist;
31990      playlist.resolvedUri = resolveUrl$1(master.uri, playlist.uri);
31991      playlist.id = i;
31992
31993      if (!playlist.attributes) {
31994        // Although the spec states an #EXT-X-STREAM-INF tag MUST have a
31995        // BANDWIDTH attribute, we can play the stream without it. This means a poorly
31996        // formatted master playlist may not have an attribute list. An attributes
31997        // property is added here to prevent undefined references when we encounter
31998        // this scenario.
31999        playlist.attributes = {};
32000
32001        log$2.warn('Invalid playlist STREAM-INF detected. Missing BANDWIDTH attribute.');
32002      }
32003    }
32004  };
32005
32006  var resolveMediaGroupUris = function resolveMediaGroupUris(master) {
32007    forEachMediaGroup(master, function (properties) {
32008      if (properties.uri) {
32009        properties.resolvedUri = resolveUrl$1(master.uri, properties.uri);
32010      }
32011    });
32012  };
32013
32014  /**
32015   * Calculates the time to wait before refreshing a live playlist
32016   *
32017   * @param {Object} media
32018   *        The current media
32019   * @param {Boolean} update
32020   *        True if there were any updates from the last refresh, false otherwise
32021   * @return {Number}
32022   *         The time in ms to wait before refreshing the live playlist
32023   */
32024  var refreshDelay = function refreshDelay(media, update) {
32025    var lastSegment = media.segments[media.segments.length - 1];
32026    var delay = void 0;
32027
32028    if (update && lastSegment && lastSegment.duration) {
32029      delay = lastSegment.duration * 1000;
32030    } else {
32031      // if the playlist is unchanged since the last reload or last segment duration
32032      // cannot be determined, try again after half the target duration
32033      delay = (media.targetDuration || 10) * 500;
32034    }
32035    return delay;
32036  };
32037
32038  /**
32039   * Load a playlist from a remote location
32040   *
32041   * @class PlaylistLoader
32042   * @extends Stream
32043   * @param {String} srcUrl the url to start with
32044   * @param {Boolean} withCredentials the withCredentials xhr option
32045   * @constructor
32046   */
32047
32048  var PlaylistLoader = function (_EventTarget) {
32049    inherits$3(PlaylistLoader, _EventTarget);
32050
32051    function PlaylistLoader(srcUrl, hls, withCredentials) {
32052      classCallCheck$3(this, PlaylistLoader);
32053
32054      var _this = possibleConstructorReturn$3(this, (PlaylistLoader.__proto__ || Object.getPrototypeOf(PlaylistLoader)).call(this));
32055
32056      _this.srcUrl = srcUrl;
32057      _this.hls_ = hls;
32058      _this.withCredentials = withCredentials;
32059
32060      if (!_this.srcUrl) {
32061        throw new Error('A non-empty playlist URL is required');
32062      }
32063
32064      // initialize the loader state
32065      _this.state = 'HAVE_NOTHING';
32066
32067      // live playlist staleness timeout
32068      _this.on('mediaupdatetimeout', function () {
32069        if (_this.state !== 'HAVE_METADATA') {
32070          // only refresh the media playlist if no other activity is going on
32071          return;
32072        }
32073
32074        _this.state = 'HAVE_CURRENT_METADATA';
32075
32076        _this.request = _this.hls_.xhr({
32077          uri: resolveUrl$1(_this.master.uri, _this.media().uri),
32078          withCredentials: _this.withCredentials
32079        }, function (error, req) {
32080          // disposed
32081          if (!_this.request) {
32082            return;
32083          }
32084
32085          if (error) {
32086            return _this.playlistRequestError(_this.request, _this.media().uri, 'HAVE_METADATA');
32087          }
32088
32089          _this.haveMetadata(_this.request, _this.media().uri);
32090        });
32091      });
32092      return _this;
32093    }
32094
32095    createClass$2(PlaylistLoader, [{
32096      key: 'playlistRequestError',
32097      value: function playlistRequestError(xhr, url, startingState) {
32098        // any in-flight request is now finished
32099        this.request = null;
32100
32101        if (startingState) {
32102          this.state = startingState;
32103        }
32104
32105        this.error = {
32106          playlist: this.master.playlists[url],
32107          status: xhr.status,
32108          message: 'HLS playlist request error at URL: ' + url,
32109          responseText: xhr.responseText,
32110          code: xhr.status >= 500 ? 4 : 2
32111        };
32112
32113        this.trigger('error');
32114      }
32115
32116      // update the playlist loader's state in response to a new or
32117      // updated playlist.
32118
32119    }, {
32120      key: 'haveMetadata',
32121      value: function haveMetadata(xhr, url) {
32122        var _this2 = this;
32123
32124        // any in-flight request is now finished
32125        this.request = null;
32126        this.state = 'HAVE_METADATA';
32127
32128        var parser = new Parser();
32129
32130        parser.push(xhr.responseText);
32131        parser.end();
32132        parser.manifest.uri = url;
32133        // m3u8-parser does not attach an attributes property to media playlists so make
32134        // sure that the property is attached to avoid undefined reference errors
32135        parser.manifest.attributes = parser.manifest.attributes || {};
32136
32137        // merge this playlist into the master
32138        var update = updateMaster(this.master, parser.manifest);
32139
32140        this.targetDuration = parser.manifest.targetDuration;
32141
32142        if (update) {
32143          this.master = update;
32144          this.media_ = this.master.playlists[parser.manifest.uri];
32145        } else {
32146          this.trigger('playlistunchanged');
32147        }
32148
32149        // refresh live playlists after a target duration passes
32150        if (!this.media().endList) {
32151          window_1.clearTimeout(this.mediaUpdateTimeout);
32152          this.mediaUpdateTimeout = window_1.setTimeout(function () {
32153            _this2.trigger('mediaupdatetimeout');
32154          }, refreshDelay(this.media(), !!update));
32155        }
32156
32157        this.trigger('loadedplaylist');
32158      }
32159
32160      /**
32161       * Abort any outstanding work and clean up.
32162       */
32163
32164    }, {
32165      key: 'dispose',
32166      value: function dispose() {
32167        this.stopRequest();
32168        window_1.clearTimeout(this.mediaUpdateTimeout);
32169      }
32170    }, {
32171      key: 'stopRequest',
32172      value: function stopRequest() {
32173        if (this.request) {
32174          var oldRequest = this.request;
32175
32176          this.request = null;
32177          oldRequest.onreadystatechange = null;
32178          oldRequest.abort();
32179        }
32180      }
32181
32182      /**
32183       * When called without any arguments, returns the currently
32184       * active media playlist. When called with a single argument,
32185       * triggers the playlist loader to asynchronously switch to the
32186       * specified media playlist. Calling this method while the
32187       * loader is in the HAVE_NOTHING causes an error to be emitted
32188       * but otherwise has no effect.
32189       *
32190       * @param {Object=} playlist the parsed media playlist
32191       * object to switch to
32192       * @return {Playlist} the current loaded media
32193       */
32194
32195    }, {
32196      key: 'media',
32197      value: function media(playlist) {
32198        var _this3 = this;
32199
32200        // getter
32201        if (!playlist) {
32202          return this.media_;
32203        }
32204
32205        // setter
32206        if (this.state === 'HAVE_NOTHING') {
32207          throw new Error('Cannot switch media playlist from ' + this.state);
32208        }
32209
32210        var startingState = this.state;
32211
32212        // find the playlist object if the target playlist has been
32213        // specified by URI
32214        if (typeof playlist === 'string') {
32215          if (!this.master.playlists[playlist]) {
32216            throw new Error('Unknown playlist URI: ' + playlist);
32217          }
32218          playlist = this.master.playlists[playlist];
32219        }
32220
32221        var mediaChange = !this.media_ || playlist.uri !== this.media_.uri;
32222
32223        // switch to fully loaded playlists immediately
32224        if (this.master.playlists[playlist.uri].endList) {
32225          // abort outstanding playlist requests
32226          if (this.request) {
32227            this.request.onreadystatechange = null;
32228            this.request.abort();
32229            this.request = null;
32230          }
32231          this.state = 'HAVE_METADATA';
32232          this.media_ = playlist;
32233
32234          // trigger media change if the active media has been updated
32235          if (mediaChange) {
32236            this.trigger('mediachanging');
32237            this.trigger('mediachange');
32238          }
32239          return;
32240        }
32241
32242        // switching to the active playlist is a no-op
32243        if (!mediaChange) {
32244          return;
32245        }
32246
32247        this.state = 'SWITCHING_MEDIA';
32248
32249        // there is already an outstanding playlist request
32250        if (this.request) {
32251          if (resolveUrl$1(this.master.uri, playlist.uri) === this.request.url) {
32252            // requesting to switch to the same playlist multiple times
32253            // has no effect after the first
32254            return;
32255          }
32256          this.request.onreadystatechange = null;
32257          this.request.abort();
32258          this.request = null;
32259        }
32260
32261        // request the new playlist
32262        if (this.media_) {
32263          this.trigger('mediachanging');
32264        }
32265
32266        this.request = this.hls_.xhr({
32267          uri: resolveUrl$1(this.master.uri, playlist.uri),
32268          withCredentials: this.withCredentials
32269        }, function (error, req) {
32270          // disposed
32271          if (!_this3.request) {
32272            return;
32273          }
32274
32275          if (error) {
32276            return _this3.playlistRequestError(_this3.request, playlist.uri, startingState);
32277          }
32278
32279          _this3.haveMetadata(req, playlist.uri);
32280
32281          // fire loadedmetadata the first time a media playlist is loaded
32282          if (startingState === 'HAVE_MASTER') {
32283            _this3.trigger('loadedmetadata');
32284          } else {
32285            _this3.trigger('mediachange');
32286          }
32287        });
32288      }
32289
32290      /**
32291       * pause loading of the playlist
32292       */
32293
32294    }, {
32295      key: 'pause',
32296      value: function pause() {
32297        this.stopRequest();
32298        window_1.clearTimeout(this.mediaUpdateTimeout);
32299        if (this.state === 'HAVE_NOTHING') {
32300          // If we pause the loader before any data has been retrieved, its as if we never
32301          // started, so reset to an unstarted state.
32302          this.started = false;
32303        }
32304        // Need to restore state now that no activity is happening
32305        if (this.state === 'SWITCHING_MEDIA') {
32306          // if the loader was in the process of switching media, it should either return to
32307          // HAVE_MASTER or HAVE_METADATA depending on if the loader has loaded a media
32308          // playlist yet. This is determined by the existence of loader.media_
32309          if (this.media_) {
32310            this.state = 'HAVE_METADATA';
32311          } else {
32312            this.state = 'HAVE_MASTER';
32313          }
32314        } else if (this.state === 'HAVE_CURRENT_METADATA') {
32315          this.state = 'HAVE_METADATA';
32316        }
32317      }
32318
32319      /**
32320       * start loading of the playlist
32321       */
32322
32323    }, {
32324      key: 'load',
32325      value: function load(isFinalRendition) {
32326        var _this4 = this;
32327
32328        window_1.clearTimeout(this.mediaUpdateTimeout);
32329
32330        var media = this.media();
32331
32332        if (isFinalRendition) {
32333          var delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000;
32334
32335          this.mediaUpdateTimeout = window_1.setTimeout(function () {
32336            return _this4.load();
32337          }, delay);
32338          return;
32339        }
32340
32341        if (!this.started) {
32342          this.start();
32343          return;
32344        }
32345
32346        if (media && !media.endList) {
32347          this.trigger('mediaupdatetimeout');
32348        } else {
32349          this.trigger('loadedplaylist');
32350        }
32351      }
32352
32353      /**
32354       * start loading of the playlist
32355       */
32356
32357    }, {
32358      key: 'start',
32359      value: function start() {
32360        var _this5 = this;
32361
32362        this.started = true;
32363
32364        // request the specified URL
32365        this.request = this.hls_.xhr({
32366          uri: this.srcUrl,
32367          withCredentials: this.withCredentials
32368        }, function (error, req) {
32369          // disposed
32370          if (!_this5.request) {
32371            return;
32372          }
32373
32374          // clear the loader's request reference
32375          _this5.request = null;
32376
32377          if (error) {
32378            _this5.error = {
32379              status: req.status,
32380              message: 'HLS playlist request error at URL: ' + _this5.srcUrl,
32381              responseText: req.responseText,
32382              // MEDIA_ERR_NETWORK
32383              code: 2
32384            };
32385            if (_this5.state === 'HAVE_NOTHING') {
32386              _this5.started = false;
32387            }
32388            return _this5.trigger('error');
32389          }
32390
32391          var parser = new Parser();
32392
32393          parser.push(req.responseText);
32394          parser.end();
32395
32396          _this5.state = 'HAVE_MASTER';
32397
32398          parser.manifest.uri = _this5.srcUrl;
32399
32400          // loaded a master playlist
32401          if (parser.manifest.playlists) {
32402            _this5.master = parser.manifest;
32403
32404            setupMediaPlaylists(_this5.master);
32405            resolveMediaGroupUris(_this5.master);
32406
32407            _this5.trigger('loadedplaylist');
32408            if (!_this5.request) {
32409              // no media playlist was specifically selected so start
32410              // from the first listed one
32411              _this5.media(parser.manifest.playlists[0]);
32412            }
32413            return;
32414          }
32415
32416          // loaded a media playlist
32417          // infer a master playlist if none was previously requested
32418          _this5.master = {
32419            mediaGroups: {
32420              'AUDIO': {},
32421              'VIDEO': {},
32422              'CLOSED-CAPTIONS': {},
32423              'SUBTITLES': {}
32424            },
32425            uri: window_1.location.href,
32426            playlists: [{
32427              uri: _this5.srcUrl,
32428              id: 0
32429            }]
32430          };
32431          _this5.master.playlists[_this5.srcUrl] = _this5.master.playlists[0];
32432          _this5.master.playlists[0].resolvedUri = _this5.srcUrl;
32433          // m3u8-parser does not attach an attributes property to media playlists so make
32434          // sure that the property is attached to avoid undefined reference errors
32435          _this5.master.playlists[0].attributes = _this5.master.playlists[0].attributes || {};
32436          _this5.haveMetadata(req, _this5.srcUrl);
32437          return _this5.trigger('loadedmetadata');
32438        });
32439      }
32440    }]);
32441    return PlaylistLoader;
32442  }(EventTarget$1);
32443
32444  /**
32445   * @file playlist.js
32446   *
32447   * Playlist related utilities.
32448   */
32449
32450  var createTimeRange = videojs$1.createTimeRange;
32451
32452  /**
32453   * walk backward until we find a duration we can use
32454   * or return a failure
32455   *
32456   * @param {Playlist} playlist the playlist to walk through
32457   * @param {Number} endSequence the mediaSequence to stop walking on
32458   */
32459
32460  var backwardDuration = function backwardDuration(playlist, endSequence) {
32461    var result = 0;
32462    var i = endSequence - playlist.mediaSequence;
32463    // if a start time is available for segment immediately following
32464    // the interval, use it
32465    var segment = playlist.segments[i];
32466
32467    // Walk backward until we find the latest segment with timeline
32468    // information that is earlier than endSequence
32469    if (segment) {
32470      if (typeof segment.start !== 'undefined') {
32471        return { result: segment.start, precise: true };
32472      }
32473      if (typeof segment.end !== 'undefined') {
32474        return {
32475          result: segment.end - segment.duration,
32476          precise: true
32477        };
32478      }
32479    }
32480    while (i--) {
32481      segment = playlist.segments[i];
32482      if (typeof segment.end !== 'undefined') {
32483        return { result: result + segment.end, precise: true };
32484      }
32485
32486      result += segment.duration;
32487
32488      if (typeof segment.start !== 'undefined') {
32489        return { result: result + segment.start, precise: true };
32490      }
32491    }
32492    return { result: result, precise: false };
32493  };
32494
32495  /**
32496   * walk forward until we find a duration we can use
32497   * or return a failure
32498   *
32499   * @param {Playlist} playlist the playlist to walk through
32500   * @param {Number} endSequence the mediaSequence to stop walking on
32501   */
32502  var forwardDuration = function forwardDuration(playlist, endSequence) {
32503    var result = 0;
32504    var segment = void 0;
32505    var i = endSequence - playlist.mediaSequence;
32506    // Walk forward until we find the earliest segment with timeline
32507    // information
32508
32509    for (; i < playlist.segments.length; i++) {
32510      segment = playlist.segments[i];
32511      if (typeof segment.start !== 'undefined') {
32512        return {
32513          result: segment.start - result,
32514          precise: true
32515        };
32516      }
32517
32518      result += segment.duration;
32519
32520      if (typeof segment.end !== 'undefined') {
32521        return {
32522          result: segment.end - result,
32523          precise: true
32524        };
32525      }
32526    }
32527    // indicate we didn't find a useful duration estimate
32528    return { result: -1, precise: false };
32529  };
32530
32531  /**
32532    * Calculate the media duration from the segments associated with a
32533    * playlist. The duration of a subinterval of the available segments
32534    * may be calculated by specifying an end index.
32535    *
32536    * @param {Object} playlist a media playlist object
32537    * @param {Number=} endSequence an exclusive upper boundary
32538    * for the playlist.  Defaults to playlist length.
32539    * @param {Number} expired the amount of time that has dropped
32540    * off the front of the playlist in a live scenario
32541    * @return {Number} the duration between the first available segment
32542    * and end index.
32543    */
32544  var intervalDuration = function intervalDuration(playlist, endSequence, expired) {
32545    var backward = void 0;
32546    var forward = void 0;
32547
32548    if (typeof endSequence === 'undefined') {
32549      endSequence = playlist.mediaSequence + playlist.segments.length;
32550    }
32551
32552    if (endSequence < playlist.mediaSequence) {
32553      return 0;
32554    }
32555
32556    // do a backward walk to estimate the duration
32557    backward = backwardDuration(playlist, endSequence);
32558    if (backward.precise) {
32559      // if we were able to base our duration estimate on timing
32560      // information provided directly from the Media Source, return
32561      // it
32562      return backward.result;
32563    }
32564
32565    // walk forward to see if a precise duration estimate can be made
32566    // that way
32567    forward = forwardDuration(playlist, endSequence);
32568    if (forward.precise) {
32569      // we found a segment that has been buffered and so it's
32570      // position is known precisely
32571      return forward.result;
32572    }
32573
32574    // return the less-precise, playlist-based duration estimate
32575    return backward.result + expired;
32576  };
32577
32578  /**
32579    * Calculates the duration of a playlist. If a start and end index
32580    * are specified, the duration will be for the subset of the media
32581    * timeline between those two indices. The total duration for live
32582    * playlists is always Infinity.
32583    *
32584    * @param {Object} playlist a media playlist object
32585    * @param {Number=} endSequence an exclusive upper
32586    * boundary for the playlist. Defaults to the playlist media
32587    * sequence number plus its length.
32588    * @param {Number=} expired the amount of time that has
32589    * dropped off the front of the playlist in a live scenario
32590    * @return {Number} the duration between the start index and end
32591    * index.
32592    */
32593  var duration = function duration(playlist, endSequence, expired) {
32594    if (!playlist) {
32595      return 0;
32596    }
32597
32598    if (typeof expired !== 'number') {
32599      expired = 0;
32600    }
32601
32602    // if a slice of the total duration is not requested, use
32603    // playlist-level duration indicators when they're present
32604    if (typeof endSequence === 'undefined') {
32605      // if present, use the duration specified in the playlist
32606      if (playlist.totalDuration) {
32607        return playlist.totalDuration;
32608      }
32609
32610      // duration should be Infinity for live playlists
32611      if (!playlist.endList) {
32612        return window_1.Infinity;
32613      }
32614    }
32615
32616    // calculate the total duration based on the segment durations
32617    return intervalDuration(playlist, endSequence, expired);
32618  };
32619
32620  /**
32621    * Calculate the time between two indexes in the current playlist
32622    * neight the start- nor the end-index need to be within the current
32623    * playlist in which case, the targetDuration of the playlist is used
32624    * to approximate the durations of the segments
32625    *
32626    * @param {Object} playlist a media playlist object
32627    * @param {Number} startIndex
32628    * @param {Number} endIndex
32629    * @return {Number} the number of seconds between startIndex and endIndex
32630    */
32631  var sumDurations = function sumDurations(playlist, startIndex, endIndex) {
32632    var durations = 0;
32633
32634    if (startIndex > endIndex) {
32635      var _ref = [endIndex, startIndex];
32636      startIndex = _ref[0];
32637      endIndex = _ref[1];
32638    }
32639
32640    if (startIndex < 0) {
32641      for (var i = startIndex; i < Math.min(0, endIndex); i++) {
32642        durations += playlist.targetDuration;
32643      }
32644      startIndex = 0;
32645    }
32646
32647    for (var _i = startIndex; _i < endIndex; _i++) {
32648      durations += playlist.segments[_i].duration;
32649    }
32650
32651    return durations;
32652  };
32653
32654  /**
32655   * Determines the media index of the segment corresponding to the safe edge of the live
32656   * window which is the duration of the last segment plus 2 target durations from the end
32657   * of the playlist.
32658   *
32659   * @param {Object} playlist
32660   *        a media playlist object
32661   * @return {Number}
32662   *         The media index of the segment at the safe live point. 0 if there is no "safe"
32663   *         point.
32664   * @function safeLiveIndex
32665   */
32666  var safeLiveIndex = function safeLiveIndex(playlist) {
32667    if (!playlist.segments.length) {
32668      return 0;
32669    }
32670
32671    var i = playlist.segments.length - 1;
32672    var distanceFromEnd = playlist.segments[i].duration || playlist.targetDuration;
32673    var safeDistance = distanceFromEnd + playlist.targetDuration * 2;
32674
32675    while (i--) {
32676      distanceFromEnd += playlist.segments[i].duration;
32677
32678      if (distanceFromEnd >= safeDistance) {
32679        break;
32680      }
32681    }
32682
32683    return Math.max(0, i);
32684  };
32685
32686  /**
32687   * Calculates the playlist end time
32688   *
32689   * @param {Object} playlist a media playlist object
32690   * @param {Number=} expired the amount of time that has
32691   *                  dropped off the front of the playlist in a live scenario
32692   * @param {Boolean|false} useSafeLiveEnd a boolean value indicating whether or not the
32693   *                        playlist end calculation should consider the safe live end
32694   *                        (truncate the playlist end by three segments). This is normally
32695   *                        used for calculating the end of the playlist's seekable range.
32696   * @returns {Number} the end time of playlist
32697   * @function playlistEnd
32698   */
32699  var playlistEnd = function playlistEnd(playlist, expired, useSafeLiveEnd) {
32700    if (!playlist || !playlist.segments) {
32701      return null;
32702    }
32703    if (playlist.endList) {
32704      return duration(playlist);
32705    }
32706
32707    if (expired === null) {
32708      return null;
32709    }
32710
32711    expired = expired || 0;
32712
32713    var endSequence = useSafeLiveEnd ? safeLiveIndex(playlist) : playlist.segments.length;
32714
32715    return intervalDuration(playlist, playlist.mediaSequence + endSequence, expired);
32716  };
32717
32718  /**
32719    * Calculates the interval of time that is currently seekable in a
32720    * playlist. The returned time ranges are relative to the earliest
32721    * moment in the specified playlist that is still available. A full
32722    * seekable implementation for live streams would need to offset
32723    * these values by the duration of content that has expired from the
32724    * stream.
32725    *
32726    * @param {Object} playlist a media playlist object
32727    * dropped off the front of the playlist in a live scenario
32728    * @param {Number=} expired the amount of time that has
32729    * dropped off the front of the playlist in a live scenario
32730    * @return {TimeRanges} the periods of time that are valid targets
32731    * for seeking
32732    */
32733  var seekable = function seekable(playlist, expired) {
32734    var useSafeLiveEnd = true;
32735    var seekableStart = expired || 0;
32736    var seekableEnd = playlistEnd(playlist, expired, useSafeLiveEnd);
32737
32738    if (seekableEnd === null) {
32739      return createTimeRange();
32740    }
32741    return createTimeRange(seekableStart, seekableEnd);
32742  };
32743
32744  var isWholeNumber = function isWholeNumber(num) {
32745    return num - Math.floor(num) === 0;
32746  };
32747
32748  var roundSignificantDigit = function roundSignificantDigit(increment, num) {
32749    // If we have a whole number, just add 1 to it
32750    if (isWholeNumber(num)) {
32751      return num + increment * 0.1;
32752    }
32753
32754    var numDecimalDigits = num.toString().split('.')[1].length;
32755
32756    for (var i = 1; i <= numDecimalDigits; i++) {
32757      var scale = Math.pow(10, i);
32758      var temp = num * scale;
32759
32760      if (isWholeNumber(temp) || i === numDecimalDigits) {
32761        return (temp + increment) / scale;
32762      }
32763    }
32764  };
32765
32766  var ceilLeastSignificantDigit = roundSignificantDigit.bind(null, 1);
32767  var floorLeastSignificantDigit = roundSignificantDigit.bind(null, -1);
32768
32769  /**
32770   * Determine the index and estimated starting time of the segment that
32771   * contains a specified playback position in a media playlist.
32772   *
32773   * @param {Object} playlist the media playlist to query
32774   * @param {Number} currentTime The number of seconds since the earliest
32775   * possible position to determine the containing segment for
32776   * @param {Number} startIndex
32777   * @param {Number} startTime
32778   * @return {Object}
32779   */
32780  var getMediaInfoForTime = function getMediaInfoForTime(playlist, currentTime, startIndex, startTime) {
32781    var i = void 0;
32782    var segment = void 0;
32783    var numSegments = playlist.segments.length;
32784
32785    var time = currentTime - startTime;
32786
32787    if (time < 0) {
32788      // Walk backward from startIndex in the playlist, adding durations
32789      // until we find a segment that contains `time` and return it
32790      if (startIndex > 0) {
32791        for (i = startIndex - 1; i >= 0; i--) {
32792          segment = playlist.segments[i];
32793          time += floorLeastSignificantDigit(segment.duration);
32794          if (time > 0) {
32795            return {
32796              mediaIndex: i,
32797              startTime: startTime - sumDurations(playlist, startIndex, i)
32798            };
32799          }
32800        }
32801      }
32802      // We were unable to find a good segment within the playlist
32803      // so select the first segment
32804      return {
32805        mediaIndex: 0,
32806        startTime: currentTime
32807      };
32808    }
32809
32810    // When startIndex is negative, we first walk forward to first segment
32811    // adding target durations. If we "run out of time" before getting to
32812    // the first segment, return the first segment
32813    if (startIndex < 0) {
32814      for (i = startIndex; i < 0; i++) {
32815        time -= playlist.targetDuration;
32816        if (time < 0) {
32817          return {
32818            mediaIndex: 0,
32819            startTime: currentTime
32820          };
32821        }
32822      }
32823      startIndex = 0;
32824    }
32825
32826    // Walk forward from startIndex in the playlist, subtracting durations
32827    // until we find a segment that contains `time` and return it
32828    for (i = startIndex; i < numSegments; i++) {
32829      segment = playlist.segments[i];
32830      time -= ceilLeastSignificantDigit(segment.duration);
32831      if (time < 0) {
32832        return {
32833          mediaIndex: i,
32834          startTime: startTime + sumDurations(playlist, startIndex, i)
32835        };
32836      }
32837    }
32838
32839    // We are out of possible candidates so load the last one...
32840    return {
32841      mediaIndex: numSegments - 1,
32842      startTime: currentTime
32843    };
32844  };
32845
32846  /**
32847   * Check whether the playlist is blacklisted or not.
32848   *
32849   * @param {Object} playlist the media playlist object
32850   * @return {boolean} whether the playlist is blacklisted or not
32851   * @function isBlacklisted
32852   */
32853  var isBlacklisted = function isBlacklisted(playlist) {
32854    return playlist.excludeUntil && playlist.excludeUntil > Date.now();
32855  };
32856
32857  /**
32858   * Check whether the playlist is compatible with current playback configuration or has
32859   * been blacklisted permanently for being incompatible.
32860   *
32861   * @param {Object} playlist the media playlist object
32862   * @return {boolean} whether the playlist is incompatible or not
32863   * @function isIncompatible
32864   */
32865  var isIncompatible = function isIncompatible(playlist) {
32866    return playlist.excludeUntil && playlist.excludeUntil === Infinity;
32867  };
32868
32869  /**
32870   * Check whether the playlist is enabled or not.
32871   *
32872   * @param {Object} playlist the media playlist object
32873   * @return {boolean} whether the playlist is enabled or not
32874   * @function isEnabled
32875   */
32876  var isEnabled = function isEnabled(playlist) {
32877    var blacklisted = isBlacklisted(playlist);
32878
32879    return !playlist.disabled && !blacklisted;
32880  };
32881
32882  /**
32883   * Check whether the playlist has been manually disabled through the representations api.
32884   *
32885   * @param {Object} playlist the media playlist object
32886   * @return {boolean} whether the playlist is disabled manually or not
32887   * @function isDisabled
32888   */
32889  var isDisabled = function isDisabled(playlist) {
32890    return playlist.disabled;
32891  };
32892
32893  /**
32894   * Returns whether the current playlist is an AES encrypted HLS stream
32895   *
32896   * @return {Boolean} true if it's an AES encrypted HLS stream
32897   */
32898  var isAes = function isAes(media) {
32899    for (var i = 0; i < media.segments.length; i++) {
32900      if (media.segments[i].key) {
32901        return true;
32902      }
32903    }
32904    return false;
32905  };
32906
32907  /**
32908   * Returns whether the current playlist contains fMP4
32909   *
32910   * @return {Boolean} true if the playlist contains fMP4
32911   */
32912  var isFmp4 = function isFmp4(media) {
32913    for (var i = 0; i < media.segments.length; i++) {
32914      if (media.segments[i].map) {
32915        return true;
32916      }
32917    }
32918    return false;
32919  };
32920
32921  /**
32922   * Checks if the playlist has a value for the specified attribute
32923   *
32924   * @param {String} attr
32925   *        Attribute to check for
32926   * @param {Object} playlist
32927   *        The media playlist object
32928   * @return {Boolean}
32929   *         Whether the playlist contains a value for the attribute or not
32930   * @function hasAttribute
32931   */
32932  var hasAttribute = function hasAttribute(attr, playlist) {
32933    return playlist.attributes && playlist.attributes[attr];
32934  };
32935
32936  /**
32937   * Estimates the time required to complete a segment download from the specified playlist
32938   *
32939   * @param {Number} segmentDuration
32940   *        Duration of requested segment
32941   * @param {Number} bandwidth
32942   *        Current measured bandwidth of the player
32943   * @param {Object} playlist
32944   *        The media playlist object
32945   * @param {Number=} bytesReceived
32946   *        Number of bytes already received for the request. Defaults to 0
32947   * @return {Number|NaN}
32948   *         The estimated time to request the segment. NaN if bandwidth information for
32949   *         the given playlist is unavailable
32950   * @function estimateSegmentRequestTime
32951   */
32952  var estimateSegmentRequestTime = function estimateSegmentRequestTime(segmentDuration, bandwidth, playlist) {
32953    var bytesReceived = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
32954
32955    if (!hasAttribute('BANDWIDTH', playlist)) {
32956      return NaN;
32957    }
32958
32959    var size = segmentDuration * playlist.attributes.BANDWIDTH;
32960
32961    return (size - bytesReceived * 8) / bandwidth;
32962  };
32963
32964  /*
32965   * Returns whether the current playlist is the lowest rendition
32966   *
32967   * @return {Boolean} true if on lowest rendition
32968   */
32969  var isLowestEnabledRendition = function isLowestEnabledRendition(master, media) {
32970    if (master.playlists.length === 1) {
32971      return true;
32972    }
32973
32974    var currentBandwidth = media.attributes.BANDWIDTH || Number.MAX_VALUE;
32975
32976    return master.playlists.filter(function (playlist) {
32977      if (!isEnabled(playlist)) {
32978        return false;
32979      }
32980
32981      return (playlist.attributes.BANDWIDTH || 0) < currentBandwidth;
32982    }).length === 0;
32983  };
32984
32985  // exports
32986  var Playlist = {
32987    duration: duration,
32988    seekable: seekable,
32989    safeLiveIndex: safeLiveIndex,
32990    getMediaInfoForTime: getMediaInfoForTime,
32991    isEnabled: isEnabled,
32992    isDisabled: isDisabled,
32993    isBlacklisted: isBlacklisted,
32994    isIncompatible: isIncompatible,
32995    playlistEnd: playlistEnd,
32996    isAes: isAes,
32997    isFmp4: isFmp4,
32998    hasAttribute: hasAttribute,
32999    estimateSegmentRequestTime: estimateSegmentRequestTime,
33000    isLowestEnabledRendition: isLowestEnabledRendition
33001  };
33002
33003  /**
33004   * @file xhr.js
33005   */
33006
33007  var videojsXHR = videojs$1.xhr,
33008      mergeOptions$1$1 = videojs$1.mergeOptions;
33009
33010  var xhrFactory = function xhrFactory() {
33011    var xhr = function XhrFunction(options, callback) {
33012      // Add a default timeout for all hls requests
33013      options = mergeOptions$1$1({
33014        timeout: 45e3
33015      }, options);
33016
33017      // Allow an optional user-specified function to modify the option
33018      // object before we construct the xhr request
33019      var beforeRequest = XhrFunction.beforeRequest || videojs$1.Hls.xhr.beforeRequest;
33020
33021      if (beforeRequest && typeof beforeRequest === 'function') {
33022        var newOptions = beforeRequest(options);
33023
33024        if (newOptions) {
33025          options = newOptions;
33026        }
33027      }
33028
33029      var request = videojsXHR(options, function (error, response) {
33030        var reqResponse = request.response;
33031
33032        if (!error && reqResponse) {
33033          request.responseTime = Date.now();
33034          request.roundTripTime = request.responseTime - request.requestTime;
33035          request.bytesReceived = reqResponse.byteLength || reqResponse.length;
33036          if (!request.bandwidth) {
33037            request.bandwidth = Math.floor(request.bytesReceived / request.roundTripTime * 8 * 1000);
33038          }
33039        }
33040
33041        if (response.headers) {
33042          request.responseHeaders = response.headers;
33043        }
33044
33045        // videojs.xhr now uses a specific code on the error
33046        // object to signal that a request has timed out instead
33047        // of setting a boolean on the request object
33048        if (error && error.code === 'ETIMEDOUT') {
33049          request.timedout = true;
33050        }
33051
33052        // videojs.xhr no longer considers status codes outside of 200 and 0
33053        // (for file uris) to be errors, but the old XHR did, so emulate that
33054        // behavior. Status 206 may be used in response to byterange requests.
33055        if (!error && !request.aborted && response.statusCode !== 200 && response.statusCode !== 206 && response.statusCode !== 0) {
33056          error = new Error('XHR Failed with a response of: ' + (request && (reqResponse || request.responseText)));
33057        }
33058
33059        callback(error, request);
33060      });
33061      var originalAbort = request.abort;
33062
33063      request.abort = function () {
33064        request.aborted = true;
33065        return originalAbort.apply(request, arguments);
33066      };
33067      request.uri = options.uri;
33068      request.requestTime = Date.now();
33069      return request;
33070    };
33071
33072    return xhr;
33073  };
33074
33075  /**
33076   * @file bin-utils.js
33077   */
33078
33079  /**
33080   * convert a TimeRange to text
33081   *
33082   * @param {TimeRange} range the timerange to use for conversion
33083   * @param {Number} i the iterator on the range to convert
33084   */
33085  var textRange = function textRange(range, i) {
33086    return range.start(i) + '-' + range.end(i);
33087  };
33088
33089  /**
33090   * format a number as hex string
33091   *
33092   * @param {Number} e The number
33093   * @param {Number} i the iterator
33094   */
33095  var formatHexString = function formatHexString(e, i) {
33096    var value = e.toString(16);
33097
33098    return '00'.substring(0, 2 - value.length) + value + (i % 2 ? ' ' : '');
33099  };
33100  var formatAsciiString = function formatAsciiString(e) {
33101    if (e >= 0x20 && e < 0x7e) {
33102      return String.fromCharCode(e);
33103    }
33104    return '.';
33105  };
33106
33107  /**
33108   * Creates an object for sending to a web worker modifying properties that are TypedArrays
33109   * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
33110   *
33111   * @param {Object} message
33112   *        Object of properties and values to send to the web worker
33113   * @return {Object}
33114   *         Modified message with TypedArray values expanded
33115   * @function createTransferableMessage
33116   */
33117  var createTransferableMessage = function createTransferableMessage(message) {
33118    var transferable = {};
33119
33120    Object.keys(message).forEach(function (key) {
33121      var value = message[key];
33122
33123      if (ArrayBuffer.isView(value)) {
33124        transferable[key] = {
33125          bytes: value.buffer,
33126          byteOffset: value.byteOffset,
33127          byteLength: value.byteLength
33128        };
33129      } else {
33130        transferable[key] = value;
33131      }
33132    });
33133
33134    return transferable;
33135  };
33136
33137  /**
33138   * Returns a unique string identifier for a media initialization
33139   * segment.
33140   */
33141  var initSegmentId = function initSegmentId(initSegment) {
33142    var byterange = initSegment.byterange || {
33143      length: Infinity,
33144      offset: 0
33145    };
33146
33147    return [byterange.length, byterange.offset, initSegment.resolvedUri].join(',');
33148  };
33149
33150  /**
33151   * utils to help dump binary data to the console
33152   */
33153  var hexDump = function hexDump(data) {
33154    var bytes = Array.prototype.slice.call(data);
33155    var step = 16;
33156    var result = '';
33157    var hex = void 0;
33158    var ascii = void 0;
33159
33160    for (var j = 0; j < bytes.length / step; j++) {
33161      hex = bytes.slice(j * step, j * step + step).map(formatHexString).join('');
33162      ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join('');
33163      result += hex + ' ' + ascii + '\n';
33164    }
33165
33166    return result;
33167  };
33168
33169  var tagDump = function tagDump(_ref) {
33170    var bytes = _ref.bytes;
33171    return hexDump(bytes);
33172  };
33173
33174  var textRanges = function textRanges(ranges) {
33175    var result = '';
33176    var i = void 0;
33177
33178    for (i = 0; i < ranges.length; i++) {
33179      result += textRange(ranges, i) + ' ';
33180    }
33181    return result;
33182  };
33183
33184  var utils = /*#__PURE__*/Object.freeze({
33185    createTransferableMessage: createTransferableMessage,
33186    initSegmentId: initSegmentId,
33187    hexDump: hexDump,
33188    tagDump: tagDump,
33189    textRanges: textRanges
33190  });
33191
33192  /**
33193   * ranges
33194   *
33195   * Utilities for working with TimeRanges.
33196   *
33197   */
33198
33199  // Fudge factor to account for TimeRanges rounding
33200  var TIME_FUDGE_FACTOR = 1 / 30;
33201  // Comparisons between time values such as current time and the end of the buffered range
33202  // can be misleading because of precision differences or when the current media has poorly
33203  // aligned audio and video, which can cause values to be slightly off from what you would
33204  // expect. This value is what we consider to be safe to use in such comparisons to account
33205  // for these scenarios.
33206  var SAFE_TIME_DELTA = TIME_FUDGE_FACTOR * 3;
33207  var filterRanges = function filterRanges(timeRanges, predicate) {
33208    var results = [];
33209    var i = void 0;
33210
33211    if (timeRanges && timeRanges.length) {
33212      // Search for ranges that match the predicate
33213      for (i = 0; i < timeRanges.length; i++) {
33214        if (predicate(timeRanges.start(i), timeRanges.end(i))) {
33215          results.push([timeRanges.start(i), timeRanges.end(i)]);
33216        }
33217      }
33218    }
33219
33220    return videojs$1.createTimeRanges(results);
33221  };
33222
33223  /**
33224   * Attempts to find the buffered TimeRange that contains the specified
33225   * time.
33226   * @param {TimeRanges} buffered - the TimeRanges object to query
33227   * @param {number} time  - the time to filter on.
33228   * @returns {TimeRanges} a new TimeRanges object
33229   */
33230  var findRange = function findRange(buffered, time) {
33231    return filterRanges(buffered, function (start, end) {
33232      return start - TIME_FUDGE_FACTOR <= time && end + TIME_FUDGE_FACTOR >= time;
33233    });
33234  };
33235
33236  /**
33237   * Returns the TimeRanges that begin later than the specified time.
33238   * @param {TimeRanges} timeRanges - the TimeRanges object to query
33239   * @param {number} time - the time to filter on.
33240   * @returns {TimeRanges} a new TimeRanges object.
33241   */
33242  var findNextRange = function findNextRange(timeRanges, time) {
33243    return filterRanges(timeRanges, function (start) {
33244      return start - TIME_FUDGE_FACTOR >= time;
33245    });
33246  };
33247
33248  /**
33249   * Returns gaps within a list of TimeRanges
33250   * @param {TimeRanges} buffered - the TimeRanges object
33251   * @return {TimeRanges} a TimeRanges object of gaps
33252   */
33253  var findGaps = function findGaps(buffered) {
33254    if (buffered.length < 2) {
33255      return videojs$1.createTimeRanges();
33256    }
33257
33258    var ranges = [];
33259
33260    for (var i = 1; i < buffered.length; i++) {
33261      var start = buffered.end(i - 1);
33262      var end = buffered.start(i);
33263
33264      ranges.push([start, end]);
33265    }
33266
33267    return videojs$1.createTimeRanges(ranges);
33268  };
33269
33270  /**
33271   * Gets a human readable string for a TimeRange
33272   *
33273   * @param {TimeRange} range
33274   * @returns {String} a human readable string
33275   */
33276  var printableRange = function printableRange(range) {
33277    var strArr = [];
33278
33279    if (!range || !range.length) {
33280      return '';
33281    }
33282
33283    for (var i = 0; i < range.length; i++) {
33284      strArr.push(range.start(i) + ' => ' + range.end(i));
33285    }
33286
33287    return strArr.join(', ');
33288  };
33289
33290  /**
33291   * Calculates the amount of time left in seconds until the player hits the end of the
33292   * buffer and causes a rebuffer
33293   *
33294   * @param {TimeRange} buffered
33295   *        The state of the buffer
33296   * @param {Numnber} currentTime
33297   *        The current time of the player
33298   * @param {Number} playbackRate
33299   *        The current playback rate of the player. Defaults to 1.
33300   * @return {Number}
33301   *         Time until the player has to start rebuffering in seconds.
33302   * @function timeUntilRebuffer
33303   */
33304  var timeUntilRebuffer = function timeUntilRebuffer(buffered, currentTime) {
33305    var playbackRate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
33306
33307    var bufferedEnd = buffered.length ? buffered.end(buffered.length - 1) : 0;
33308
33309    return (bufferedEnd - currentTime) / playbackRate;
33310  };
33311
33312  /**
33313   * Converts a TimeRanges object into an array representation
33314   * @param {TimeRanges} timeRanges
33315   * @returns {Array}
33316   */
33317  var timeRangesToArray = function timeRangesToArray(timeRanges) {
33318    var timeRangesList = [];
33319
33320    for (var i = 0; i < timeRanges.length; i++) {
33321      timeRangesList.push({
33322        start: timeRanges.start(i),
33323        end: timeRanges.end(i)
33324      });
33325    }
33326
33327    return timeRangesList;
33328  };
33329
33330  /**
33331   * @file create-text-tracks-if-necessary.js
33332   */
33333
33334  /**
33335   * Create text tracks on video.js if they exist on a segment.
33336   *
33337   * @param {Object} sourceBuffer the VSB or FSB
33338   * @param {Object} mediaSource the HTML media source
33339   * @param {Object} segment the segment that may contain the text track
33340   * @private
33341   */
33342  var createTextTracksIfNecessary = function createTextTracksIfNecessary(sourceBuffer, mediaSource, segment) {
33343    var player = mediaSource.player_;
33344
33345    // create an in-band caption track if one is present in the segment
33346    if (segment.captions && segment.captions.length) {
33347      if (!sourceBuffer.inbandTextTracks_) {
33348        sourceBuffer.inbandTextTracks_ = {};
33349      }
33350
33351      for (var trackId in segment.captionStreams) {
33352        if (!sourceBuffer.inbandTextTracks_[trackId]) {
33353          player.tech_.trigger({ type: 'usage', name: 'hls-608' });
33354          var track = player.textTracks().getTrackById(trackId);
33355
33356          if (track) {
33357            // Resuse an existing track with a CC# id because this was
33358            // very likely created by videojs-contrib-hls from information
33359            // in the m3u8 for us to use
33360            sourceBuffer.inbandTextTracks_[trackId] = track;
33361          } else {
33362            // Otherwise, create a track with the default `CC#` label and
33363            // without a language
33364            sourceBuffer.inbandTextTracks_[trackId] = player.addRemoteTextTrack({
33365              kind: 'captions',
33366              id: trackId,
33367              label: trackId
33368            }, false).track;
33369          }
33370        }
33371      }
33372    }
33373
33374    if (segment.metadata && segment.metadata.length && !sourceBuffer.metadataTrack_) {
33375      sourceBuffer.metadataTrack_ = player.addRemoteTextTrack({
33376        kind: 'metadata',
33377        label: 'Timed Metadata'
33378      }, false).track;
33379      sourceBuffer.metadataTrack_.inBandMetadataTrackDispatchType = segment.metadata.dispatchType;
33380    }
33381  };
33382
33383  /**
33384   * @file remove-cues-from-track.js
33385   */
33386
33387  /**
33388   * Remove cues from a track on video.js.
33389   *
33390   * @param {Double} start start of where we should remove the cue
33391   * @param {Double} end end of where the we should remove the cue
33392   * @param {Object} track the text track to remove the cues from
33393   * @private
33394   */
33395  var removeCuesFromTrack = function removeCuesFromTrack(start, end, track) {
33396    var i = void 0;
33397    var cue = void 0;
33398
33399    if (!track) {
33400      return;
33401    }
33402
33403    if (!track.cues) {
33404      return;
33405    }
33406
33407    i = track.cues.length;
33408
33409    while (i--) {
33410      cue = track.cues[i];
33411
33412      // Remove any overlapping cue
33413      if (cue.startTime <= end && cue.endTime >= start) {
33414        track.removeCue(cue);
33415      }
33416    }
33417  };
33418
33419  /**
33420   * @file add-text-track-data.js
33421   */
33422  /**
33423   * Define properties on a cue for backwards compatability,
33424   * but warn the user that the way that they are using it
33425   * is depricated and will be removed at a later date.
33426   *
33427   * @param {Cue} cue the cue to add the properties on
33428   * @private
33429   */
33430  var deprecateOldCue = function deprecateOldCue(cue) {
33431    Object.defineProperties(cue.frame, {
33432      id: {
33433        get: function get$$1() {
33434          videojs$1.log.warn('cue.frame.id is deprecated. Use cue.value.key instead.');
33435          return cue.value.key;
33436        }
33437      },
33438      value: {
33439        get: function get$$1() {
33440          videojs$1.log.warn('cue.frame.value is deprecated. Use cue.value.data instead.');
33441          return cue.value.data;
33442        }
33443      },
33444      privateData: {
33445        get: function get$$1() {
33446          videojs$1.log.warn('cue.frame.privateData is deprecated. Use cue.value.data instead.');
33447          return cue.value.data;
33448        }
33449      }
33450    });
33451  };
33452
33453  var durationOfVideo = function durationOfVideo(duration) {
33454    var dur = void 0;
33455
33456    if (isNaN(duration) || Math.abs(duration) === Infinity) {
33457      dur = Number.MAX_VALUE;
33458    } else {
33459      dur = duration;
33460    }
33461    return dur;
33462  };
33463  /**
33464   * Add text track data to a source handler given the captions and
33465   * metadata from the buffer.
33466   *
33467   * @param {Object} sourceHandler the virtual source buffer
33468   * @param {Array} captionArray an array of caption data
33469   * @param {Array} metadataArray an array of meta data
33470   * @private
33471   */
33472  var addTextTrackData = function addTextTrackData(sourceHandler, captionArray, metadataArray) {
33473    var Cue = window_1.WebKitDataCue || window_1.VTTCue;
33474
33475    if (captionArray) {
33476      captionArray.forEach(function (caption) {
33477        var track = caption.stream;
33478
33479        this.inbandTextTracks_[track].addCue(new Cue(caption.startTime + this.timestampOffset, caption.endTime + this.timestampOffset, caption.text));
33480      }, sourceHandler);
33481    }
33482
33483    if (metadataArray) {
33484      var videoDuration = durationOfVideo(sourceHandler.mediaSource_.duration);
33485
33486      metadataArray.forEach(function (metadata) {
33487        var time = metadata.cueTime + this.timestampOffset;
33488
33489        metadata.frames.forEach(function (frame) {
33490          var cue = new Cue(time, time, frame.value || frame.url || frame.data || '');
33491
33492          cue.frame = frame;
33493          cue.value = frame;
33494          deprecateOldCue(cue);
33495
33496          this.metadataTrack_.addCue(cue);
33497        }, this);
33498      }, sourceHandler);
33499
33500      // Updating the metadeta cues so that
33501      // the endTime of each cue is the startTime of the next cue
33502      // the endTime of last cue is the duration of the video
33503      if (sourceHandler.metadataTrack_ && sourceHandler.metadataTrack_.cues && sourceHandler.metadataTrack_.cues.length) {
33504        var cues = sourceHandler.metadataTrack_.cues;
33505        var cuesArray = [];
33506
33507        // Create a copy of the TextTrackCueList...
33508        // ...disregarding cues with a falsey value
33509        for (var i = 0; i < cues.length; i++) {
33510          if (cues[i]) {
33511            cuesArray.push(cues[i]);
33512          }
33513        }
33514
33515        // Group cues by their startTime value
33516        var cuesGroupedByStartTime = cuesArray.reduce(function (obj, cue) {
33517          var timeSlot = obj[cue.startTime] || [];
33518
33519          timeSlot.push(cue);
33520          obj[cue.startTime] = timeSlot;
33521
33522          return obj;
33523        }, {});
33524
33525        // Sort startTimes by ascending order
33526        var sortedStartTimes = Object.keys(cuesGroupedByStartTime).sort(function (a, b) {
33527          return Number(a) - Number(b);
33528        });
33529
33530        // Map each cue group's endTime to the next group's startTime
33531        sortedStartTimes.forEach(function (startTime, idx) {
33532          var cueGroup = cuesGroupedByStartTime[startTime];
33533          var nextTime = Number(sortedStartTimes[idx + 1]) || videoDuration;
33534
33535          // Map each cue's endTime the next group's startTime
33536          cueGroup.forEach(function (cue) {
33537            cue.endTime = nextTime;
33538          });
33539        });
33540      }
33541    }
33542  };
33543
33544  var win$1 = typeof window !== 'undefined' ? window : {},
33545      TARGET = typeof Symbol === 'undefined' ? '__target' : Symbol(),
33546      SCRIPT_TYPE = 'application/javascript',
33547      BlobBuilder = win$1.BlobBuilder || win$1.WebKitBlobBuilder || win$1.MozBlobBuilder || win$1.MSBlobBuilder,
33548      URL = win$1.URL || win$1.webkitURL || URL && URL.msURL,
33549      Worker = win$1.Worker;
33550
33551  /**
33552   * Returns a wrapper around Web Worker code that is constructible.
33553   *
33554   * @function shimWorker
33555   *
33556   * @param { String }    filename    The name of the file
33557   * @param { Function }  fn          Function wrapping the code of the worker
33558   */
33559  function shimWorker(filename, fn) {
33560    return function ShimWorker(forceFallback) {
33561      var o = this;
33562
33563      if (!fn) {
33564        return new Worker(filename);
33565      } else if (Worker && !forceFallback) {
33566        // Convert the function's inner code to a string to construct the worker
33567        var source = fn.toString().replace(/^function.+?{/, '').slice(0, -1),
33568            objURL = createSourceObject(source);
33569
33570        this[TARGET] = new Worker(objURL);
33571        wrapTerminate(this[TARGET], objURL);
33572        return this[TARGET];
33573      } else {
33574        var selfShim = {
33575          postMessage: function postMessage(m) {
33576            if (o.onmessage) {
33577              setTimeout(function () {
33578                o.onmessage({ data: m, target: selfShim });
33579              });
33580            }
33581          }
33582        };
33583
33584        fn.call(selfShim);
33585        this.postMessage = function (m) {
33586          setTimeout(function () {
33587            selfShim.onmessage({ data: m, target: o });
33588          });
33589        };
33590        this.isThisThread = true;
33591      }
33592    };
33593  }
33594  // Test Worker capabilities
33595  if (Worker) {
33596    var testWorker,
33597        objURL = createSourceObject('self.onmessage = function () {}'),
33598        testArray = new Uint8Array(1);
33599
33600    try {
33601      testWorker = new Worker(objURL);
33602
33603      // Native browser on some Samsung devices throws for transferables, let's detect it
33604      testWorker.postMessage(testArray, [testArray.buffer]);
33605    } catch (e) {
33606      Worker = null;
33607    } finally {
33608      URL.revokeObjectURL(objURL);
33609      if (testWorker) {
33610        testWorker.terminate();
33611      }
33612    }
33613  }
33614
33615  function createSourceObject(str) {
33616    try {
33617      return URL.createObjectURL(new Blob([str], { type: SCRIPT_TYPE }));
33618    } catch (e) {
33619      var blob = new BlobBuilder();
33620      blob.append(str);
33621      return URL.createObjectURL(blob.getBlob(type));
33622    }
33623  }
33624
33625  function wrapTerminate(worker, objURL) {
33626    if (!worker || !objURL) return;
33627    var term = worker.terminate;
33628    worker.objURL = objURL;
33629    worker.terminate = function () {
33630      if (worker.objURL) URL.revokeObjectURL(worker.objURL);
33631      term.call(worker);
33632    };
33633  }
33634
33635  var TransmuxWorker = new shimWorker("./transmuxer-worker.worker.js", function (window, document$$1) {
33636    var self = this;
33637    var transmuxerWorker = function () {
33638
33639      var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
33640
33641      var win;
33642
33643      if (typeof window !== "undefined") {
33644        win = window;
33645      } else if (typeof commonjsGlobal !== "undefined") {
33646        win = commonjsGlobal;
33647      } else if (typeof self !== "undefined") {
33648        win = self;
33649      } else {
33650        win = {};
33651      }
33652
33653      var window_1$$1 = win;
33654
33655      /**
33656       * mux.js
33657       *
33658       * Copyright (c) 2015 Brightcove
33659       * All rights reserved.
33660       *
33661       * Functions that generate fragmented MP4s suitable for use with Media
33662       * Source Extensions.
33663       */
33664
33665      var UINT32_MAX = Math.pow(2, 32) - 1;
33666
33667      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;
33668
33669      // pre-calculate constants
33670      (function () {
33671        var i;
33672        types = {
33673          avc1: [], // codingname
33674          avcC: [],
33675          btrt: [],
33676          dinf: [],
33677          dref: [],
33678          esds: [],
33679          ftyp: [],
33680          hdlr: [],
33681          mdat: [],
33682          mdhd: [],
33683          mdia: [],
33684          mfhd: [],
33685          minf: [],
33686          moof: [],
33687          moov: [],
33688          mp4a: [], // codingname
33689          mvex: [],
33690          mvhd: [],
33691          sdtp: [],
33692          smhd: [],
33693          stbl: [],
33694          stco: [],
33695          stsc: [],
33696          stsd: [],
33697          stsz: [],
33698          stts: [],
33699          styp: [],
33700          tfdt: [],
33701          tfhd: [],
33702          traf: [],
33703          trak: [],
33704          trun: [],
33705          trex: [],
33706          tkhd: [],
33707          vmhd: []
33708        };
33709
33710        // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we
33711        // don't throw an error
33712        if (typeof Uint8Array === 'undefined') {
33713          return;
33714        }
33715
33716        for (i in types) {
33717          if (types.hasOwnProperty(i)) {
33718            types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)];
33719          }
33720        }
33721
33722        MAJOR_BRAND = new Uint8Array(['i'.charCodeAt(0), 's'.charCodeAt(0), 'o'.charCodeAt(0), 'm'.charCodeAt(0)]);
33723        AVC1_BRAND = new Uint8Array(['a'.charCodeAt(0), 'v'.charCodeAt(0), 'c'.charCodeAt(0), '1'.charCodeAt(0)]);
33724        MINOR_VERSION = new Uint8Array([0, 0, 0, 1]);
33725        VIDEO_HDLR = new Uint8Array([0x00, // version 0
33726        0x00, 0x00, 0x00, // flags
33727        0x00, 0x00, 0x00, 0x00, // pre_defined
33728        0x76, 0x69, 0x64, 0x65, // handler_type: 'vide'
33729        0x00, 0x00, 0x00, 0x00, // reserved
33730        0x00, 0x00, 0x00, 0x00, // reserved
33731        0x00, 0x00, 0x00, 0x00, // reserved
33732        0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
33733        ]);
33734        AUDIO_HDLR = new Uint8Array([0x00, // version 0
33735        0x00, 0x00, 0x00, // flags
33736        0x00, 0x00, 0x00, 0x00, // pre_defined
33737        0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun'
33738        0x00, 0x00, 0x00, 0x00, // reserved
33739        0x00, 0x00, 0x00, 0x00, // reserved
33740        0x00, 0x00, 0x00, 0x00, // reserved
33741        0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'
33742        ]);
33743        HDLR_TYPES = {
33744          video: VIDEO_HDLR,
33745          audio: AUDIO_HDLR
33746        };
33747        DREF = new Uint8Array([0x00, // version 0
33748        0x00, 0x00, 0x00, // flags
33749        0x00, 0x00, 0x00, 0x01, // entry_count
33750        0x00, 0x00, 0x00, 0x0c, // entry_size
33751        0x75, 0x72, 0x6c, 0x20, // 'url' type
33752        0x00, // version 0
33753        0x00, 0x00, 0x01 // entry_flags
33754        ]);
33755        SMHD = new Uint8Array([0x00, // version
33756        0x00, 0x00, 0x00, // flags
33757        0x00, 0x00, // balance, 0 means centered
33758        0x00, 0x00 // reserved
33759        ]);
33760        STCO = new Uint8Array([0x00, // version
33761        0x00, 0x00, 0x00, // flags
33762        0x00, 0x00, 0x00, 0x00 // entry_count
33763        ]);
33764        STSC = STCO;
33765        STSZ = new Uint8Array([0x00, // version
33766        0x00, 0x00, 0x00, // flags
33767        0x00, 0x00, 0x00, 0x00, // sample_size
33768        0x00, 0x00, 0x00, 0x00 // sample_count
33769        ]);
33770        STTS = STCO;
33771        VMHD = new Uint8Array([0x00, // version
33772        0x00, 0x00, 0x01, // flags
33773        0x00, 0x00, // graphicsmode
33774        0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor
33775        ]);
33776      })();
33777
33778      box = function box(type) {
33779        var payload = [],
33780            size = 0,
33781            i,
33782            result,
33783            view;
33784
33785        for (i = 1; i < arguments.length; i++) {
33786          payload.push(arguments[i]);
33787        }
33788
33789        i = payload.length;
33790
33791        // calculate the total size we need to allocate
33792        while (i--) {
33793          size += payload[i].byteLength;
33794        }
33795        result = new Uint8Array(size + 8);
33796        view = new DataView(result.buffer, result.byteOffset, result.byteLength);
33797        view.setUint32(0, result.byteLength);
33798        result.set(type, 4);
33799
33800        // copy the payload into the result
33801        for (i = 0, size = 8; i < payload.length; i++) {
33802          result.set(payload[i], size);
33803          size += payload[i].byteLength;
33804        }
33805        return result;
33806      };
33807
33808      dinf = function dinf() {
33809        return box(types.dinf, box(types.dref, DREF));
33810      };
33811
33812      esds = function esds(track) {
33813        return box(types.esds, new Uint8Array([0x00, // version
33814        0x00, 0x00, 0x00, // flags
33815
33816        // ES_Descriptor
33817        0x03, // tag, ES_DescrTag
33818        0x19, // length
33819        0x00, 0x00, // ES_ID
33820        0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority
33821
33822        // DecoderConfigDescriptor
33823        0x04, // tag, DecoderConfigDescrTag
33824        0x11, // length
33825        0x40, // object type
33826        0x15, // streamType
33827        0x00, 0x06, 0x00, // bufferSizeDB
33828        0x00, 0x00, 0xda, 0xc0, // maxBitrate
33829        0x00, 0x00, 0xda, 0xc0, // avgBitrate
33830
33831        // DecoderSpecificInfo
33832        0x05, // tag, DecoderSpecificInfoTag
33833        0x02, // length
33834        // ISO/IEC 14496-3, AudioSpecificConfig
33835        // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35
33836        track.audioobjecttype << 3 | track.samplingfrequencyindex >>> 1, track.samplingfrequencyindex << 7 | track.channelcount << 3, 0x06, 0x01, 0x02 // GASpecificConfig
33837        ]));
33838      };
33839
33840      ftyp = function ftyp() {
33841        return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND);
33842      };
33843
33844      hdlr = function hdlr(type) {
33845        return box(types.hdlr, HDLR_TYPES[type]);
33846      };
33847      mdat = function mdat(data) {
33848        return box(types.mdat, data);
33849      };
33850      mdhd = function mdhd(track) {
33851        var result = new Uint8Array([0x00, // version 0
33852        0x00, 0x00, 0x00, // flags
33853        0x00, 0x00, 0x00, 0x02, // creation_time
33854        0x00, 0x00, 0x00, 0x03, // modification_time
33855        0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
33856
33857        track.duration >>> 24 & 0xFF, track.duration >>> 16 & 0xFF, track.duration >>> 8 & 0xFF, track.duration & 0xFF, // duration
33858        0x55, 0xc4, // 'und' language (undetermined)
33859        0x00, 0x00]);
33860
33861        // Use the sample rate from the track metadata, when it is
33862        // defined. The sample rate can be parsed out of an ADTS header, for
33863        // instance.
33864        if (track.samplerate) {
33865          result[12] = track.samplerate >>> 24 & 0xFF;
33866          result[13] = track.samplerate >>> 16 & 0xFF;
33867          result[14] = track.samplerate >>> 8 & 0xFF;
33868          result[15] = track.samplerate & 0xFF;
33869        }
33870
33871        return box(types.mdhd, result);
33872      };
33873      mdia = function mdia(track) {
33874        return box(types.mdia, mdhd(track), hdlr(track.type), minf(track));
33875      };
33876      mfhd = function mfhd(sequenceNumber) {
33877        return box(types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // flags
33878        (sequenceNumber & 0xFF000000) >> 24, (sequenceNumber & 0xFF0000) >> 16, (sequenceNumber & 0xFF00) >> 8, sequenceNumber & 0xFF // sequence_number
33879        ]));
33880      };
33881      minf = function minf(track) {
33882        return box(types.minf, track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), dinf(), stbl(track));
33883      };
33884      moof = function moof(sequenceNumber, tracks) {
33885        var trackFragments = [],
33886            i = tracks.length;
33887        // build traf boxes for each track fragment
33888        while (i--) {
33889          trackFragments[i] = traf(tracks[i]);
33890        }
33891        return box.apply(null, [types.moof, mfhd(sequenceNumber)].concat(trackFragments));
33892      };
33893      /**
33894       * Returns a movie box.
33895       * @param tracks {array} the tracks associated with this movie
33896       * @see ISO/IEC 14496-12:2012(E), section 8.2.1
33897       */
33898      moov = function moov(tracks) {
33899        var i = tracks.length,
33900            boxes = [];
33901
33902        while (i--) {
33903          boxes[i] = trak(tracks[i]);
33904        }
33905
33906        return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks)));
33907      };
33908      mvex = function mvex(tracks) {
33909        var i = tracks.length,
33910            boxes = [];
33911
33912        while (i--) {
33913          boxes[i] = trex(tracks[i]);
33914        }
33915        return box.apply(null, [types.mvex].concat(boxes));
33916      };
33917      mvhd = function mvhd(duration) {
33918        var bytes = new Uint8Array([0x00, // version 0
33919        0x00, 0x00, 0x00, // flags
33920        0x00, 0x00, 0x00, 0x01, // creation_time
33921        0x00, 0x00, 0x00, 0x02, // modification_time
33922        0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
33923        (duration & 0xFF000000) >> 24, (duration & 0xFF0000) >> 16, (duration & 0xFF00) >> 8, duration & 0xFF, // duration
33924        0x00, 0x01, 0x00, 0x00, // 1.0 rate
33925        0x01, 0x00, // 1.0 volume
33926        0x00, 0x00, // reserved
33927        0x00, 0x00, 0x00, 0x00, // reserved
33928        0x00, 0x00, 0x00, 0x00, // reserved
33929        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
33930        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
33931        0xff, 0xff, 0xff, 0xff // next_track_ID
33932        ]);
33933        return box(types.mvhd, bytes);
33934      };
33935
33936      sdtp = function sdtp(track) {
33937        var samples = track.samples || [],
33938            bytes = new Uint8Array(4 + samples.length),
33939            flags,
33940            i;
33941
33942        // leave the full box header (4 bytes) all zero
33943
33944        // write the sample table
33945        for (i = 0; i < samples.length; i++) {
33946          flags = samples[i].flags;
33947
33948          bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy;
33949        }
33950
33951        return box(types.sdtp, bytes);
33952      };
33953
33954      stbl = function stbl(track) {
33955        return box(types.stbl, stsd(track), box(types.stts, STTS), box(types.stsc, STSC), box(types.stsz, STSZ), box(types.stco, STCO));
33956      };
33957
33958      (function () {
33959        var videoSample, audioSample;
33960
33961        stsd = function stsd(track) {
33962
33963          return box(types.stsd, new Uint8Array([0x00, // version 0
33964          0x00, 0x00, 0x00, // flags
33965          0x00, 0x00, 0x00, 0x01]), track.type === 'video' ? videoSample(track) : audioSample(track));
33966        };
33967
33968        videoSample = function videoSample(track) {
33969          var sps = track.sps || [],
33970              pps = track.pps || [],
33971              sequenceParameterSets = [],
33972              pictureParameterSets = [],
33973              i;
33974
33975          // assemble the SPSs
33976          for (i = 0; i < sps.length; i++) {
33977            sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8);
33978            sequenceParameterSets.push(sps[i].byteLength & 0xFF); // sequenceParameterSetLength
33979            sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS
33980          }
33981
33982          // assemble the PPSs
33983          for (i = 0; i < pps.length; i++) {
33984            pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8);
33985            pictureParameterSets.push(pps[i].byteLength & 0xFF);
33986            pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i]));
33987          }
33988
33989          return box(types.avc1, new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
33990          0x00, 0x01, // data_reference_index
33991          0x00, 0x00, // pre_defined
33992          0x00, 0x00, // reserved
33993          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined
33994          (track.width & 0xff00) >> 8, track.width & 0xff, // width
33995          (track.height & 0xff00) >> 8, track.height & 0xff, // height
33996          0x00, 0x48, 0x00, 0x00, // horizresolution
33997          0x00, 0x48, 0x00, 0x00, // vertresolution
33998          0x00, 0x00, 0x00, 0x00, // reserved
33999          0x00, 0x01, // frame_count
34000          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
34001          0x00, 0x18, // depth = 24
34002          0x11, 0x11 // pre_defined = -1
34003          ]), box(types.avcC, new Uint8Array([0x01, // configurationVersion
34004          track.profileIdc, // AVCProfileIndication
34005          track.profileCompatibility, // profile_compatibility
34006          track.levelIdc, // AVCLevelIndication
34007          0xff // lengthSizeMinusOne, hard-coded to 4 bytes
34008          ].concat([sps.length // numOfSequenceParameterSets
34009          ]).concat(sequenceParameterSets).concat([pps.length // numOfPictureParameterSets
34010          ]).concat(pictureParameterSets))), // "PPS"
34011          box(types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB
34012          0x00, 0x2d, 0xc6, 0xc0, // maxBitrate
34013          0x00, 0x2d, 0xc6, 0xc0])) // avgBitrate
34014          );
34015        };
34016
34017        audioSample = function audioSample(track) {
34018          return box(types.mp4a, new Uint8Array([
34019
34020          // SampleEntry, ISO/IEC 14496-12
34021          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
34022          0x00, 0x01, // data_reference_index
34023
34024          // AudioSampleEntry, ISO/IEC 14496-12
34025          0x00, 0x00, 0x00, 0x00, // reserved
34026          0x00, 0x00, 0x00, 0x00, // reserved
34027          (track.channelcount & 0xff00) >> 8, track.channelcount & 0xff, // channelcount
34028
34029          (track.samplesize & 0xff00) >> 8, track.samplesize & 0xff, // samplesize
34030          0x00, 0x00, // pre_defined
34031          0x00, 0x00, // reserved
34032
34033          (track.samplerate & 0xff00) >> 8, track.samplerate & 0xff, 0x00, 0x00 // samplerate, 16.16
34034
34035          // MP4AudioSampleEntry, ISO/IEC 14496-14
34036          ]), esds(track));
34037        };
34038      })();
34039
34040      tkhd = function tkhd(track) {
34041        var result = new Uint8Array([0x00, // version 0
34042        0x00, 0x00, 0x07, // flags
34043        0x00, 0x00, 0x00, 0x00, // creation_time
34044        0x00, 0x00, 0x00, 0x00, // modification_time
34045        (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
34046        0x00, 0x00, 0x00, 0x00, // reserved
34047        (track.duration & 0xFF000000) >> 24, (track.duration & 0xFF0000) >> 16, (track.duration & 0xFF00) >> 8, track.duration & 0xFF, // duration
34048        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
34049        0x00, 0x00, // layer
34050        0x00, 0x00, // alternate_group
34051        0x01, 0x00, // non-audio track volume
34052        0x00, 0x00, // reserved
34053        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
34054        (track.width & 0xFF00) >> 8, track.width & 0xFF, 0x00, 0x00, // width
34055        (track.height & 0xFF00) >> 8, track.height & 0xFF, 0x00, 0x00 // height
34056        ]);
34057
34058        return box(types.tkhd, result);
34059      };
34060
34061      /**
34062       * Generate a track fragment (traf) box. A traf box collects metadata
34063       * about tracks in a movie fragment (moof) box.
34064       */
34065      traf = function traf(track) {
34066        var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable, dataOffset, upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime;
34067
34068        trackFragmentHeader = box(types.tfhd, new Uint8Array([0x00, // version 0
34069        0x00, 0x00, 0x3a, // flags
34070        (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
34071        0x00, 0x00, 0x00, 0x01, // sample_description_index
34072        0x00, 0x00, 0x00, 0x00, // default_sample_duration
34073        0x00, 0x00, 0x00, 0x00, // default_sample_size
34074        0x00, 0x00, 0x00, 0x00 // default_sample_flags
34075        ]));
34076
34077        upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / (UINT32_MAX + 1));
34078        lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % (UINT32_MAX + 1));
34079
34080        trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([0x01, // version 1
34081        0x00, 0x00, 0x00, // flags
34082        // baseMediaDecodeTime
34083        upperWordBaseMediaDecodeTime >>> 24 & 0xFF, upperWordBaseMediaDecodeTime >>> 16 & 0xFF, upperWordBaseMediaDecodeTime >>> 8 & 0xFF, upperWordBaseMediaDecodeTime & 0xFF, lowerWordBaseMediaDecodeTime >>> 24 & 0xFF, lowerWordBaseMediaDecodeTime >>> 16 & 0xFF, lowerWordBaseMediaDecodeTime >>> 8 & 0xFF, lowerWordBaseMediaDecodeTime & 0xFF]));
34084
34085        // the data offset specifies the number of bytes from the start of
34086        // the containing moof to the first payload byte of the associated
34087        // mdat
34088        dataOffset = 32 + // tfhd
34089        20 + // tfdt
34090        8 + // traf header
34091        16 + // mfhd
34092        8 + // moof header
34093        8; // mdat header
34094
34095        // audio tracks require less metadata
34096        if (track.type === 'audio') {
34097          trackFragmentRun = trun(track, dataOffset);
34098          return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun);
34099        }
34100
34101        // video tracks should contain an independent and disposable samples
34102        // box (sdtp)
34103        // generate one and adjust offsets to match
34104        sampleDependencyTable = sdtp(track);
34105        trackFragmentRun = trun(track, sampleDependencyTable.length + dataOffset);
34106        return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable);
34107      };
34108
34109      /**
34110       * Generate a track box.
34111       * @param track {object} a track definition
34112       * @return {Uint8Array} the track box
34113       */
34114      trak = function trak(track) {
34115        track.duration = track.duration || 0xffffffff;
34116        return box(types.trak, tkhd(track), mdia(track));
34117      };
34118
34119      trex = function trex(track) {
34120        var result = new Uint8Array([0x00, // version 0
34121        0x00, 0x00, 0x00, // flags
34122        (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
34123        0x00, 0x00, 0x00, 0x01, // default_sample_description_index
34124        0x00, 0x00, 0x00, 0x00, // default_sample_duration
34125        0x00, 0x00, 0x00, 0x00, // default_sample_size
34126        0x00, 0x01, 0x00, 0x01 // default_sample_flags
34127        ]);
34128        // the last two bytes of default_sample_flags is the sample
34129        // degradation priority, a hint about the importance of this sample
34130        // relative to others. Lower the degradation priority for all sample
34131        // types other than video.
34132        if (track.type !== 'video') {
34133          result[result.length - 1] = 0x00;
34134        }
34135
34136        return box(types.trex, result);
34137      };
34138
34139      (function () {
34140        var audioTrun, videoTrun, trunHeader;
34141
34142        // This method assumes all samples are uniform. That is, if a
34143        // duration is present for the first sample, it will be present for
34144        // all subsequent samples.
34145        // see ISO/IEC 14496-12:2012, Section 8.8.8.1
34146        trunHeader = function trunHeader(samples, offset) {
34147          var durationPresent = 0,
34148              sizePresent = 0,
34149              flagsPresent = 0,
34150              compositionTimeOffset = 0;
34151
34152          // trun flag constants
34153          if (samples.length) {
34154            if (samples[0].duration !== undefined) {
34155              durationPresent = 0x1;
34156            }
34157            if (samples[0].size !== undefined) {
34158              sizePresent = 0x2;
34159            }
34160            if (samples[0].flags !== undefined) {
34161              flagsPresent = 0x4;
34162            }
34163            if (samples[0].compositionTimeOffset !== undefined) {
34164              compositionTimeOffset = 0x8;
34165            }
34166          }
34167
34168          return [0x00, // version 0
34169          0x00, durationPresent | sizePresent | flagsPresent | compositionTimeOffset, 0x01, // flags
34170          (samples.length & 0xFF000000) >>> 24, (samples.length & 0xFF0000) >>> 16, (samples.length & 0xFF00) >>> 8, samples.length & 0xFF, // sample_count
34171          (offset & 0xFF000000) >>> 24, (offset & 0xFF0000) >>> 16, (offset & 0xFF00) >>> 8, offset & 0xFF // data_offset
34172          ];
34173        };
34174
34175        videoTrun = function videoTrun(track, offset) {
34176          var bytes, samples, sample, i;
34177
34178          samples = track.samples || [];
34179          offset += 8 + 12 + 16 * samples.length;
34180
34181          bytes = trunHeader(samples, offset);
34182
34183          for (i = 0; i < samples.length; i++) {
34184            sample = samples[i];
34185            bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration
34186            (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF, // sample_size
34187            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
34188            (sample.compositionTimeOffset & 0xFF000000) >>> 24, (sample.compositionTimeOffset & 0xFF0000) >>> 16, (sample.compositionTimeOffset & 0xFF00) >>> 8, sample.compositionTimeOffset & 0xFF // sample_composition_time_offset
34189            ]);
34190          }
34191          return box(types.trun, new Uint8Array(bytes));
34192        };
34193
34194        audioTrun = function audioTrun(track, offset) {
34195          var bytes, samples, sample, i;
34196
34197          samples = track.samples || [];
34198          offset += 8 + 12 + 8 * samples.length;
34199
34200          bytes = trunHeader(samples, offset);
34201
34202          for (i = 0; i < samples.length; i++) {
34203            sample = samples[i];
34204            bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration
34205            (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF]); // sample_size
34206          }
34207
34208          return box(types.trun, new Uint8Array(bytes));
34209        };
34210
34211        trun = function trun(track, offset) {
34212          if (track.type === 'audio') {
34213            return audioTrun(track, offset);
34214          }
34215
34216          return videoTrun(track, offset);
34217        };
34218      })();
34219
34220      var mp4Generator = {
34221        ftyp: ftyp,
34222        mdat: mdat,
34223        moof: moof,
34224        moov: moov,
34225        initSegment: function initSegment(tracks) {
34226          var fileType = ftyp(),
34227              movie = moov(tracks),
34228              result;
34229
34230          result = new Uint8Array(fileType.byteLength + movie.byteLength);
34231          result.set(fileType);
34232          result.set(movie, fileType.byteLength);
34233          return result;
34234        }
34235      };
34236
34237      /**
34238       * mux.js
34239       *
34240       * Copyright (c) 2014 Brightcove
34241       * All rights reserved.
34242       *
34243       * A lightweight readable stream implemention that handles event dispatching.
34244       * Objects that inherit from streams should call init in their constructors.
34245       */
34246
34247      var Stream = function Stream() {
34248        this.init = function () {
34249          var listeners = {};
34250          /**
34251           * Add a listener for a specified event type.
34252           * @param type {string} the event name
34253           * @param listener {function} the callback to be invoked when an event of
34254           * the specified type occurs
34255           */
34256          this.on = function (type, listener) {
34257            if (!listeners[type]) {
34258              listeners[type] = [];
34259            }
34260            listeners[type] = listeners[type].concat(listener);
34261          };
34262          /**
34263           * Remove a listener for a specified event type.
34264           * @param type {string} the event name
34265           * @param listener {function} a function previously registered for this
34266           * type of event through `on`
34267           */
34268          this.off = function (type, listener) {
34269            var index;
34270            if (!listeners[type]) {
34271              return false;
34272            }
34273            index = listeners[type].indexOf(listener);
34274            listeners[type] = listeners[type].slice();
34275            listeners[type].splice(index, 1);
34276            return index > -1;
34277          };
34278          /**
34279           * Trigger an event of the specified type on this stream. Any additional
34280           * arguments to this function are passed as parameters to event listeners.
34281           * @param type {string} the event name
34282           */
34283          this.trigger = function (type) {
34284            var callbacks, i, length, args;
34285            callbacks = listeners[type];
34286            if (!callbacks) {
34287              return;
34288            }
34289            // Slicing the arguments on every invocation of this method
34290            // can add a significant amount of overhead. Avoid the
34291            // intermediate object creation for the common case of a
34292            // single callback argument
34293            if (arguments.length === 2) {
34294              length = callbacks.length;
34295              for (i = 0; i < length; ++i) {
34296                callbacks[i].call(this, arguments[1]);
34297              }
34298            } else {
34299              args = [];
34300              i = arguments.length;
34301              for (i = 1; i < arguments.length; ++i) {
34302                args.push(arguments[i]);
34303              }
34304              length = callbacks.length;
34305              for (i = 0; i < length; ++i) {
34306                callbacks[i].apply(this, args);
34307              }
34308            }
34309          };
34310          /**
34311           * Destroys the stream and cleans up.
34312           */
34313          this.dispose = function () {
34314            listeners = {};
34315          };
34316        };
34317      };
34318
34319      /**
34320       * Forwards all `data` events on this stream to the destination stream. The
34321       * destination stream should provide a method `push` to receive the data
34322       * events as they arrive.
34323       * @param destination {stream} the stream that will receive all `data` events
34324       * @param autoFlush {boolean} if false, we will not call `flush` on the destination
34325       *                            when the current stream emits a 'done' event
34326       * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
34327       */
34328      Stream.prototype.pipe = function (destination) {
34329        this.on('data', function (data) {
34330          destination.push(data);
34331        });
34332
34333        this.on('done', function (flushSource) {
34334          destination.flush(flushSource);
34335        });
34336
34337        return destination;
34338      };
34339
34340      // Default stream functions that are expected to be overridden to perform
34341      // actual work. These are provided by the prototype as a sort of no-op
34342      // implementation so that we don't have to check for their existence in the
34343      // `pipe` function above.
34344      Stream.prototype.push = function (data) {
34345        this.trigger('data', data);
34346      };
34347
34348      Stream.prototype.flush = function (flushSource) {
34349        this.trigger('done', flushSource);
34350      };
34351
34352      var stream = Stream;
34353
34354      // -----------------
34355      // Link To Transport
34356      // -----------------
34357
34358      // Supplemental enhancement information (SEI) NAL units have a
34359      // payload type field to indicate how they are to be
34360      // interpreted. CEAS-708 caption content is always transmitted with
34361      // payload type 0x04.
34362      var USER_DATA_REGISTERED_ITU_T_T35 = 4,
34363          RBSP_TRAILING_BITS = 128;
34364
34365      /**
34366        * Parse a supplemental enhancement information (SEI) NAL unit.
34367        * Stops parsing once a message of type ITU T T35 has been found.
34368        *
34369        * @param bytes {Uint8Array} the bytes of a SEI NAL unit
34370        * @return {object} the parsed SEI payload
34371        * @see Rec. ITU-T H.264, 7.3.2.3.1
34372        */
34373      var parseSei = function parseSei(bytes) {
34374        var i = 0,
34375            result = {
34376          payloadType: -1,
34377          payloadSize: 0
34378        },
34379            payloadType = 0,
34380            payloadSize = 0;
34381
34382        // go through the sei_rbsp parsing each each individual sei_message
34383        while (i < bytes.byteLength) {
34384          // stop once we have hit the end of the sei_rbsp
34385          if (bytes[i] === RBSP_TRAILING_BITS) {
34386            break;
34387          }
34388
34389          // Parse payload type
34390          while (bytes[i] === 0xFF) {
34391            payloadType += 255;
34392            i++;
34393          }
34394          payloadType += bytes[i++];
34395
34396          // Parse payload size
34397          while (bytes[i] === 0xFF) {
34398            payloadSize += 255;
34399            i++;
34400          }
34401          payloadSize += bytes[i++];
34402
34403          // this sei_message is a 608/708 caption so save it and break
34404          // there can only ever be one caption message in a frame's sei
34405          if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) {
34406            result.payloadType = payloadType;
34407            result.payloadSize = payloadSize;
34408            result.payload = bytes.subarray(i, i + payloadSize);
34409            break;
34410          }
34411
34412          // skip the payload and parse the next message
34413          i += payloadSize;
34414          payloadType = 0;
34415          payloadSize = 0;
34416        }
34417
34418        return result;
34419      };
34420
34421      // see ANSI/SCTE 128-1 (2013), section 8.1
34422      var parseUserData = function parseUserData(sei) {
34423        // itu_t_t35_contry_code must be 181 (United States) for
34424        // captions
34425        if (sei.payload[0] !== 181) {
34426          return null;
34427        }
34428
34429        // itu_t_t35_provider_code should be 49 (ATSC) for captions
34430        if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) {
34431          return null;
34432        }
34433
34434        // the user_identifier should be "GA94" to indicate ATSC1 data
34435        if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') {
34436          return null;
34437        }
34438
34439        // finally, user_data_type_code should be 0x03 for caption data
34440        if (sei.payload[7] !== 0x03) {
34441          return null;
34442        }
34443
34444        // return the user_data_type_structure and strip the trailing
34445        // marker bits
34446        return sei.payload.subarray(8, sei.payload.length - 1);
34447      };
34448
34449      // see CEA-708-D, section 4.4
34450      var parseCaptionPackets = function parseCaptionPackets(pts, userData) {
34451        var results = [],
34452            i,
34453            count,
34454            offset,
34455            data;
34456
34457        // if this is just filler, return immediately
34458        if (!(userData[0] & 0x40)) {
34459          return results;
34460        }
34461
34462        // parse out the cc_data_1 and cc_data_2 fields
34463        count = userData[0] & 0x1f;
34464        for (i = 0; i < count; i++) {
34465          offset = i * 3;
34466          data = {
34467            type: userData[offset + 2] & 0x03,
34468            pts: pts
34469          };
34470
34471          // capture cc data when cc_valid is 1
34472          if (userData[offset + 2] & 0x04) {
34473            data.ccData = userData[offset + 3] << 8 | userData[offset + 4];
34474            results.push(data);
34475          }
34476        }
34477        return results;
34478      };
34479
34480      var CaptionStream = function CaptionStream() {
34481
34482        CaptionStream.prototype.init.call(this);
34483
34484        this.captionPackets_ = [];
34485
34486        this.ccStreams_ = [new Cea608Stream(0, 0), // eslint-disable-line no-use-before-define
34487        new Cea608Stream(0, 1), // eslint-disable-line no-use-before-define
34488        new Cea608Stream(1, 0), // eslint-disable-line no-use-before-define
34489        new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define
34490        ];
34491
34492        this.reset();
34493
34494        // forward data and done events from CCs to this CaptionStream
34495        this.ccStreams_.forEach(function (cc) {
34496          cc.on('data', this.trigger.bind(this, 'data'));
34497          cc.on('done', this.trigger.bind(this, 'done'));
34498        }, this);
34499      };
34500
34501      CaptionStream.prototype = new stream();
34502      CaptionStream.prototype.push = function (event) {
34503        var sei, userData;
34504
34505        // only examine SEI NALs
34506        if (event.nalUnitType !== 'sei_rbsp') {
34507          return;
34508        }
34509
34510        // parse the sei
34511        sei = parseSei(event.escapedRBSP);
34512
34513        // ignore everything but user_data_registered_itu_t_t35
34514        if (sei.payloadType !== USER_DATA_REGISTERED_ITU_T_T35) {
34515          return;
34516        }
34517
34518        // parse out the user data payload
34519        userData = parseUserData(sei);
34520
34521        // ignore unrecognized userData
34522        if (!userData) {
34523          return;
34524        }
34525
34526        // Sometimes, the same segment # will be downloaded twice. To stop the
34527        // caption data from being processed twice, we track the latest dts we've
34528        // received and ignore everything with a dts before that. However, since
34529        // data for a specific dts can be split across packets on either side of
34530        // a segment boundary, we need to make sure we *don't* ignore the packets
34531        // from the *next* segment that have dts === this.latestDts_. By constantly
34532        // tracking the number of packets received with dts === this.latestDts_, we
34533        // know how many should be ignored once we start receiving duplicates.
34534        if (event.dts < this.latestDts_) {
34535          // We've started getting older data, so set the flag.
34536          this.ignoreNextEqualDts_ = true;
34537          return;
34538        } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) {
34539          this.numSameDts_--;
34540          if (!this.numSameDts_) {
34541            // We've received the last duplicate packet, time to start processing again
34542            this.ignoreNextEqualDts_ = false;
34543          }
34544          return;
34545        }
34546
34547        // parse out CC data packets and save them for later
34548        this.captionPackets_ = this.captionPackets_.concat(parseCaptionPackets(event.pts, userData));
34549        if (this.latestDts_ !== event.dts) {
34550          this.numSameDts_ = 0;
34551        }
34552        this.numSameDts_++;
34553        this.latestDts_ = event.dts;
34554      };
34555
34556      CaptionStream.prototype.flush = function () {
34557        // make sure we actually parsed captions before proceeding
34558        if (!this.captionPackets_.length) {
34559          this.ccStreams_.forEach(function (cc) {
34560            cc.flush();
34561          }, this);
34562          return;
34563        }
34564
34565        // In Chrome, the Array#sort function is not stable so add a
34566        // presortIndex that we can use to ensure we get a stable-sort
34567        this.captionPackets_.forEach(function (elem, idx) {
34568          elem.presortIndex = idx;
34569        });
34570
34571        // sort caption byte-pairs based on their PTS values
34572        this.captionPackets_.sort(function (a, b) {
34573          if (a.pts === b.pts) {
34574            return a.presortIndex - b.presortIndex;
34575          }
34576          return a.pts - b.pts;
34577        });
34578
34579        this.captionPackets_.forEach(function (packet) {
34580          if (packet.type < 2) {
34581            // Dispatch packet to the right Cea608Stream
34582            this.dispatchCea608Packet(packet);
34583          }
34584          // this is where an 'else' would go for a dispatching packets
34585          // to a theoretical Cea708Stream that handles SERVICEn data
34586        }, this);
34587
34588        this.captionPackets_.length = 0;
34589        this.ccStreams_.forEach(function (cc) {
34590          cc.flush();
34591        }, this);
34592        return;
34593      };
34594
34595      CaptionStream.prototype.reset = function () {
34596        this.latestDts_ = null;
34597        this.ignoreNextEqualDts_ = false;
34598        this.numSameDts_ = 0;
34599        this.activeCea608Channel_ = [null, null];
34600        this.ccStreams_.forEach(function (ccStream) {
34601          ccStream.reset();
34602        });
34603      };
34604
34605      CaptionStream.prototype.dispatchCea608Packet = function (packet) {
34606        // NOTE: packet.type is the CEA608 field
34607        if (this.setsChannel1Active(packet)) {
34608          this.activeCea608Channel_[packet.type] = 0;
34609        } else if (this.setsChannel2Active(packet)) {
34610          this.activeCea608Channel_[packet.type] = 1;
34611        }
34612        if (this.activeCea608Channel_[packet.type] === null) {
34613          // If we haven't received anything to set the active channel, discard the
34614          // data; we don't want jumbled captions
34615          return;
34616        }
34617        this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet);
34618      };
34619
34620      CaptionStream.prototype.setsChannel1Active = function (packet) {
34621        return (packet.ccData & 0x7800) === 0x1000;
34622      };
34623      CaptionStream.prototype.setsChannel2Active = function (packet) {
34624        return (packet.ccData & 0x7800) === 0x1800;
34625      };
34626
34627      // ----------------------
34628      // Session to Application
34629      // ----------------------
34630
34631      var CHARACTER_TRANSLATION = {
34632        0x2a: 0xe1, // á
34633        0x5c: 0xe9, // é
34634        0x5e: 0xed, // í
34635        0x5f: 0xf3, // ó
34636        0x60: 0xfa, // ú
34637        0x7b: 0xe7, // ç
34638        0x7c: 0xf7, // ÷
34639        0x7d: 0xd1, // Ñ
34640        0x7e: 0xf1, // ñ
34641        0x7f: 0x2588, // █
34642        0x0130: 0xae, // ®
34643        0x0131: 0xb0, // °
34644        0x0132: 0xbd, // ½
34645        0x0133: 0xbf, // ¿
34646        0x0134: 0x2122, // ™
34647        0x0135: 0xa2, // ¢
34648        0x0136: 0xa3, // £
34649        0x0137: 0x266a, // ♪
34650        0x0138: 0xe0, // à
34651        0x0139: 0xa0, //
34652        0x013a: 0xe8, // è
34653        0x013b: 0xe2, // â
34654        0x013c: 0xea, // ê
34655        0x013d: 0xee, // î
34656        0x013e: 0xf4, // ô
34657        0x013f: 0xfb, // û
34658        0x0220: 0xc1, // Á
34659        0x0221: 0xc9, // É
34660        0x0222: 0xd3, // Ó
34661        0x0223: 0xda, // Ú
34662        0x0224: 0xdc, // Ü
34663        0x0225: 0xfc, // ü
34664        0x0226: 0x2018, // ‘
34665        0x0227: 0xa1, // ¡
34666        0x0228: 0x2a, // *
34667        0x0229: 0x27, // '
34668        0x022a: 0x2014, // —
34669        0x022b: 0xa9, // ©
34670        0x022c: 0x2120, // ℠
34671        0x022d: 0x2022, // •
34672        0x022e: 0x201c, // “
34673        0x022f: 0x201d, // ”
34674        0x0230: 0xc0, // À
34675        0x0231: 0xc2, // Â
34676        0x0232: 0xc7, // Ç
34677        0x0233: 0xc8, // È
34678        0x0234: 0xca, // Ê
34679        0x0235: 0xcb, // Ë
34680        0x0236: 0xeb, // ë
34681        0x0237: 0xce, // Î
34682        0x0238: 0xcf, // Ï
34683        0x0239: 0xef, // ï
34684        0x023a: 0xd4, // Ô
34685        0x023b: 0xd9, // Ù
34686        0x023c: 0xf9, // ù
34687        0x023d: 0xdb, // Û
34688        0x023e: 0xab, // «
34689        0x023f: 0xbb, // »
34690        0x0320: 0xc3, // Ã
34691        0x0321: 0xe3, // ã
34692        0x0322: 0xcd, // Í
34693        0x0323: 0xcc, // Ì
34694        0x0324: 0xec, // ì
34695        0x0325: 0xd2, // Ò
34696        0x0326: 0xf2, // ò
34697        0x0327: 0xd5, // Õ
34698        0x0328: 0xf5, // õ
34699        0x0329: 0x7b, // {
34700        0x032a: 0x7d, // }
34701        0x032b: 0x5c, // \
34702        0x032c: 0x5e, // ^
34703        0x032d: 0x5f, // _
34704        0x032e: 0x7c, // |
34705        0x032f: 0x7e, // ~
34706        0x0330: 0xc4, // Ä
34707        0x0331: 0xe4, // ä
34708        0x0332: 0xd6, // Ö
34709        0x0333: 0xf6, // ö
34710        0x0334: 0xdf, // ß
34711        0x0335: 0xa5, // ¥
34712        0x0336: 0xa4, // ¤
34713        0x0337: 0x2502, // │
34714        0x0338: 0xc5, // Å
34715        0x0339: 0xe5, // å
34716        0x033a: 0xd8, // Ø
34717        0x033b: 0xf8, // ø
34718        0x033c: 0x250c, // ┌
34719        0x033d: 0x2510, // ┐
34720        0x033e: 0x2514, // └
34721        0x033f: 0x2518 // ┘
34722      };
34723
34724      var getCharFromCode = function getCharFromCode(code) {
34725        if (code === null) {
34726          return '';
34727        }
34728        code = CHARACTER_TRANSLATION[code] || code;
34729        return String.fromCharCode(code);
34730      };
34731
34732      // the index of the last row in a CEA-608 display buffer
34733      var BOTTOM_ROW = 14;
34734
34735      // This array is used for mapping PACs -> row #, since there's no way of
34736      // getting it through bit logic.
34737      var ROWS = [0x1100, 0x1120, 0x1200, 0x1220, 0x1500, 0x1520, 0x1600, 0x1620, 0x1700, 0x1720, 0x1000, 0x1300, 0x1320, 0x1400, 0x1420];
34738
34739      // CEA-608 captions are rendered onto a 34x15 matrix of character
34740      // cells. The "bottom" row is the last element in the outer array.
34741      var createDisplayBuffer = function createDisplayBuffer() {
34742        var result = [],
34743            i = BOTTOM_ROW + 1;
34744        while (i--) {
34745          result.push('');
34746        }
34747        return result;
34748      };
34749
34750      var Cea608Stream = function Cea608Stream(field, dataChannel) {
34751        Cea608Stream.prototype.init.call(this);
34752
34753        this.field_ = field || 0;
34754        this.dataChannel_ = dataChannel || 0;
34755
34756        this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1);
34757
34758        this.setConstants();
34759        this.reset();
34760
34761        this.push = function (packet) {
34762          var data, swap, char0, char1, text;
34763          // remove the parity bits
34764          data = packet.ccData & 0x7f7f;
34765
34766          // ignore duplicate control codes; the spec demands they're sent twice
34767          if (data === this.lastControlCode_) {
34768            this.lastControlCode_ = null;
34769            return;
34770          }
34771
34772          // Store control codes
34773          if ((data & 0xf000) === 0x1000) {
34774            this.lastControlCode_ = data;
34775          } else if (data !== this.PADDING_) {
34776            this.lastControlCode_ = null;
34777          }
34778
34779          char0 = data >>> 8;
34780          char1 = data & 0xff;
vendor: 4,605 bytes, lines 34781-34882
34781
34782          if (data === this.PADDING_) {
34783            return;
34784          } else if (data === this.RESUME_CAPTION_LOADING_) {
34785            this.mode_ = 'popOn';
34786          } else if (data === this.END_OF_CAPTION_) {
34787            this.clearFormatting(packet.pts);
34788            // if a caption was being displayed, it's gone now
34789            this.flushDisplayed(packet.pts);
34790
34791            // flip memory
34792            swap = this.displayed_;
34793            this.displayed_ = this.nonDisplayed_;
34794            this.nonDisplayed_ = swap;
34795
34796            // start measuring the time to display the caption
34797            this.startPts_ = packet.pts;
34798          } else if (data === this.ROLL_UP_2_ROWS_) {
34799            this.topRow_ = BOTTOM_ROW - 1;
34800            this.mode_ = 'rollUp';
34801          } else if (data === this.ROLL_UP_3_ROWS_) {
34802            this.topRow_ = BOTTOM_ROW - 2;
34803            this.mode_ = 'rollUp';
34804          } else if (data === this.ROLL_UP_4_ROWS_) {
34805            this.topRow_ = BOTTOM_ROW - 3;
34806            this.mode_ = 'rollUp';
34807          } else if (data === this.CARRIAGE_RETURN_) {
34808            this.clearFormatting(packet.pts);
34809            this.flushDisplayed(packet.pts);
34810            this.shiftRowsUp_();
34811            this.startPts_ = packet.pts;
34812          } else if (data === this.BACKSPACE_) {
34813            if (this.mode_ === 'popOn') {
34814              this.nonDisplayed_[BOTTOM_ROW] = this.nonDisplayed_[BOTTOM_ROW].slice(0, -1);
34815            } else {
34816              this.displayed_[BOTTOM_ROW] = this.displayed_[BOTTOM_ROW].slice(0, -1);
34817            }
34818          } else if (data === this.ERASE_DISPLAYED_MEMORY_) {
34819            this.flushDisplayed(packet.pts);
34820            this.displayed_ = createDisplayBuffer();
34821          } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) {
34822            this.nonDisplayed_ = createDisplayBuffer();
34823          } else if (data === this.RESUME_DIRECT_CAPTIONING_) {
34824            this.mode_ = 'paintOn';
34825
34826            // Append special characters to caption text
34827          } else if (this.isSpecialCharacter(char0, char1)) {
34828            // Bitmask char0 so that we can apply character transformations
34829            // regardless of field and data channel.
34830            // Then byte-shift to the left and OR with char1 so we can pass the
34831            // entire character code to `getCharFromCode`.
34832            char0 = (char0 & 0x03) << 8;
34833            text = getCharFromCode(char0 | char1);
34834            this[this.mode_](packet.pts, text);
34835            this.column_++;
34836
34837            // Append extended characters to caption text
34838          } else if (this.isExtCharacter(char0, char1)) {
34839            // Extended characters always follow their "non-extended" equivalents.
34840            // IE if a "è" is desired, you'll always receive "eè"; non-compliant
34841            // decoders are supposed to drop the "è", while compliant decoders
34842            // backspace the "e" and insert "è".
34843
34844            // Delete the previous character
34845            if (this.mode_ === 'popOn') {
34846              this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
34847            } else {
34848              this.displayed_[BOTTOM_ROW] = this.displayed_[BOTTOM_ROW].slice(0, -1);
34849            }
34850
34851            // Bitmask char0 so that we can apply character transformations
34852            // regardless of field and data channel.
34853            // Then byte-shift to the left and OR with char1 so we can pass the
34854            // entire character code to `getCharFromCode`.
34855            char0 = (char0 & 0x03) << 8;
34856            text = getCharFromCode(char0 | char1);
34857            this[this.mode_](packet.pts, text);
34858            this.column_++;
34859
34860            // Process mid-row codes
34861          } else if (this.isMidRowCode(char0, char1)) {
34862            // Attributes are not additive, so clear all formatting
34863            this.clearFormatting(packet.pts);
34864
34865            // According to the standard, mid-row codes
34866            // should be replaced with spaces, so add one now
34867            this[this.mode_](packet.pts, ' ');
34868            this.column_++;
34869
34870            if ((char1 & 0xe) === 0xe) {
34871              this.addFormatting(packet.pts, ['i']);
34872            }
34873
34874            if ((char1 & 0x1) === 0x1) {
34875              this.addFormatting(packet.pts, ['u']);
34876            }
34877
34878            // Detect offset control codes and adjust cursor
34879          } else if (this.isOffsetControlCode(char0, char1)) {
34880            // Cursor position is set by indent PAC (see below) in 4-column
34881            // increments, with an additional offset code of 1-3 to reach any
34882            // of the 32 columns specified by CEA-608. So all we need to do
vendor: 80,906 bytes, lines 34883-37094
34883            // here is increment the column cursor by the given offset.
34884            this.column_ += char1 & 0x03;
34885
34886            // Detect PACs (Preamble Address Codes)
34887          } else if (this.isPAC(char0, char1)) {
34888
34889            // There's no logic for PAC -> row mapping, so we have to just
34890            // find the row code in an array and use its index :(
34891            var row = ROWS.indexOf(data & 0x1f20);
34892
34893            if (row !== this.row_) {
34894              // formatting is only persistent for current row
34895              this.clearFormatting(packet.pts);
34896              this.row_ = row;
34897            }
34898            // All PACs can apply underline, so detect and apply
34899            // (All odd-numbered second bytes set underline)
34900            if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) {
34901              this.addFormatting(packet.pts, ['u']);
34902            }
34903
34904            if ((data & 0x10) === 0x10) {
34905              // We've got an indent level code. Each successive even number
34906              // increments the column cursor by 4, so we can get the desired
34907              // column position by bit-shifting to the right (to get n/2)
34908              // and multiplying by 4.
34909              this.column_ = ((data & 0xe) >> 1) * 4;
34910            }
34911
34912            if (this.isColorPAC(char1)) {
34913              // it's a color code, though we only support white, which
34914              // can be either normal or italicized. white italics can be
34915              // either 0x4e or 0x6e depending on the row, so we just
34916              // bitwise-and with 0xe to see if italics should be turned on
34917              if ((char1 & 0xe) === 0xe) {
34918                this.addFormatting(packet.pts, ['i']);
34919              }
34920            }
34921
34922            // We have a normal character in char0, and possibly one in char1
34923          } else if (this.isNormalChar(char0)) {
34924            if (char1 === 0x00) {
34925              char1 = null;
34926            }
34927            text = getCharFromCode(char0);
34928            text += getCharFromCode(char1);
34929            this[this.mode_](packet.pts, text);
34930            this.column_ += text.length;
34931          } // finish data processing
34932        };
34933      };
34934      Cea608Stream.prototype = new stream();
34935      // Trigger a cue point that captures the current state of the
34936      // display buffer
34937      Cea608Stream.prototype.flushDisplayed = function (pts) {
34938        var content = this.displayed_
34939        // remove spaces from the start and end of the string
34940        .map(function (row) {
34941          return row.trim();
34942        })
34943        // combine all text rows to display in one cue
34944        .join('\n')
34945        // and remove blank rows from the start and end, but not the middle
34946        .replace(/^\n+|\n+$/g, '');
34947
34948        if (content.length) {
34949          this.trigger('data', {
34950            startPts: this.startPts_,
34951            endPts: pts,
34952            text: content,
34953            stream: this.name_
34954          });
34955        }
34956      };
34957
34958      /**
34959       * Zero out the data, used for startup and on seek
34960       */
34961      Cea608Stream.prototype.reset = function () {
34962        this.mode_ = 'popOn';
34963        // When in roll-up mode, the index of the last row that will
34964        // actually display captions. If a caption is shifted to a row
34965        // with a lower index than this, it is cleared from the display
34966        // buffer
34967        this.topRow_ = 0;
34968        this.startPts_ = 0;
34969        this.displayed_ = createDisplayBuffer();
34970        this.nonDisplayed_ = createDisplayBuffer();
34971        this.lastControlCode_ = null;
34972
34973        // Track row and column for proper line-breaking and spacing
34974        this.column_ = 0;
34975        this.row_ = BOTTOM_ROW;
34976
34977        // This variable holds currently-applied formatting
34978        this.formatting_ = [];
34979      };
34980
34981      /**
34982       * Sets up control code and related constants for this instance
34983       */
34984      Cea608Stream.prototype.setConstants = function () {
34985        // The following attributes have these uses:
34986        // ext_ :    char0 for mid-row codes, and the base for extended
34987        //           chars (ext_+0, ext_+1, and ext_+2 are char0s for
34988        //           extended codes)
34989        // control_: char0 for control codes, except byte-shifted to the
34990        //           left so that we can do this.control_ | CONTROL_CODE
34991        // offset_:  char0 for tab offset codes
34992        //
34993        // It's also worth noting that control codes, and _only_ control codes,
34994        // differ between field 1 and field2. Field 2 control codes are always
34995        // their field 1 value plus 1. That's why there's the "| field" on the
34996        // control value.
34997        if (this.dataChannel_ === 0) {
34998          this.BASE_ = 0x10;
34999          this.EXT_ = 0x11;
35000          this.CONTROL_ = (0x14 | this.field_) << 8;
35001          this.OFFSET_ = 0x17;
35002        } else if (this.dataChannel_ === 1) {
35003          this.BASE_ = 0x18;
35004          this.EXT_ = 0x19;
35005          this.CONTROL_ = (0x1c | this.field_) << 8;
35006          this.OFFSET_ = 0x1f;
35007        }
35008
35009        // Constants for the LSByte command codes recognized by Cea608Stream. This
35010        // list is not exhaustive. For a more comprehensive listing and semantics see
35011        // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf
35012        // Padding
35013        this.PADDING_ = 0x0000;
35014        // Pop-on Mode
35015        this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20;
35016        this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f;
35017        // Roll-up Mode
35018        this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25;
35019        this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26;
35020        this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27;
35021        this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d;
35022        // paint-on mode (not supported)
35023        this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29;
35024        // Erasure
35025        this.BACKSPACE_ = this.CONTROL_ | 0x21;
35026        this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c;
35027        this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e;
35028      };
35029
35030      /**
35031       * Detects if the 2-byte packet data is a special character
35032       *
35033       * Special characters have a second byte in the range 0x30 to 0x3f,
35034       * with the first byte being 0x11 (for data channel 1) or 0x19 (for
35035       * data channel 2).
35036       *
35037       * @param  {Integer} char0 The first byte
35038       * @param  {Integer} char1 The second byte
35039       * @return {Boolean}       Whether the 2 bytes are an special character
35040       */
35041      Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) {
35042        return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f;
35043      };
35044
35045      /**
35046       * Detects if the 2-byte packet data is an extended character
35047       *
35048       * Extended characters have a second byte in the range 0x20 to 0x3f,
35049       * with the first byte being 0x12 or 0x13 (for data channel 1) or
35050       * 0x1a or 0x1b (for data channel 2).
35051       *
35052       * @param  {Integer} char0 The first byte
35053       * @param  {Integer} char1 The second byte
35054       * @return {Boolean}       Whether the 2 bytes are an extended character
35055       */
35056      Cea608Stream.prototype.isExtCharacter = function (char0, char1) {
35057        return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f;
35058      };
35059
35060      /**
35061       * Detects if the 2-byte packet is a mid-row code
35062       *
35063       * Mid-row codes have a second byte in the range 0x20 to 0x2f, with
35064       * the first byte being 0x11 (for data channel 1) or 0x19 (for data
35065       * channel 2).
35066       *
35067       * @param  {Integer} char0 The first byte
35068       * @param  {Integer} char1 The second byte
35069       * @return {Boolean}       Whether the 2 bytes are a mid-row code
35070       */
35071      Cea608Stream.prototype.isMidRowCode = function (char0, char1) {
35072        return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f;
35073      };
35074
35075      /**
35076       * Detects if the 2-byte packet is an offset control code
35077       *
35078       * Offset control codes have a second byte in the range 0x21 to 0x23,
35079       * with the first byte being 0x17 (for data channel 1) or 0x1f (for
35080       * data channel 2).
35081       *
35082       * @param  {Integer} char0 The first byte
35083       * @param  {Integer} char1 The second byte
35084       * @return {Boolean}       Whether the 2 bytes are an offset control code
35085       */
35086      Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) {
35087        return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23;
35088      };
35089
35090      /**
35091       * Detects if the 2-byte packet is a Preamble Address Code
35092       *
35093       * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1)
35094       * or 0x18 to 0x1f (for data channel 2), with the second byte in the
35095       * range 0x40 to 0x7f.
35096       *
35097       * @param  {Integer} char0 The first byte
35098       * @param  {Integer} char1 The second byte
35099       * @return {Boolean}       Whether the 2 bytes are a PAC
35100       */
35101      Cea608Stream.prototype.isPAC = function (char0, char1) {
35102        return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f;
35103      };
35104
35105      /**
35106       * Detects if a packet's second byte is in the range of a PAC color code
35107       *
35108       * PAC color codes have the second byte be in the range 0x40 to 0x4f, or
35109       * 0x60 to 0x6f.
35110       *
35111       * @param  {Integer} char1 The second byte
35112       * @return {Boolean}       Whether the byte is a color PAC
35113       */
35114      Cea608Stream.prototype.isColorPAC = function (char1) {
35115        return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f;
35116      };
35117
35118      /**
35119       * Detects if a single byte is in the range of a normal character
35120       *
35121       * Normal text bytes are in the range 0x20 to 0x7f.
35122       *
35123       * @param  {Integer} char  The byte
35124       * @return {Boolean}       Whether the byte is a normal character
35125       */
35126      Cea608Stream.prototype.isNormalChar = function (char) {
35127        return char >= 0x20 && char <= 0x7f;
35128      };
35129
35130      // Adds the opening HTML tag for the passed character to the caption text,
35131      // and keeps track of it for later closing
35132      Cea608Stream.prototype.addFormatting = function (pts, format) {
35133        this.formatting_ = this.formatting_.concat(format);
35134        var text = format.reduce(function (text, format) {
35135          return text + '<' + format + '>';
35136        }, '');
35137        this[this.mode_](pts, text);
35138      };
35139
35140      // Adds HTML closing tags for current formatting to caption text and
35141      // clears remembered formatting
35142      Cea608Stream.prototype.clearFormatting = function (pts) {
35143        if (!this.formatting_.length) {
35144          return;
35145        }
35146        var text = this.formatting_.reverse().reduce(function (text, format) {
35147          return text + '</' + format + '>';
35148        }, '');
35149        this.formatting_ = [];
35150        this[this.mode_](pts, text);
35151      };
35152
35153      // Mode Implementations
35154      Cea608Stream.prototype.popOn = function (pts, text) {
35155        var baseRow = this.nonDisplayed_[this.row_];
35156
35157        // buffer characters
35158        baseRow += text;
35159        this.nonDisplayed_[this.row_] = baseRow;
35160      };
35161
35162      Cea608Stream.prototype.rollUp = function (pts, text) {
35163        var baseRow = this.displayed_[BOTTOM_ROW];
35164
35165        baseRow += text;
35166        this.displayed_[BOTTOM_ROW] = baseRow;
35167      };
35168
35169      Cea608Stream.prototype.shiftRowsUp_ = function () {
35170        var i;
35171        // clear out inactive rows
35172        for (i = 0; i < this.topRow_; i++) {
35173          this.displayed_[i] = '';
35174        }
35175        // shift displayed rows up
35176        for (i = this.topRow_; i < BOTTOM_ROW; i++) {
35177          this.displayed_[i] = this.displayed_[i + 1];
35178        }
35179        // clear out the bottom row
35180        this.displayed_[BOTTOM_ROW] = '';
35181      };
35182
35183      // paintOn mode is not implemented
35184      Cea608Stream.prototype.paintOn = function () {};
35185
35186      // exports
35187      var captionStream = {
35188        CaptionStream: CaptionStream,
35189        Cea608Stream: Cea608Stream
35190      };
35191
35192      var streamTypes = {
35193        H264_STREAM_TYPE: 0x1B,
35194        ADTS_STREAM_TYPE: 0x0F,
35195        METADATA_STREAM_TYPE: 0x15
35196      };
35197
35198      var MAX_TS = 8589934592;
35199
35200      var RO_THRESH = 4294967296;
35201
35202      var handleRollover = function handleRollover(value, reference) {
35203        var direction = 1;
35204
35205        if (value > reference) {
35206          // If the current timestamp value is greater than our reference timestamp and we detect a
35207          // timestamp rollover, this means the roll over is happening in the opposite direction.
35208          // Example scenario: Enter a long stream/video just after a rollover occurred. The reference
35209          // point will be set to a small number, e.g. 1. The user then seeks backwards over the
35210          // rollover point. In loading this segment, the timestamp values will be very large,
35211          // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust
35212          // the time stamp to be `value - 2^33`.
35213          direction = -1;
35214        }
35215
35216        // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will
35217        // cause an incorrect adjustment.
35218        while (Math.abs(reference - value) > RO_THRESH) {
35219          value += direction * MAX_TS;
35220        }
35221
35222        return value;
35223      };
35224
35225      var TimestampRolloverStream = function TimestampRolloverStream(type) {
35226        var lastDTS, referenceDTS;
35227
35228        TimestampRolloverStream.prototype.init.call(this);
35229
35230        this.type_ = type;
35231
35232        this.push = function (data) {
35233          if (data.type !== this.type_) {
35234            return;
35235          }
35236
35237          if (referenceDTS === undefined) {
35238            referenceDTS = data.dts;
35239          }
35240
35241          data.dts = handleRollover(data.dts, referenceDTS);
35242          data.pts = handleRollover(data.pts, referenceDTS);
35243
35244          lastDTS = data.dts;
35245
35246          this.trigger('data', data);
35247        };
35248
35249        this.flush = function () {
35250          referenceDTS = lastDTS;
35251          this.trigger('done');
35252        };
35253
35254        this.discontinuity = function () {
35255          referenceDTS = void 0;
35256          lastDTS = void 0;
35257        };
35258      };
35259
35260      TimestampRolloverStream.prototype = new stream();
35261
35262      var timestampRolloverStream = {
35263        TimestampRolloverStream: TimestampRolloverStream,
35264        handleRollover: handleRollover
35265      };
35266
35267      var percentEncode = function percentEncode(bytes, start, end) {
35268        var i,
35269            result = '';
35270        for (i = start; i < end; i++) {
35271          result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
35272        }
35273        return result;
35274      },
35275
35276
35277      // return the string representation of the specified byte range,
35278      // interpreted as UTf-8.
35279      parseUtf8 = function parseUtf8(bytes, start, end) {
35280        return decodeURIComponent(percentEncode(bytes, start, end));
35281      },
35282
35283
35284      // return the string representation of the specified byte range,
35285      // interpreted as ISO-8859-1.
35286      parseIso88591 = function parseIso88591(bytes, start, end) {
35287        return unescape(percentEncode(bytes, start, end)); // jshint ignore:line
35288      },
35289          parseSyncSafeInteger = function parseSyncSafeInteger(data) {
35290        return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3];
35291      },
35292          tagParsers = {
35293        TXXX: function TXXX(tag) {
35294          var i;
35295          if (tag.data[0] !== 3) {
35296            // ignore frames with unrecognized character encodings
35297            return;
35298          }
35299
35300          for (i = 1; i < tag.data.length; i++) {
35301            if (tag.data[i] === 0) {
35302              // parse the text fields
35303              tag.description = parseUtf8(tag.data, 1, i);
35304              // do not include the null terminator in the tag value
35305              tag.value = parseUtf8(tag.data, i + 1, tag.data.length).replace(/\0*$/, '');
35306              break;
35307            }
35308          }
35309          tag.data = tag.value;
35310        },
35311        WXXX: function WXXX(tag) {
35312          var i;
35313          if (tag.data[0] !== 3) {
35314            // ignore frames with unrecognized character encodings
35315            return;
35316          }
35317
35318          for (i = 1; i < tag.data.length; i++) {
35319            if (tag.data[i] === 0) {
35320              // parse the description and URL fields
35321              tag.description = parseUtf8(tag.data, 1, i);
35322              tag.url = parseUtf8(tag.data, i + 1, tag.data.length);
35323              break;
35324            }
35325          }
35326        },
35327        PRIV: function PRIV(tag) {
35328          var i;
35329
35330          for (i = 0; i < tag.data.length; i++) {
35331            if (tag.data[i] === 0) {
35332              // parse the description and URL fields
35333              tag.owner = parseIso88591(tag.data, 0, i);
35334              break;
35335            }
35336          }
35337          tag.privateData = tag.data.subarray(i + 1);
35338          tag.data = tag.privateData;
35339        }
35340      },
35341          _MetadataStream;
35342
35343      _MetadataStream = function MetadataStream(options) {
35344        var settings = {
35345          debug: !!(options && options.debug),
35346
35347          // the bytes of the program-level descriptor field in MP2T
35348          // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and
35349          // program element descriptors"
35350          descriptor: options && options.descriptor
35351        },
35352
35353
35354        // the total size in bytes of the ID3 tag being parsed
35355        tagSize = 0,
35356
35357
35358        // tag data that is not complete enough to be parsed
35359        buffer = [],
35360
35361
35362        // the total number of bytes currently in the buffer
35363        bufferSize = 0,
35364            i;
35365
35366        _MetadataStream.prototype.init.call(this);
35367
35368        // calculate the text track in-band metadata track dispatch type
35369        // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track
35370        this.dispatchType = streamTypes.METADATA_STREAM_TYPE.toString(16);
35371        if (settings.descriptor) {
35372          for (i = 0; i < settings.descriptor.length; i++) {
35373            this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2);
35374          }
35375        }
35376
35377        this.push = function (chunk) {
35378          var tag, frameStart, frameSize, frame, i, frameHeader;
35379          if (chunk.type !== 'timed-metadata') {
35380            return;
35381          }
35382
35383          // if data_alignment_indicator is set in the PES header,
35384          // we must have the start of a new ID3 tag. Assume anything
35385          // remaining in the buffer was malformed and throw it out
35386          if (chunk.dataAlignmentIndicator) {
35387            bufferSize = 0;
35388            buffer.length = 0;
35389          }
35390
35391          // ignore events that don't look like ID3 data
35392          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))) {
35393            if (settings.debug) {
35394              // eslint-disable-next-line no-console
35395              console.log('Skipping unrecognized metadata packet');
35396            }
35397            return;
35398          }
35399
35400          // add this chunk to the data we've collected so far
35401
35402          buffer.push(chunk);
35403          bufferSize += chunk.data.byteLength;
35404
35405          // grab the size of the entire frame from the ID3 header
35406          if (buffer.length === 1) {
35407            // the frame size is transmitted as a 28-bit integer in the
35408            // last four bytes of the ID3 header.
35409            // The most significant bit of each byte is dropped and the
35410            // results concatenated to recover the actual value.
35411            tagSize = parseSyncSafeInteger(chunk.data.subarray(6, 10));
35412
35413            // ID3 reports the tag size excluding the header but it's more
35414            // convenient for our comparisons to include it
35415            tagSize += 10;
35416          }
35417
35418          // if the entire frame has not arrived, wait for more data
35419          if (bufferSize < tagSize) {
35420            return;
35421          }
35422
35423          // collect the entire frame so it can be parsed
35424          tag = {
35425            data: new Uint8Array(tagSize),
35426            frames: [],
35427            pts: buffer[0].pts,
35428            dts: buffer[0].dts
35429          };
35430          for (i = 0; i < tagSize;) {
35431            tag.data.set(buffer[0].data.subarray(0, tagSize - i), i);
35432            i += buffer[0].data.byteLength;
35433            bufferSize -= buffer[0].data.byteLength;
35434            buffer.shift();
35435          }
35436
35437          // find the start of the first frame and the end of the tag
35438          frameStart = 10;
35439          if (tag.data[5] & 0x40) {
35440            // advance the frame start past the extended header
35441            frameStart += 4; // header size field
35442            frameStart += parseSyncSafeInteger(tag.data.subarray(10, 14));
35443
35444            // clip any padding off the end
35445            tagSize -= parseSyncSafeInteger(tag.data.subarray(16, 20));
35446          }
35447
35448          // parse one or more ID3 frames
35449          // http://id3.org/id3v2.3.0#ID3v2_frame_overview
35450          do {
35451            // determine the number of bytes in this frame
35452            frameSize = parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8));
35453            if (frameSize < 1) {
35454              // eslint-disable-next-line no-console
35455              return console.log('Malformed ID3 frame encountered. Skipping metadata parsing.');
35456            }
35457            frameHeader = String.fromCharCode(tag.data[frameStart], tag.data[frameStart + 1], tag.data[frameStart + 2], tag.data[frameStart + 3]);
35458
35459            frame = {
35460              id: frameHeader,
35461              data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10)
35462            };
35463            frame.key = frame.id;
35464            if (tagParsers[frame.id]) {
35465              tagParsers[frame.id](frame);
35466
35467              // handle the special PRIV frame used to indicate the start
35468              // time for raw AAC data
35469              if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') {
35470                var d = frame.data,
35471                    size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2;
35472
35473                size *= 4;
35474                size += d[7] & 0x03;
35475                frame.timeStamp = size;
35476                // in raw AAC, all subsequent data will be timestamped based
35477                // on the value of this frame
35478                // we couldn't have known the appropriate pts and dts before
35479                // parsing this ID3 tag so set those values now
35480                if (tag.pts === undefined && tag.dts === undefined) {
35481                  tag.pts = frame.timeStamp;
35482                  tag.dts = frame.timeStamp;
35483                }
35484                this.trigger('timestamp', frame);
35485              }
35486            }
35487            tag.frames.push(frame);
35488
35489            frameStart += 10; // advance past the frame header
35490            frameStart += frameSize; // advance past the frame body
35491          } while (frameStart < tagSize);
35492          this.trigger('data', tag);
35493        };
35494      };
35495      _MetadataStream.prototype = new stream();
35496
35497      var metadataStream = _MetadataStream;
35498
35499      var TimestampRolloverStream$1 = timestampRolloverStream.TimestampRolloverStream;
35500
35501      // object types
35502      var _TransportPacketStream, _TransportParseStream, _ElementaryStream;
35503
35504      // constants
35505      var MP2T_PACKET_LENGTH = 188,
35506
35507
35508      // bytes
35509      SYNC_BYTE = 0x47;
35510
35511      /**
35512       * Splits an incoming stream of binary data into MPEG-2 Transport
35513       * Stream packets.
35514       */
35515      _TransportPacketStream = function TransportPacketStream() {
35516        var buffer = new Uint8Array(MP2T_PACKET_LENGTH),
35517            bytesInBuffer = 0;
35518
35519        _TransportPacketStream.prototype.init.call(this);
35520
35521        // Deliver new bytes to the stream.
35522
35523        this.push = function (bytes) {
35524          var startIndex = 0,
35525              endIndex = MP2T_PACKET_LENGTH,
35526              everything;
35527
35528          // If there are bytes remaining from the last segment, prepend them to the
35529          // bytes that were pushed in
35530          if (bytesInBuffer) {
35531            everything = new Uint8Array(bytes.byteLength + bytesInBuffer);
35532            everything.set(buffer.subarray(0, bytesInBuffer));
35533            everything.set(bytes, bytesInBuffer);
35534            bytesInBuffer = 0;
35535          } else {
35536            everything = bytes;
35537          }
35538
35539          // While we have enough data for a packet
35540          while (endIndex < everything.byteLength) {
35541            // Look for a pair of start and end sync bytes in the data..
35542            if (everything[startIndex] === SYNC_BYTE && everything[endIndex] === SYNC_BYTE) {
35543              // We found a packet so emit it and jump one whole packet forward in
35544              // the stream
35545              this.trigger('data', everything.subarray(startIndex, endIndex));
35546              startIndex += MP2T_PACKET_LENGTH;
35547              endIndex += MP2T_PACKET_LENGTH;
35548              continue;
35549            }
35550            // If we get here, we have somehow become de-synchronized and we need to step
35551            // forward one byte at a time until we find a pair of sync bytes that denote
35552            // a packet
35553            startIndex++;
35554            endIndex++;
35555          }
35556
35557          // If there was some data left over at the end of the segment that couldn't
35558          // possibly be a whole packet, keep it because it might be the start of a packet
35559          // that continues in the next segment
35560          if (startIndex < everything.byteLength) {
35561            buffer.set(everything.subarray(startIndex), 0);
35562            bytesInBuffer = everything.byteLength - startIndex;
35563          }
35564        };
35565
35566        this.flush = function () {
35567          // If the buffer contains a whole packet when we are being flushed, emit it
35568          // and empty the buffer. Otherwise hold onto the data because it may be
35569          // important for decoding the next segment
35570          if (bytesInBuffer === MP2T_PACKET_LENGTH && buffer[0] === SYNC_BYTE) {
35571            this.trigger('data', buffer);
35572            bytesInBuffer = 0;
35573          }
35574          this.trigger('done');
35575        };
35576      };
35577      _TransportPacketStream.prototype = new stream();
35578
35579      /**
35580       * Accepts an MP2T TransportPacketStream and emits data events with parsed
35581       * forms of the individual transport stream packets.
35582       */
35583      _TransportParseStream = function TransportParseStream() {
35584        var parsePsi, parsePat, parsePmt, self;
35585        _TransportParseStream.prototype.init.call(this);
35586        self = this;
35587
35588        this.packetsWaitingForPmt = [];
35589        this.programMapTable = undefined;
35590
35591        parsePsi = function parsePsi(payload, psi) {
35592          var offset = 0;
35593
35594          // PSI packets may be split into multiple sections and those
35595          // sections may be split into multiple packets. If a PSI
35596          // section starts in this packet, the payload_unit_start_indicator
35597          // will be true and the first byte of the payload will indicate
35598          // the offset from the current position to the start of the
35599          // section.
35600          if (psi.payloadUnitStartIndicator) {
35601            offset += payload[offset] + 1;
35602          }
35603
35604          if (psi.type === 'pat') {
35605            parsePat(payload.subarray(offset), psi);
35606          } else {
35607            parsePmt(payload.subarray(offset), psi);
35608          }
35609        };
35610
35611        parsePat = function parsePat(payload, pat) {
35612          pat.section_number = payload[7]; // eslint-disable-line camelcase
35613          pat.last_section_number = payload[8]; // eslint-disable-line camelcase
35614
35615          // skip the PSI header and parse the first PMT entry
35616          self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11];
35617          pat.pmtPid = self.pmtPid;
35618        };
35619
35620        /**
35621         * Parse out the relevant fields of a Program Map Table (PMT).
35622         * @param payload {Uint8Array} the PMT-specific portion of an MP2T
35623         * packet. The first byte in this array should be the table_id
35624         * field.
35625         * @param pmt {object} the object that should be decorated with
35626         * fields parsed from the PMT.
35627         */
35628        parsePmt = function parsePmt(payload, pmt) {
35629          var sectionLength, tableEnd, programInfoLength, offset;
35630
35631          // PMTs can be sent ahead of the time when they should actually
35632          // take effect. We don't believe this should ever be the case
35633          // for HLS but we'll ignore "forward" PMT declarations if we see
35634          // them. Future PMT declarations have the current_next_indicator
35635          // set to zero.
35636          if (!(payload[5] & 0x01)) {
35637            return;
35638          }
35639
35640          // overwrite any existing program map table
35641          self.programMapTable = {
35642            video: null,
35643            audio: null,
35644            'timed-metadata': {}
35645          };
35646
35647          // the mapping table ends at the end of the current section
35648          sectionLength = (payload[1] & 0x0f) << 8 | payload[2];
35649          tableEnd = 3 + sectionLength - 4;
35650
35651          // to determine where the table is, we have to figure out how
35652          // long the program info descriptors are
35653          programInfoLength = (payload[10] & 0x0f) << 8 | payload[11];
35654
35655          // advance the offset to the first entry in the mapping table
35656          offset = 12 + programInfoLength;
35657          while (offset < tableEnd) {
35658            var streamType = payload[offset];
35659            var pid = (payload[offset + 1] & 0x1F) << 8 | payload[offset + 2];
35660
35661            // only map a single elementary_pid for audio and video stream types
35662            // TODO: should this be done for metadata too? for now maintain behavior of
35663            //       multiple metadata streams
35664            if (streamType === streamTypes.H264_STREAM_TYPE && self.programMapTable.video === null) {
35665              self.programMapTable.video = pid;
35666            } else if (streamType === streamTypes.ADTS_STREAM_TYPE && self.programMapTable.audio === null) {
35667              self.programMapTable.audio = pid;
35668            } else if (streamType === streamTypes.METADATA_STREAM_TYPE) {
35669              // map pid to stream type for metadata streams
35670              self.programMapTable['timed-metadata'][pid] = streamType;
35671            }
35672
35673            // move to the next table entry
35674            // skip past the elementary stream descriptors, if present
35675            offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5;
35676          }
35677
35678          // record the map on the packet as well
35679          pmt.programMapTable = self.programMapTable;
35680        };
35681
35682        /**
35683         * Deliver a new MP2T packet to the stream.
35684         */
35685        this.push = function (packet) {
35686          var result = {},
35687              offset = 4;
35688
35689          result.payloadUnitStartIndicator = !!(packet[1] & 0x40);
35690
35691          // pid is a 13-bit field starting at the last bit of packet[1]
35692          result.pid = packet[1] & 0x1f;
35693          result.pid <<= 8;
35694          result.pid |= packet[2];
35695
35696          // if an adaption field is present, its length is specified by the
35697          // fifth byte of the TS packet header. The adaptation field is
35698          // used to add stuffing to PES packets that don't fill a complete
35699          // TS packet, and to specify some forms of timing and control data
35700          // that we do not currently use.
35701          if ((packet[3] & 0x30) >>> 4 > 0x01) {
35702            offset += packet[offset] + 1;
35703          }
35704
35705          // parse the rest of the packet based on the type
35706          if (result.pid === 0) {
35707            result.type = 'pat';
35708            parsePsi(packet.subarray(offset), result);
35709            this.trigger('data', result);
35710          } else if (result.pid === this.pmtPid) {
35711            result.type = 'pmt';
35712            parsePsi(packet.subarray(offset), result);
35713            this.trigger('data', result);
35714
35715            // if there are any packets waiting for a PMT to be found, process them now
35716            while (this.packetsWaitingForPmt.length) {
35717              this.processPes_.apply(this, this.packetsWaitingForPmt.shift());
35718            }
35719          } else if (this.programMapTable === undefined) {
35720            // When we have not seen a PMT yet, defer further processing of
35721            // PES packets until one has been parsed
35722            this.packetsWaitingForPmt.push([packet, offset, result]);
35723          } else {
35724            this.processPes_(packet, offset, result);
35725          }
35726        };
35727
35728        this.processPes_ = function (packet, offset, result) {
35729          // set the appropriate stream type
35730          if (result.pid === this.programMapTable.video) {
35731            result.streamType = streamTypes.H264_STREAM_TYPE;
35732          } else if (result.pid === this.programMapTable.audio) {
35733            result.streamType = streamTypes.ADTS_STREAM_TYPE;
35734          } else {
35735            // if not video or audio, it is timed-metadata or unknown
35736            // if unknown, streamType will be undefined
35737            result.streamType = this.programMapTable['timed-metadata'][result.pid];
35738          }
35739
35740          result.type = 'pes';
35741          result.data = packet.subarray(offset);
35742
35743          this.trigger('data', result);
35744        };
35745      };
35746      _TransportParseStream.prototype = new stream();
35747      _TransportParseStream.STREAM_TYPES = {
35748        h264: 0x1b,
35749        adts: 0x0f
35750      };
35751
35752      /**
35753       * Reconsistutes program elementary stream (PES) packets from parsed
35754       * transport stream packets. That is, if you pipe an
35755       * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output
35756       * events will be events which capture the bytes for individual PES
35757       * packets plus relevant metadata that has been extracted from the
35758       * container.
35759       */
35760      _ElementaryStream = function ElementaryStream() {
35761        var self = this,
35762
35763
35764        // PES packet fragments
35765        video = {
35766          data: [],
35767          size: 0
35768        },
35769            audio = {
35770          data: [],
35771          size: 0
35772        },
35773            timedMetadata = {
35774          data: [],
35775          size: 0
35776        },
35777            parsePes = function parsePes(payload, pes) {
35778          var ptsDtsFlags;
35779
35780          // get the packet length, this will be 0 for video
35781          pes.packetLength = 6 + (payload[4] << 8 | payload[5]);
35782
35783          // find out if this packets starts a new keyframe
35784          pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0;
35785          // PES packets may be annotated with a PTS value, or a PTS value
35786          // and a DTS value. Determine what combination of values is
35787          // available to work with.
35788          ptsDtsFlags = payload[7];
35789
35790          // PTS and DTS are normally stored as a 33-bit number.  Javascript
35791          // performs all bitwise operations on 32-bit integers but javascript
35792          // supports a much greater range (52-bits) of integer using standard
35793          // mathematical operations.
35794          // We construct a 31-bit value using bitwise operators over the 31
35795          // most significant bits and then multiply by 4 (equal to a left-shift
35796          // of 2) before we add the final 2 least significant bits of the
35797          // timestamp (equal to an OR.)
35798          if (ptsDtsFlags & 0xC0) {
35799            // the PTS and DTS are not written out directly. For information
35800            // on how they are encoded, see
35801            // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
35802            pes.pts = (payload[9] & 0x0E) << 27 | (payload[10] & 0xFF) << 20 | (payload[11] & 0xFE) << 12 | (payload[12] & 0xFF) << 5 | (payload[13] & 0xFE) >>> 3;
35803            pes.pts *= 4; // Left shift by 2
35804            pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs
35805            pes.dts = pes.pts;
35806            if (ptsDtsFlags & 0x40) {
35807              pes.dts = (payload[14] & 0x0E) << 27 | (payload[15] & 0xFF) << 20 | (payload[16] & 0xFE) << 12 | (payload[17] & 0xFF) << 5 | (payload[18] & 0xFE) >>> 3;
35808              pes.dts *= 4; // Left shift by 2
35809              pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs
35810            }
35811          }
35812          // the data section starts immediately after the PES header.
35813          // pes_header_data_length specifies the number of header bytes
35814          // that follow the last byte of the field.
35815          pes.data = payload.subarray(9 + payload[8]);
35816        },
35817            flushStream = function flushStream(stream$$1, type, forceFlush) {
35818          var packetData = new Uint8Array(stream$$1.size),
35819              event = {
35820            type: type
35821          },
35822              i = 0,
35823              offset = 0,
35824              packetFlushable = false,
35825              fragment;
35826
35827          // do nothing if there is not enough buffered data for a complete
35828          // PES header
35829          if (!stream$$1.data.length || stream$$1.size < 9) {
35830            return;
35831          }
35832          event.trackId = stream$$1.data[0].pid;
35833
35834          // reassemble the packet
35835          for (i = 0; i < stream$$1.data.length; i++) {
35836            fragment = stream$$1.data[i];
35837
35838            packetData.set(fragment.data, offset);
35839            offset += fragment.data.byteLength;
35840          }
35841
35842          // parse assembled packet's PES header
35843          parsePes(packetData, event);
35844
35845          // non-video PES packets MUST have a non-zero PES_packet_length
35846          // check that there is enough stream data to fill the packet
35847          packetFlushable = type === 'video' || event.packetLength <= stream$$1.size;
35848
35849          // flush pending packets if the conditions are right
35850          if (forceFlush || packetFlushable) {
35851            stream$$1.size = 0;
35852            stream$$1.data.length = 0;
35853          }
35854
35855          // only emit packets that are complete. this is to avoid assembling
35856          // incomplete PES packets due to poor segmentation
35857          if (packetFlushable) {
35858            self.trigger('data', event);
35859          }
35860        };
35861
35862        _ElementaryStream.prototype.init.call(this);
35863
35864        this.push = function (data) {
35865          ({
35866            pat: function pat() {
35867              // we have to wait for the PMT to arrive as well before we
35868              // have any meaningful metadata
35869            },
35870            pes: function pes() {
35871              var stream$$1, streamType;
35872
35873              switch (data.streamType) {
35874                case streamTypes.H264_STREAM_TYPE:
35875                case streamTypes.H264_STREAM_TYPE:
35876                  stream$$1 = video;
35877                  streamType = 'video';
35878                  break;
35879                case streamTypes.ADTS_STREAM_TYPE:
35880                  stream$$1 = audio;
35881                  streamType = 'audio';
35882                  break;
35883                case streamTypes.METADATA_STREAM_TYPE:
35884                  stream$$1 = timedMetadata;
35885                  streamType = 'timed-metadata';
35886                  break;
35887                default:
35888                  // ignore unknown stream types
35889                  return;
35890              }
35891
35892              // if a new packet is starting, we can flush the completed
35893              // packet
35894              if (data.payloadUnitStartIndicator) {
35895                flushStream(stream$$1, streamType, true);
35896              }
35897
35898              // buffer this fragment until we are sure we've received the
35899              // complete payload
35900              stream$$1.data.push(data);
35901              stream$$1.size += data.data.byteLength;
35902            },
35903            pmt: function pmt() {
35904              var event = {
35905                type: 'metadata',
35906                tracks: []
35907              },
35908                  programMapTable = data.programMapTable;
35909
35910              // translate audio and video streams to tracks
35911              if (programMapTable.video !== null) {
35912                event.tracks.push({
35913                  timelineStartInfo: {
35914                    baseMediaDecodeTime: 0
35915                  },
35916                  id: +programMapTable.video,
35917                  codec: 'avc',
35918                  type: 'video'
35919                });
35920              }
35921              if (programMapTable.audio !== null) {
35922                event.tracks.push({
35923                  timelineStartInfo: {
35924                    baseMediaDecodeTime: 0
35925                  },
35926                  id: +programMapTable.audio,
35927                  codec: 'adts',
35928                  type: 'audio'
35929                });
35930              }
35931
35932              self.trigger('data', event);
35933            }
35934          })[data.type]();
35935        };
35936
35937        /**
35938         * Flush any remaining input. Video PES packets may be of variable
35939         * length. Normally, the start of a new video packet can trigger the
35940         * finalization of the previous packet. That is not possible if no
35941         * more video is forthcoming, however. In that case, some other
35942         * mechanism (like the end of the file) has to be employed. When it is
35943         * clear that no additional data is forthcoming, calling this method
35944         * will flush the buffered packets.
35945         */
35946        this.flush = function () {
35947          // !!THIS ORDER IS IMPORTANT!!
35948          // video first then audio
35949          flushStream(video, 'video');
35950          flushStream(audio, 'audio');
35951          flushStream(timedMetadata, 'timed-metadata');
35952          this.trigger('done');
35953        };
35954      };
35955      _ElementaryStream.prototype = new stream();
35956
35957      var m2ts = {
35958        PAT_PID: 0x0000,
35959        MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH,
35960        TransportPacketStream: _TransportPacketStream,
35961        TransportParseStream: _TransportParseStream,
35962        ElementaryStream: _ElementaryStream,
35963        TimestampRolloverStream: TimestampRolloverStream$1,
35964        CaptionStream: captionStream.CaptionStream,
35965        Cea608Stream: captionStream.Cea608Stream,
35966        MetadataStream: metadataStream
35967      };
35968
35969      for (var type in streamTypes) {
35970        if (streamTypes.hasOwnProperty(type)) {
35971          m2ts[type] = streamTypes[type];
35972        }
35973      }
35974
35975      var m2ts_1 = m2ts;
35976
35977      var _AdtsStream;
35978
35979      var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
35980
35981      /*
35982       * Accepts a ElementaryStream and emits data events with parsed
35983       * AAC Audio Frames of the individual packets. Input audio in ADTS
35984       * format is unpacked and re-emitted as AAC frames.
35985       *
35986       * @see http://wiki.multimedia.cx/index.php?title=ADTS
35987       * @see http://wiki.multimedia.cx/?title=Understanding_AAC
35988       */
35989      _AdtsStream = function AdtsStream() {
35990        var buffer;
35991
35992        _AdtsStream.prototype.init.call(this);
35993
35994        this.push = function (packet) {
35995          var i = 0,
35996              frameNum = 0,
35997              frameLength,
35998              protectionSkipBytes,
35999              frameEnd,
36000              oldBuffer,
36001              sampleCount,
36002              adtsFrameDuration;
36003
36004          if (packet.type !== 'audio') {
36005            // ignore non-audio data
36006            return;
36007          }
36008
36009          // Prepend any data in the buffer to the input data so that we can parse
36010          // aac frames the cross a PES packet boundary
36011          if (buffer) {
36012            oldBuffer = buffer;
36013            buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength);
36014            buffer.set(oldBuffer);
36015            buffer.set(packet.data, oldBuffer.byteLength);
36016          } else {
36017            buffer = packet.data;
36018          }
36019
36020          // unpack any ADTS frames which have been fully received
36021          // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS
36022          while (i + 5 < buffer.length) {
36023
36024            // Loook for the start of an ADTS header..
36025            if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) {
36026              // If a valid header was not found,  jump one forward and attempt to
36027              // find a valid ADTS header starting at the next byte
36028              i++;
36029              continue;
36030            }
36031
36032            // The protection skip bit tells us if we have 2 bytes of CRC data at the
36033            // end of the ADTS header
36034            protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2;
36035
36036            // Frame length is a 13 bit integer starting 16 bits from the
36037            // end of the sync sequence
36038            frameLength = (buffer[i + 3] & 0x03) << 11 | buffer[i + 4] << 3 | (buffer[i + 5] & 0xe0) >> 5;
36039
36040            sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024;
36041            adtsFrameDuration = sampleCount * 90000 / ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2];
36042
36043            frameEnd = i + frameLength;
36044
36045            // If we don't have enough data to actually finish this ADTS frame, return
36046            // and wait for more data
36047            if (buffer.byteLength < frameEnd) {
36048              return;
36049            }
36050
36051            // Otherwise, deliver the complete AAC frame
36052            this.trigger('data', {
36053              pts: packet.pts + frameNum * adtsFrameDuration,
36054              dts: packet.dts + frameNum * adtsFrameDuration,
36055              sampleCount: sampleCount,
36056              audioobjecttype: (buffer[i + 2] >>> 6 & 0x03) + 1,
36057              channelcount: (buffer[i + 2] & 1) << 2 | (buffer[i + 3] & 0xc0) >>> 6,
36058              samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2],
36059              samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2,
36060              // assume ISO/IEC 14496-12 AudioSampleEntry default of 16
36061              samplesize: 16,
36062              data: buffer.subarray(i + 7 + protectionSkipBytes, frameEnd)
36063            });
36064
36065            // If the buffer is empty, clear it and return
36066            if (buffer.byteLength === frameEnd) {
36067              buffer = undefined;
36068              return;
36069            }
36070
36071            frameNum++;
36072
36073            // Remove the finished frame from the buffer and start the process again
36074            buffer = buffer.subarray(frameEnd);
36075          }
36076        };
36077        this.flush = function () {
36078          this.trigger('done');
36079        };
36080      };
36081
36082      _AdtsStream.prototype = new stream();
36083
36084      var adts = _AdtsStream;
36085
36086      var ExpGolomb;
36087
36088      /**
36089       * Parser for exponential Golomb codes, a variable-bitwidth number encoding
36090       * scheme used by h264.
36091       */
36092      ExpGolomb = function ExpGolomb(workingData) {
36093        var
36094        // the number of bytes left to examine in workingData
36095        workingBytesAvailable = workingData.byteLength,
36096
36097
36098        // the current word being examined
36099        workingWord = 0,
36100
36101
36102        // :uint
36103
36104        // the number of bits left to examine in the current word
36105        workingBitsAvailable = 0; // :uint;
36106
36107        // ():uint
36108        this.length = function () {
36109          return 8 * workingBytesAvailable;
36110        };
36111
36112        // ():uint
36113        this.bitsAvailable = function () {
36114          return 8 * workingBytesAvailable + workingBitsAvailable;
36115        };
36116
36117        // ():void
36118        this.loadWord = function () {
36119          var position = workingData.byteLength - workingBytesAvailable,
36120              workingBytes = new Uint8Array(4),
36121              availableBytes = Math.min(4, workingBytesAvailable);
36122
36123          if (availableBytes === 0) {
36124            throw new Error('no bytes available');
36125          }
36126
36127          workingBytes.set(workingData.subarray(position, position + availableBytes));
36128          workingWord = new DataView(workingBytes.buffer).getUint32(0);
36129
36130          // track the amount of workingData that has been processed
36131          workingBitsAvailable = availableBytes * 8;
36132          workingBytesAvailable -= availableBytes;
36133        };
36134
36135        // (count:int):void
36136        this.skipBits = function (count) {
36137          var skipBytes; // :int
36138          if (workingBitsAvailable > count) {
36139            workingWord <<= count;
36140            workingBitsAvailable -= count;
36141          } else {
36142            count -= workingBitsAvailable;
36143            skipBytes = Math.floor(count / 8);
36144
36145            count -= skipBytes * 8;
36146            workingBytesAvailable -= skipBytes;
36147
36148            this.loadWord();
36149
36150            workingWord <<= count;
36151            workingBitsAvailable -= count;
36152          }
36153        };
36154
36155        // (size:int):uint
36156        this.readBits = function (size) {
36157          var bits = Math.min(workingBitsAvailable, size),
36158
36159
36160          // :uint
36161          valu = workingWord >>> 32 - bits; // :uint
36162          // if size > 31, handle error
36163          workingBitsAvailable -= bits;
36164          if (workingBitsAvailable > 0) {
36165            workingWord <<= bits;
36166          } else if (workingBytesAvailable > 0) {
36167            this.loadWord();
36168          }
36169
36170          bits = size - bits;
36171          if (bits > 0) {
36172            return valu << bits | this.readBits(bits);
36173          }
36174          return valu;
36175        };
36176
36177        // ():uint
36178        this.skipLeadingZeros = function () {
36179          var leadingZeroCount; // :uint
36180          for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) {
36181            if ((workingWord & 0x80000000 >>> leadingZeroCount) !== 0) {
36182              // the first bit of working word is 1
36183              workingWord <<= leadingZeroCount;
36184              workingBitsAvailable -= leadingZeroCount;
36185              return leadingZeroCount;
36186            }
36187          }
36188
36189          // we exhausted workingWord and still have not found a 1
36190          this.loadWord();
36191          return leadingZeroCount + this.skipLeadingZeros();
36192        };
36193
36194        // ():void
36195        this.skipUnsignedExpGolomb = function () {
36196          this.skipBits(1 + this.skipLeadingZeros());
36197        };
36198
36199        // ():void
36200        this.skipExpGolomb = function () {
36201          this.skipBits(1 + this.skipLeadingZeros());
36202        };
36203
36204        // ():uint
36205        this.readUnsignedExpGolomb = function () {
36206          var clz = this.skipLeadingZeros(); // :uint
36207          return this.readBits(clz + 1) - 1;
36208        };
36209
36210        // ():int
36211        this.readExpGolomb = function () {
36212          var valu = this.readUnsignedExpGolomb(); // :int
36213          if (0x01 & valu) {
36214            // the number is odd if the low order bit is set
36215            return 1 + valu >>> 1; // add 1 to make it even, and divide by 2
36216          }
36217          return -1 * (valu >>> 1); // divide by two then make it negative
36218        };
36219
36220        // Some convenience functions
36221        // :Boolean
36222        this.readBoolean = function () {
36223          return this.readBits(1) === 1;
36224        };
36225
36226        // ():int
36227        this.readUnsignedByte = function () {
36228          return this.readBits(8);
36229        };
36230
36231        this.loadWord();
36232      };
36233
36234      var expGolomb = ExpGolomb;
36235
36236      var _H264Stream, _NalByteStream;
36237      var PROFILES_WITH_OPTIONAL_SPS_DATA;
36238
36239      /**
36240       * Accepts a NAL unit byte stream and unpacks the embedded NAL units.
36241       */
36242      _NalByteStream = function NalByteStream() {
36243        var syncPoint = 0,
36244            i,
36245            buffer;
36246        _NalByteStream.prototype.init.call(this);
36247
36248        this.push = function (data) {
36249          var swapBuffer;
36250
36251          if (!buffer) {
36252            buffer = data.data;
36253          } else {
36254            swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength);
36255            swapBuffer.set(buffer);
36256            swapBuffer.set(data.data, buffer.byteLength);
36257            buffer = swapBuffer;
36258          }
36259
36260          // Rec. ITU-T H.264, Annex B
36261          // scan for NAL unit boundaries
36262
36263          // a match looks like this:
36264          // 0 0 1 .. NAL .. 0 0 1
36265          // ^ sync point        ^ i
36266          // or this:
36267          // 0 0 1 .. NAL .. 0 0 0
36268          // ^ sync point        ^ i
36269
36270          // advance the sync point to a NAL start, if necessary
36271          for (; syncPoint < buffer.byteLength - 3; syncPoint++) {
36272            if (buffer[syncPoint + 2] === 1) {
36273              // the sync point is properly aligned
36274              i = syncPoint + 5;
36275              break;
36276            }
36277          }
36278
36279          while (i < buffer.byteLength) {
36280            // look at the current byte to determine if we've hit the end of
36281            // a NAL unit boundary
36282            switch (buffer[i]) {
36283              case 0:
36284                // skip past non-sync sequences
36285                if (buffer[i - 1] !== 0) {
36286                  i += 2;
36287                  break;
36288                } else if (buffer[i - 2] !== 0) {
36289                  i++;
36290                  break;
36291                }
36292
36293                // deliver the NAL unit if it isn't empty
36294                if (syncPoint + 3 !== i - 2) {
36295                  this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
36296                }
36297
36298                // drop trailing zeroes
36299                do {
36300                  i++;
36301                } while (buffer[i] !== 1 && i < buffer.length);
36302                syncPoint = i - 2;
36303                i += 3;
36304                break;
36305              case 1:
36306                // skip past non-sync sequences
36307                if (buffer[i - 1] !== 0 || buffer[i - 2] !== 0) {
36308                  i += 3;
36309                  break;
36310                }
36311
36312                // deliver the NAL unit
36313                this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
36314                syncPoint = i - 2;
36315                i += 3;
36316                break;
36317              default:
36318                // the current byte isn't a one or zero, so it cannot be part
36319                // of a sync sequence
36320                i += 3;
36321                break;
36322            }
36323          }
36324          // filter out the NAL units that were delivered
36325          buffer = buffer.subarray(syncPoint);
36326          i -= syncPoint;
36327          syncPoint = 0;
36328        };
36329
36330        this.flush = function () {
36331          // deliver the last buffered NAL unit
36332          if (buffer && buffer.byteLength > 3) {
36333            this.trigger('data', buffer.subarray(syncPoint + 3));
36334          }
36335          // reset the stream state
36336          buffer = null;
36337          syncPoint = 0;
36338          this.trigger('done');
36339        };
36340      };
36341      _NalByteStream.prototype = new stream();
36342
36343      // values of profile_idc that indicate additional fields are included in the SPS
36344      // see Recommendation ITU-T H.264 (4/2013),
36345      // 7.3.2.1.1 Sequence parameter set data syntax
36346      PROFILES_WITH_OPTIONAL_SPS_DATA = {
36347        100: true,
36348        110: true,
36349        122: true,
36350        244: true,
36351        44: true,
36352        83: true,
36353        86: true,
36354        118: true,
36355        128: true,
36356        138: true,
36357        139: true,
36358        134: true
36359      };
36360
36361      /**
36362       * Accepts input from a ElementaryStream and produces H.264 NAL unit data
36363       * events.
36364       */
36365      _H264Stream = function H264Stream() {
36366        var nalByteStream = new _NalByteStream(),
36367            self,
36368            trackId,
36369            currentPts,
36370            currentDts,
36371            discardEmulationPreventionBytes,
36372            readSequenceParameterSet,
36373            skipScalingList;
36374
36375        _H264Stream.prototype.init.call(this);
36376        self = this;
36377
36378        this.push = function (packet) {
36379          if (packet.type !== 'video') {
36380            return;
36381          }
36382          trackId = packet.trackId;
36383          currentPts = packet.pts;
36384          currentDts = packet.dts;
36385
36386          nalByteStream.push(packet);
36387        };
36388
36389        nalByteStream.on('data', function (data) {
36390          var event = {
36391            trackId: trackId,
36392            pts: currentPts,
36393            dts: currentDts,
36394            data: data
36395          };
36396
36397          switch (data[0] & 0x1f) {
36398            case 0x05:
36399              event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr';
36400              break;
36401            case 0x06:
36402              event.nalUnitType = 'sei_rbsp';
36403              event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
36404              break;
36405            case 0x07:
36406              event.nalUnitType = 'seq_parameter_set_rbsp';
36407              event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
36408              event.config = readSequenceParameterSet(event.escapedRBSP);
36409              break;
36410            case 0x08:
36411              event.nalUnitType = 'pic_parameter_set_rbsp';
36412              break;
36413            case 0x09:
36414              event.nalUnitType = 'access_unit_delimiter_rbsp';
36415              break;
36416
36417            default:
36418              break;
36419          }
36420          self.trigger('data', event);
36421        });
36422        nalByteStream.on('done', function () {
36423          self.trigger('done');
36424        });
36425
36426        this.flush = function () {
36427          nalByteStream.flush();
36428        };
36429
36430        /**
36431         * Advance the ExpGolomb decoder past a scaling list. The scaling
36432         * list is optionally transmitted as part of a sequence parameter
36433         * set and is not relevant to transmuxing.
36434         * @param count {number} the number of entries in this scaling list
36435         * @param expGolombDecoder {object} an ExpGolomb pointed to the
36436         * start of a scaling list
36437         * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
36438         */
36439        skipScalingList = function skipScalingList(count, expGolombDecoder) {
36440          var lastScale = 8,
36441              nextScale = 8,
36442              j,
36443              deltaScale;
36444
36445          for (j = 0; j < count; j++) {
36446            if (nextScale !== 0) {
36447              deltaScale = expGolombDecoder.readExpGolomb();
36448              nextScale = (lastScale + deltaScale + 256) % 256;
36449            }
36450
36451            lastScale = nextScale === 0 ? lastScale : nextScale;
36452          }
36453        };
36454
36455        /**
36456         * Expunge any "Emulation Prevention" bytes from a "Raw Byte
36457         * Sequence Payload"
36458         * @param data {Uint8Array} the bytes of a RBSP from a NAL
36459         * unit
36460         * @return {Uint8Array} the RBSP without any Emulation
36461         * Prevention Bytes
36462         */
36463        discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) {
36464          var length = data.byteLength,
36465              emulationPreventionBytesPositions = [],
36466              i = 1,
36467              newLength,
36468              newData;
36469
36470          // Find all `Emulation Prevention Bytes`
36471          while (i < length - 2) {
36472            if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
36473              emulationPreventionBytesPositions.push(i + 2);
36474              i += 2;
36475            } else {
36476              i++;
36477            }
36478          }
36479
36480          // If no Emulation Prevention Bytes were found just return the original
36481          // array
36482          if (emulationPreventionBytesPositions.length === 0) {
36483            return data;
36484          }
36485
36486          // Create a new array to hold the NAL unit data
36487          newLength = length - emulationPreventionBytesPositions.length;
36488          newData = new Uint8Array(newLength);
36489          var sourceIndex = 0;
36490
36491          for (i = 0; i < newLength; sourceIndex++, i++) {
36492            if (sourceIndex === emulationPreventionBytesPositions[0]) {
36493              // Skip this byte
36494              sourceIndex++;
36495              // Remove this position index
36496              emulationPreventionBytesPositions.shift();
36497            }
36498            newData[i] = data[sourceIndex];
36499          }
36500
36501          return newData;
36502        };
36503
36504        /**
36505         * Read a sequence parameter set and return some interesting video
36506         * properties. A sequence parameter set is the H264 metadata that
36507         * describes the properties of upcoming video frames.
36508         * @param data {Uint8Array} the bytes of a sequence parameter set
36509         * @return {object} an object with configuration parsed from the
36510         * sequence parameter set, including the dimensions of the
36511         * associated video frames.
36512         */
36513        readSequenceParameterSet = function readSequenceParameterSet(data) {
36514          var frameCropLeftOffset = 0,
36515              frameCropRightOffset = 0,
36516              frameCropTopOffset = 0,
36517              frameCropBottomOffset = 0,
36518              sarScale = 1,
36519              expGolombDecoder,
36520              profileIdc,
36521              levelIdc,
36522              profileCompatibility,
36523              chromaFormatIdc,
36524              picOrderCntType,
36525              numRefFramesInPicOrderCntCycle,
36526              picWidthInMbsMinus1,
36527              picHeightInMapUnitsMinus1,
36528              frameMbsOnlyFlag,
36529              scalingListCount,
36530              sarRatio,
36531              aspectRatioIdc,
36532              i;
36533
36534          expGolombDecoder = new expGolomb(data);
36535          profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc
36536          profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag
36537          levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8)
36538          expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id
36539
36540          // some profiles have more optional data we don't need
36541          if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) {
36542            chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb();
36543            if (chromaFormatIdc === 3) {
36544              expGolombDecoder.skipBits(1); // separate_colour_plane_flag
36545            }
36546            expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
36547            expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
36548            expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag
36549            if (expGolombDecoder.readBoolean()) {
36550              // seq_scaling_matrix_present_flag
36551              scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
36552              for (i = 0; i < scalingListCount; i++) {
36553                if (expGolombDecoder.readBoolean()) {
36554                  // seq_scaling_list_present_flag[ i ]
36555                  if (i < 6) {
36556                    skipScalingList(16, expGolombDecoder);
36557                  } else {
36558                    skipScalingList(64, expGolombDecoder);
36559                  }
36560                }
36561              }
36562            }
36563          }
36564
36565          expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
36566          picOrderCntType = expGolombDecoder.readUnsignedExpGolomb();
36567
36568          if (picOrderCntType === 0) {
36569            expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4
36570          } else if (picOrderCntType === 1) {
36571            expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag
36572            expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic
36573            expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field
36574            numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb();
36575            for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
36576              expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ]
36577            }
36578          }
36579
36580          expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames
36581          expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag
36582
36583          picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
36584          picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
36585
36586          frameMbsOnlyFlag = expGolombDecoder.readBits(1);
36587          if (frameMbsOnlyFlag === 0) {
36588            expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag
36589          }
36590
36591          expGolombDecoder.skipBits(1); // direct_8x8_inference_flag
36592          if (expGolombDecoder.readBoolean()) {
36593            // frame_cropping_flag
36594            frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb();
36595            frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb();
36596            frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb();
36597            frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb();
36598          }
36599          if (expGolombDecoder.readBoolean()) {
36600            // vui_parameters_present_flag
36601            if (expGolombDecoder.readBoolean()) {
36602              // aspect_ratio_info_present_flag
36603              aspectRatioIdc = expGolombDecoder.readUnsignedByte();
36604              switch (aspectRatioIdc) {
36605                case 1:
36606                  sarRatio = [1, 1];break;
36607                case 2:
36608                  sarRatio = [12, 11];break;
36609                case 3:
36610                  sarRatio = [10, 11];break;
36611                case 4:
36612                  sarRatio = [16, 11];break;
36613                case 5:
36614                  sarRatio = [40, 33];break;
36615                case 6:
36616                  sarRatio = [24, 11];break;
36617                case 7:
36618                  sarRatio = [20, 11];break;
36619                case 8:
36620                  sarRatio = [32, 11];break;
36621                case 9:
36622                  sarRatio = [80, 33];break;
36623                case 10:
36624                  sarRatio = [18, 11];break;
36625                case 11:
36626                  sarRatio = [15, 11];break;
36627                case 12:
36628                  sarRatio = [64, 33];break;
36629                case 13:
36630                  sarRatio = [160, 99];break;
36631                case 14:
36632                  sarRatio = [4, 3];break;
36633                case 15:
36634                  sarRatio = [3, 2];break;
36635                case 16:
36636                  sarRatio = [2, 1];break;
36637                case 255:
36638                  {
36639                    sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte(), expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte()];
36640                    break;
36641                  }
36642              }
36643              if (sarRatio) {
36644                sarScale = sarRatio[0] / sarRatio[1];
36645              }
36646            }
36647          }
36648          return {
36649            profileIdc: profileIdc,
36650            levelIdc: levelIdc,
36651            profileCompatibility: profileCompatibility,
36652            width: Math.ceil(((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale),
36653            height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - frameCropTopOffset * 2 - frameCropBottomOffset * 2
36654          };
36655        };
36656      };
36657      _H264Stream.prototype = new stream();
36658
36659      var h264 = {
36660        H264Stream: _H264Stream,
36661        NalByteStream: _NalByteStream
36662      };
36663
36664      // Constants
36665      var _AacStream;
36666
36667      /**
36668       * Splits an incoming stream of binary data into ADTS and ID3 Frames.
36669       */
36670
36671      _AacStream = function AacStream() {
36672        var everything = new Uint8Array(),
36673            timeStamp = 0;
36674
36675        _AacStream.prototype.init.call(this);
36676
36677        this.setTimestamp = function (timestamp) {
36678          timeStamp = timestamp;
36679        };
36680
36681        this.parseId3TagSize = function (header, byteIndex) {
36682          var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9],
36683              flags = header[byteIndex + 5],
36684              footerPresent = (flags & 16) >> 4;
36685
36686          if (footerPresent) {
36687            return returnSize + 20;
36688          }
36689          return returnSize + 10;
36690        };
36691
36692        this.parseAdtsSize = function (header, byteIndex) {
36693          var lowThree = (header[byteIndex + 5] & 0xE0) >> 5,
36694              middle = header[byteIndex + 4] << 3,
36695              highTwo = header[byteIndex + 3] & 0x3 << 11;
36696
36697          return highTwo | middle | lowThree;
36698        };
36699
36700        this.push = function (bytes) {
36701          var frameSize = 0,
36702              byteIndex = 0,
36703              bytesLeft,
36704              chunk,
36705              packet,
36706              tempLength;
36707
36708          // If there are bytes remaining from the last segment, prepend them to the
36709          // bytes that were pushed in
36710          if (everything.length) {
36711            tempLength = everything.length;
36712            everything = new Uint8Array(bytes.byteLength + tempLength);
36713            everything.set(everything.subarray(0, tempLength));
36714            everything.set(bytes, tempLength);
36715          } else {
36716            everything = bytes;
36717          }
36718
36719          while (everything.length - byteIndex >= 3) {
36720            if (everything[byteIndex] === 'I'.charCodeAt(0) && everything[byteIndex + 1] === 'D'.charCodeAt(0) && everything[byteIndex + 2] === '3'.charCodeAt(0)) {
36721
36722              // Exit early because we don't have enough to parse
36723              // the ID3 tag header
36724              if (everything.length - byteIndex < 10) {
36725                break;
36726              }
36727
36728              // check framesize
36729              frameSize = this.parseId3TagSize(everything, byteIndex);
36730
36731              // Exit early if we don't have enough in the buffer
36732              // to emit a full packet
36733              if (frameSize > everything.length) {
36734                break;
36735              }
36736              chunk = {
36737                type: 'timed-metadata',
36738                data: everything.subarray(byteIndex, byteIndex + frameSize)
36739              };
36740              this.trigger('data', chunk);
36741              byteIndex += frameSize;
36742              continue;
36743            } else if (everything[byteIndex] & 0xff === 0xff && (everything[byteIndex + 1] & 0xf0) === 0xf0) {
36744
36745              // Exit early because we don't have enough to parse
36746              // the ADTS frame header
36747              if (everything.length - byteIndex < 7) {
36748                break;
36749              }
36750
36751              frameSize = this.parseAdtsSize(everything, byteIndex);
36752
36753              // Exit early if we don't have enough in the buffer
36754              // to emit a full packet
36755              if (frameSize > everything.length) {
36756                break;
36757              }
36758
36759              packet = {
36760                type: 'audio',
36761                data: everything.subarray(byteIndex, byteIndex + frameSize),
36762                pts: timeStamp,
36763                dts: timeStamp
36764              };
36765              this.trigger('data', packet);
36766              byteIndex += frameSize;
36767              continue;
36768            }
36769            byteIndex++;
36770          }
36771          bytesLeft = everything.length - byteIndex;
36772
36773          if (bytesLeft > 0) {
36774            everything = everything.subarray(byteIndex);
36775          } else {
36776            everything = new Uint8Array();
36777          }
36778        };
36779      };
36780
36781      _AacStream.prototype = new stream();
36782
36783      var aac = _AacStream;
36784
36785      var highPrefix = [33, 16, 5, 32, 164, 27];
36786      var lowPrefix = [33, 65, 108, 84, 1, 2, 4, 8, 168, 2, 4, 8, 17, 191, 252];
36787      var zeroFill = function zeroFill(count) {
36788        var a = [];
36789        while (count--) {
36790          a.push(0);
36791        }
36792        return a;
36793      };
36794
36795      var makeTable = function makeTable(metaTable) {
36796        return Object.keys(metaTable).reduce(function (obj, key) {
36797          obj[key] = new Uint8Array(metaTable[key].reduce(function (arr, part) {
36798            return arr.concat(part);
36799          }, []));
36800          return obj;
36801        }, {});
36802      };
36803
36804      // Frames-of-silence to use for filling in missing AAC frames
36805      var coneOfSilence = {
36806        96000: [highPrefix, [227, 64], zeroFill(154), [56]],
36807        88200: [highPrefix, [231], zeroFill(170), [56]],
36808        64000: [highPrefix, [248, 192], zeroFill(240), [56]],
36809        48000: [highPrefix, [255, 192], zeroFill(268), [55, 148, 128], zeroFill(54), [112]],
36810        44100: [highPrefix, [255, 192], zeroFill(268), [55, 163, 128], zeroFill(84), [112]],
36811        32000: [highPrefix, [255, 192], zeroFill(268), [55, 234], zeroFill(226), [112]],
36812        24000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 112], zeroFill(126), [224]],
36813        16000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 255], zeroFill(269), [223, 108], zeroFill(195), [1, 192]],
36814        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]],
36815        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]],
36816        8000: [lowPrefix, zeroFill(268), [3, 121, 16], zeroFill(47), [7]]
36817      };
36818
36819      var silence = makeTable(coneOfSilence);
36820
36821      var ONE_SECOND_IN_TS = 90000,
36822
36823
36824      // 90kHz clock
36825      secondsToVideoTs,
36826          secondsToAudioTs,
36827          videoTsToSeconds,
36828          audioTsToSeconds,
36829          audioTsToVideoTs,
36830          videoTsToAudioTs;
36831
36832      secondsToVideoTs = function secondsToVideoTs(seconds) {
36833        return seconds * ONE_SECOND_IN_TS;
36834      };
36835
36836      secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) {
36837        return seconds * sampleRate;
36838      };
36839
36840      videoTsToSeconds = function videoTsToSeconds(timestamp) {
36841        return timestamp / ONE_SECOND_IN_TS;
36842      };
36843
36844      audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) {
36845        return timestamp / sampleRate;
36846      };
36847
36848      audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) {
36849        return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate));
36850      };
36851
36852      videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) {
36853        return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate);
36854      };
36855
36856      var clock = {
36857        secondsToVideoTs: secondsToVideoTs,
36858        secondsToAudioTs: secondsToAudioTs,
36859        videoTsToSeconds: videoTsToSeconds,
36860        audioTsToSeconds: audioTsToSeconds,
36861        audioTsToVideoTs: audioTsToVideoTs,
36862        videoTsToAudioTs: videoTsToAudioTs
36863      };
36864
36865      var H264Stream = h264.H264Stream;
36866
36867      // constants
36868      var AUDIO_PROPERTIES = ['audioobjecttype', 'channelcount', 'samplerate', 'samplingfrequencyindex', 'samplesize'];
36869
36870      var VIDEO_PROPERTIES = ['width', 'height', 'profileIdc', 'levelIdc', 'profileCompatibility'];
36871
36872      var ONE_SECOND_IN_TS$1 = 90000; // 90kHz clock
36873
36874      // object types
36875      var _VideoSegmentStream, _AudioSegmentStream, _Transmuxer, _CoalesceStream;
36876
36877      // Helper functions
36878      var createDefaultSample, isLikelyAacData, collectDtsInfo, clearDtsInfo, calculateTrackBaseMediaDecodeTime, arrayEquals, sumFrameByteLengths;
36879
36880      /**
36881       * Default sample object
36882       * see ISO/IEC 14496-12:2012, section 8.6.4.3
36883       */
36884      createDefaultSample = function createDefaultSample() {
36885        return {
36886          size: 0,
36887          flags: {
36888            isLeading: 0,
36889            dependsOn: 1,
36890            isDependedOn: 0,
36891            hasRedundancy: 0,
36892            degradationPriority: 0
36893          }
36894        };
36895      };
36896
36897      isLikelyAacData = function isLikelyAacData(data) {
36898        if (data[0] === 'I'.charCodeAt(0) && data[1] === 'D'.charCodeAt(0) && data[2] === '3'.charCodeAt(0)) {
36899          return true;
36900        }
36901        return false;
36902      };
36903
36904      /**
36905       * Compare two arrays (even typed) for same-ness
36906       */
36907      arrayEquals = function arrayEquals(a, b) {
36908        var i;
36909
36910        if (a.length !== b.length) {
36911          return false;
36912        }
36913
36914        // compare the value of each element in the array
36915        for (i = 0; i < a.length; i++) {
36916          if (a[i] !== b[i]) {
36917            return false;
36918          }
36919        }
36920
36921        return true;
36922      };
36923
36924      /**
36925       * Sum the `byteLength` properties of the data in each AAC frame
36926       */
36927      sumFrameByteLengths = function sumFrameByteLengths(array) {
36928        var i,
36929            currentObj,
36930            sum = 0;
36931
36932        // sum the byteLength's all each nal unit in the frame
36933        for (i = 0; i < array.length; i++) {
36934          currentObj = array[i];
36935          sum += currentObj.data.byteLength;
36936        }
36937
36938        return sum;
36939      };
36940
36941      /**
36942       * Constructs a single-track, ISO BMFF media segment from AAC data
36943       * events. The output of this stream can be fed to a SourceBuffer
36944       * configured with a suitable initialization segment.
36945       * @param track {object} track metadata configuration
36946       * @param options {object} transmuxer options object
36947       * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
36948       *        in the source; false to adjust the first segment to start at 0.
36949       */
36950      _AudioSegmentStream = function AudioSegmentStream(track, options) {
36951        var adtsFrames = [],
36952            sequenceNumber = 0,
36953            earliestAllowedDts = 0,
36954            audioAppendStartTs = 0,
36955            videoBaseMediaDecodeTime = Infinity;
36956
36957        options = options || {};
36958
36959        _AudioSegmentStream.prototype.init.call(this);
36960
36961        this.push = function (data) {
36962          collectDtsInfo(track, data);
36963
36964          if (track) {
36965            AUDIO_PROPERTIES.forEach(function (prop) {
36966              track[prop] = data[prop];
36967            });
36968          }
36969
36970          // buffer audio data until end() is called
36971          adtsFrames.push(data);
36972        };
36973
36974        this.setEarliestDts = function (earliestDts) {
36975          earliestAllowedDts = earliestDts - track.timelineStartInfo.baseMediaDecodeTime;
36976        };
36977
36978        this.setVideoBaseMediaDecodeTime = function (baseMediaDecodeTime) {
36979          videoBaseMediaDecodeTime = baseMediaDecodeTime;
36980        };
36981
36982        this.setAudioAppendStart = function (timestamp) {
36983          audioAppendStartTs = timestamp;
36984        };
36985
36986        this.flush = function () {
36987          var frames, moof, mdat, boxes;
36988
36989          // return early if no audio data has been observed
36990          if (adtsFrames.length === 0) {
36991            this.trigger('done', 'AudioSegmentStream');
36992            return;
36993          }
36994
36995          frames = this.trimAdtsFramesByEarliestDts_(adtsFrames);
36996          track.baseMediaDecodeTime = calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
36997
36998          this.prefixWithSilence_(track, frames);
36999
37000          // we have to build the index from byte locations to
37001          // samples (that is, adts frames) in the audio data
37002          track.samples = this.generateSampleTable_(frames);
37003
37004          // concatenate the audio data to constuct the mdat
37005          mdat = mp4Generator.mdat(this.concatenateFrameData_(frames));
37006
37007          adtsFrames = [];
37008
37009          moof = mp4Generator.moof(sequenceNumber, [track]);
37010          boxes = new Uint8Array(moof.byteLength + mdat.byteLength);
37011
37012          // bump the sequence number for next time
37013          sequenceNumber++;
37014
37015          boxes.set(moof);
37016          boxes.set(mdat, moof.byteLength);
37017
37018          clearDtsInfo(track);
37019
37020          this.trigger('data', { track: track, boxes: boxes });
37021          this.trigger('done', 'AudioSegmentStream');
37022        };
37023
37024        // Possibly pad (prefix) the audio track with silence if appending this track
37025        // would lead to the introduction of a gap in the audio buffer
37026        this.prefixWithSilence_ = function (track, frames) {
37027          var baseMediaDecodeTimeTs,
37028              frameDuration = 0,
37029              audioGapDuration = 0,
37030              audioFillFrameCount = 0,
37031              audioFillDuration = 0,
37032              silentFrame,
37033              i;
37034
37035          if (!frames.length) {
37036            return;
37037          }
37038
37039          baseMediaDecodeTimeTs = clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate);
37040          // determine frame clock duration based on sample rate, round up to avoid overfills
37041          frameDuration = Math.ceil(ONE_SECOND_IN_TS$1 / (track.samplerate / 1024));
37042
37043          if (audioAppendStartTs && videoBaseMediaDecodeTime) {
37044            // insert the shortest possible amount (audio gap or audio to video gap)
37045            audioGapDuration = baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime);
37046            // number of full frames in the audio gap
37047            audioFillFrameCount = Math.floor(audioGapDuration / frameDuration);
37048            audioFillDuration = audioFillFrameCount * frameDuration;
37049          }
37050
37051          // don't attempt to fill gaps smaller than a single frame or larger
37052          // than a half second
37053          if (audioFillFrameCount < 1 || audioFillDuration > ONE_SECOND_IN_TS$1 / 2) {
37054            return;
37055          }
37056
37057          silentFrame = silence[track.samplerate];
37058
37059          if (!silentFrame) {
37060            // we don't have a silent frame pregenerated for the sample rate, so use a frame
37061            // from the content instead
37062            silentFrame = frames[0].data;
37063          }
37064
37065          for (i = 0; i < audioFillFrameCount; i++) {
37066            frames.splice(i, 0, {
37067              data: silentFrame
37068            });
37069          }
37070
37071          track.baseMediaDecodeTime -= Math.floor(clock.videoTsToAudioTs(audioFillDuration, track.samplerate));
37072        };
37073
37074        // If the audio segment extends before the earliest allowed dts
37075        // value, remove AAC frames until starts at or after the earliest
37076        // allowed DTS so that we don't end up with a negative baseMedia-
37077        // DecodeTime for the audio track
37078        this.trimAdtsFramesByEarliestDts_ = function (adtsFrames) {
37079          if (track.minSegmentDts >= earliestAllowedDts) {
37080            return adtsFrames;
37081          }
37082
37083          // We will need to recalculate the earliest segment Dts
37084          track.minSegmentDts = Infinity;
37085
37086          return adtsFrames.filter(function (currentFrame) {
37087            // If this is an allowed frame, keep it and record it's Dts
37088            if (currentFrame.dts >= earliestAllowedDts) {
37089              track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts);
37090              track.minSegmentPts = track.minSegmentDts;
37091              return true;
37092            }
37093            // Otherwise, discard it
37094            return false;
vendor: 20,968 bytes, lines 37095-37683
37095          });
37096        };
37097
37098        // generate the track's raw mdat data from an array of frames
37099        this.generateSampleTable_ = function (frames) {
37100          var i,
37101              currentFrame,
37102              samples = [];
37103
37104          for (i = 0; i < frames.length; i++) {
37105            currentFrame = frames[i];
37106            samples.push({
37107              size: currentFrame.data.byteLength,
37108              duration: 1024 // For AAC audio, all samples contain 1024 samples
37109            });
37110          }
37111          return samples;
37112        };
37113
37114        // generate the track's sample table from an array of frames
37115        this.concatenateFrameData_ = function (frames) {
37116          var i,
37117              currentFrame,
37118              dataOffset = 0,
37119              data = new Uint8Array(sumFrameByteLengths(frames));
37120
37121          for (i = 0; i < frames.length; i++) {
37122            currentFrame = frames[i];
37123
37124            data.set(currentFrame.data, dataOffset);
37125            dataOffset += currentFrame.data.byteLength;
37126          }
37127          return data;
37128        };
37129      };
37130
37131      _AudioSegmentStream.prototype = new stream();
37132
37133      /**
37134       * Constructs a single-track, ISO BMFF media segment from H264 data
37135       * events. The output of this stream can be fed to a SourceBuffer
37136       * configured with a suitable initialization segment.
37137       * @param track {object} track metadata configuration
37138       * @param options {object} transmuxer options object
37139       * @param options.alignGopsAtEnd {boolean} If true, start from the end of the
37140       *        gopsToAlignWith list when attempting to align gop pts
37141       * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
37142       *        in the source; false to adjust the first segment to start at 0.
37143       */
37144      _VideoSegmentStream = function VideoSegmentStream(track, options) {
37145        var sequenceNumber = 0,
37146            nalUnits = [],
37147            gopsToAlignWith = [],
37148            config,
37149            pps;
37150
37151        options = options || {};
37152
37153        _VideoSegmentStream.prototype.init.call(this);
37154
37155        delete track.minPTS;
37156
37157        this.gopCache_ = [];
37158
37159        this.push = function (nalUnit) {
37160          collectDtsInfo(track, nalUnit);
37161
37162          // record the track config
37163          if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) {
37164            config = nalUnit.config;
37165            track.sps = [nalUnit.data];
37166
37167            VIDEO_PROPERTIES.forEach(function (prop) {
37168              track[prop] = config[prop];
37169            }, this);
37170          }
37171
37172          if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && !pps) {
37173            pps = nalUnit.data;
37174            track.pps = [nalUnit.data];
37175          }
37176
37177          // buffer video until flush() is called
37178          nalUnits.push(nalUnit);
37179        };
37180
37181        this.flush = function () {
37182          var frames, gopForFusion, gops, moof, mdat, boxes;
37183
37184          // Throw away nalUnits at the start of the byte stream until
37185          // we find the first AUD
37186          while (nalUnits.length) {
37187            if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
37188              break;
37189            }
37190            nalUnits.shift();
37191          }
37192
37193          // Return early if no video data has been observed
37194          if (nalUnits.length === 0) {
37195            this.resetStream_();
37196            this.trigger('done', 'VideoSegmentStream');
37197            return;
37198          }
37199
37200          // Organize the raw nal-units into arrays that represent
37201          // higher-level constructs such as frames and gops
37202          // (group-of-pictures)
37203          frames = this.groupNalsIntoFrames_(nalUnits);
37204          gops = this.groupFramesIntoGops_(frames);
37205
37206          // If the first frame of this fragment is not a keyframe we have
37207          // a problem since MSE (on Chrome) requires a leading keyframe.
37208          //
37209          // We have two approaches to repairing this situation:
37210          // 1) GOP-FUSION:
37211          //    This is where we keep track of the GOPS (group-of-pictures)
37212          //    from previous fragments and attempt to find one that we can
37213          //    prepend to the current fragment in order to create a valid
37214          //    fragment.
37215          // 2) KEYFRAME-PULLING:
37216          //    Here we search for the first keyframe in the fragment and
37217          //    throw away all the frames between the start of the fragment
37218          //    and that keyframe. We then extend the duration and pull the
37219          //    PTS of the keyframe forward so that it covers the time range
37220          //    of the frames that were disposed of.
37221          //
37222          // #1 is far prefereable over #2 which can cause "stuttering" but
37223          // requires more things to be just right.
37224          if (!gops[0][0].keyFrame) {
37225            // Search for a gop for fusion from our gopCache
37226            gopForFusion = this.getGopForFusion_(nalUnits[0], track);
37227
37228            if (gopForFusion) {
37229              gops.unshift(gopForFusion);
37230              // Adjust Gops' metadata to account for the inclusion of the
37231              // new gop at the beginning
37232              gops.byteLength += gopForFusion.byteLength;
37233              gops.nalCount += gopForFusion.nalCount;
37234              gops.pts = gopForFusion.pts;
37235              gops.dts = gopForFusion.dts;
37236              gops.duration += gopForFusion.duration;
37237            } else {
37238              // If we didn't find a candidate gop fall back to keyframe-pulling
37239              gops = this.extendFirstKeyFrame_(gops);
37240            }
37241          }
37242
37243          // Trim gops to align with gopsToAlignWith
37244          if (gopsToAlignWith.length) {
37245            var alignedGops;
37246
37247            if (options.alignGopsAtEnd) {
37248              alignedGops = this.alignGopsAtEnd_(gops);
37249            } else {
37250              alignedGops = this.alignGopsAtStart_(gops);
37251            }
37252
37253            if (!alignedGops) {
37254              // save all the nals in the last GOP into the gop cache
37255              this.gopCache_.unshift({
37256                gop: gops.pop(),
37257                pps: track.pps,
37258                sps: track.sps
37259              });
37260
37261              // Keep a maximum of 6 GOPs in the cache
37262              this.gopCache_.length = Math.min(6, this.gopCache_.length);
37263
37264              // Clear nalUnits
37265              nalUnits = [];
37266
37267              // return early no gops can be aligned with desired gopsToAlignWith
37268              this.resetStream_();
37269              this.trigger('done', 'VideoSegmentStream');
37270              return;
37271            }
37272
37273            // Some gops were trimmed. clear dts info so minSegmentDts and pts are correct
37274            // when recalculated before sending off to CoalesceStream
37275            clearDtsInfo(track);
37276
37277            gops = alignedGops;
37278          }
37279
37280          collectDtsInfo(track, gops);
37281
37282          // First, we have to build the index from byte locations to
37283          // samples (that is, frames) in the video data
37284          track.samples = this.generateSampleTable_(gops);
37285
37286          // Concatenate the video data and construct the mdat
37287          mdat = mp4Generator.mdat(this.concatenateNalData_(gops));
37288
37289          track.baseMediaDecodeTime = calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps);
37290
37291          this.trigger('processedGopsInfo', gops.map(function (gop) {
37292            return {
37293              pts: gop.pts,
37294              dts: gop.dts,
37295              byteLength: gop.byteLength
37296            };
37297          }));
37298
37299          // save all the nals in the last GOP into the gop cache
37300          this.gopCache_.unshift({
37301            gop: gops.pop(),
37302            pps: track.pps,
37303            sps: track.sps
37304          });
37305
37306          // Keep a maximum of 6 GOPs in the cache
37307          this.gopCache_.length = Math.min(6, this.gopCache_.length);
37308
37309          // Clear nalUnits
37310          nalUnits = [];
37311
37312          this.trigger('baseMediaDecodeTime', track.baseMediaDecodeTime);
37313          this.trigger('timelineStartInfo', track.timelineStartInfo);
37314
37315          moof = mp4Generator.moof(sequenceNumber, [track]);
37316
37317          // it would be great to allocate this array up front instead of
37318          // throwing away hundreds of media segment fragments
37319          boxes = new Uint8Array(moof.byteLength + mdat.byteLength);
37320
37321          // Bump the sequence number for next time
37322          sequenceNumber++;
37323
37324          boxes.set(moof);
37325          boxes.set(mdat, moof.byteLength);
37326
37327          this.trigger('data', { track: track, boxes: boxes });
37328
37329          this.resetStream_();
37330
37331          // Continue with the flush process now
37332          this.trigger('done', 'VideoSegmentStream');
37333        };
37334
37335        this.resetStream_ = function () {
37336          clearDtsInfo(track);
37337
37338          // reset config and pps because they may differ across segments
37339          // for instance, when we are rendition switching
37340          config = undefined;
37341          pps = undefined;
37342        };
37343
37344        // Search for a candidate Gop for gop-fusion from the gop cache and
37345        // return it or return null if no good candidate was found
37346        this.getGopForFusion_ = function (nalUnit) {
37347          var halfSecond = 45000,
37348
37349
37350          // Half-a-second in a 90khz clock
37351          allowableOverlap = 10000,
37352
37353
37354          // About 3 frames @ 30fps
37355          nearestDistance = Infinity,
37356              dtsDistance,
37357              nearestGopObj,
37358              currentGop,
37359              currentGopObj,
37360              i;
37361
37362          // Search for the GOP nearest to the beginning of this nal unit
37363          for (i = 0; i < this.gopCache_.length; i++) {
37364            currentGopObj = this.gopCache_[i];
37365            currentGop = currentGopObj.gop;
37366
37367            // Reject Gops with different SPS or PPS
37368            if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) || !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) {
37369              continue;
37370            }
37371
37372            // Reject Gops that would require a negative baseMediaDecodeTime
37373            if (currentGop.dts < track.timelineStartInfo.dts) {
37374              continue;
37375            }
37376
37377            // The distance between the end of the gop and the start of the nalUnit
37378            dtsDistance = nalUnit.dts - currentGop.dts - currentGop.duration;
37379
37380            // Only consider GOPS that start before the nal unit and end within
37381            // a half-second of the nal unit
37382            if (dtsDistance >= -allowableOverlap && dtsDistance <= halfSecond) {
37383
37384              // Always use the closest GOP we found if there is more than
37385              // one candidate
37386              if (!nearestGopObj || nearestDistance > dtsDistance) {
37387                nearestGopObj = currentGopObj;
37388                nearestDistance = dtsDistance;
37389              }
37390            }
37391          }
37392
37393          if (nearestGopObj) {
37394            return nearestGopObj.gop;
37395          }
37396          return null;
37397        };
37398
37399        this.extendFirstKeyFrame_ = function (gops) {
37400          var currentGop;
37401
37402          if (!gops[0][0].keyFrame && gops.length > 1) {
37403            // Remove the first GOP
37404            currentGop = gops.shift();
37405
37406            gops.byteLength -= currentGop.byteLength;
37407            gops.nalCount -= currentGop.nalCount;
37408
37409            // Extend the first frame of what is now the
37410            // first gop to cover the time period of the
37411            // frames we just removed
37412            gops[0][0].dts = currentGop.dts;
37413            gops[0][0].pts = currentGop.pts;
37414            gops[0][0].duration += currentGop.duration;
37415          }
37416
37417          return gops;
37418        };
37419
37420        // Convert an array of nal units into an array of frames with each frame being
37421        // composed of the nal units that make up that frame
37422        // Also keep track of cummulative data about the frame from the nal units such
37423        // as the frame duration, starting pts, etc.
37424        this.groupNalsIntoFrames_ = function (nalUnits) {
37425          var i,
37426              currentNal,
37427              currentFrame = [],
37428              frames = [];
37429
37430          currentFrame.byteLength = 0;
37431
37432          for (i = 0; i < nalUnits.length; i++) {
37433            currentNal = nalUnits[i];
37434
37435            // Split on 'aud'-type nal units
37436            if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
37437              // Since the very first nal unit is expected to be an AUD
37438              // only push to the frames array when currentFrame is not empty
37439              if (currentFrame.length) {
37440                currentFrame.duration = currentNal.dts - currentFrame.dts;
37441                frames.push(currentFrame);
37442              }
37443              currentFrame = [currentNal];
37444              currentFrame.byteLength = currentNal.data.byteLength;
37445              currentFrame.pts = currentNal.pts;
37446              currentFrame.dts = currentNal.dts;
37447            } else {
37448              // Specifically flag key frames for ease of use later
37449              if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
37450                currentFrame.keyFrame = true;
37451              }
37452              currentFrame.duration = currentNal.dts - currentFrame.dts;
37453              currentFrame.byteLength += currentNal.data.byteLength;
37454              currentFrame.push(currentNal);
37455            }
37456          }
37457
37458          // For the last frame, use the duration of the previous frame if we
37459          // have nothing better to go on
37460          if (frames.length && (!currentFrame.duration || currentFrame.duration <= 0)) {
37461            currentFrame.duration = frames[frames.length - 1].duration;
37462          }
37463
37464          // Push the final frame
37465          frames.push(currentFrame);
37466          return frames;
37467        };
37468
37469        // Convert an array of frames into an array of Gop with each Gop being composed
37470        // of the frames that make up that Gop
37471        // Also keep track of cummulative data about the Gop from the frames such as the
37472        // Gop duration, starting pts, etc.
37473        this.groupFramesIntoGops_ = function (frames) {
37474          var i,
37475              currentFrame,
37476              currentGop = [],
37477              gops = [];
37478
37479          // We must pre-set some of the values on the Gop since we
37480          // keep running totals of these values
37481          currentGop.byteLength = 0;
37482          currentGop.nalCount = 0;
37483          currentGop.duration = 0;
37484          currentGop.pts = frames[0].pts;
37485          currentGop.dts = frames[0].dts;
37486
37487          // store some metadata about all the Gops
37488          gops.byteLength = 0;
37489          gops.nalCount = 0;
37490          gops.duration = 0;
37491          gops.pts = frames[0].pts;
37492          gops.dts = frames[0].dts;
37493
37494          for (i = 0; i < frames.length; i++) {
37495            currentFrame = frames[i];
37496
37497            if (currentFrame.keyFrame) {
37498              // Since the very first frame is expected to be an keyframe
37499              // only push to the gops array when currentGop is not empty
37500              if (currentGop.length) {
37501                gops.push(currentGop);
37502                gops.byteLength += currentGop.byteLength;
37503                gops.nalCount += currentGop.nalCount;
37504                gops.duration += currentGop.duration;
37505              }
37506
37507              currentGop = [currentFrame];
37508              currentGop.nalCount = currentFrame.length;
37509              currentGop.byteLength = currentFrame.byteLength;
37510              currentGop.pts = currentFrame.pts;
37511              currentGop.dts = currentFrame.dts;
37512              currentGop.duration = currentFrame.duration;
37513            } else {
37514              currentGop.duration += currentFrame.duration;
37515              currentGop.nalCount += currentFrame.length;
37516              currentGop.byteLength += currentFrame.byteLength;
37517              currentGop.push(currentFrame);
37518            }
37519          }
37520
37521          if (gops.length && currentGop.duration <= 0) {
37522            currentGop.duration = gops[gops.length - 1].duration;
37523          }
37524          gops.byteLength += currentGop.byteLength;
37525          gops.nalCount += currentGop.nalCount;
37526          gops.duration += currentGop.duration;
37527
37528          // push the final Gop
37529          gops.push(currentGop);
37530          return gops;
37531        };
37532
37533        // generate the track's sample table from an array of gops
37534        this.generateSampleTable_ = function (gops, baseDataOffset) {
37535          var h,
37536              i,
37537              sample,
37538              currentGop,
37539              currentFrame,
37540              dataOffset = baseDataOffset || 0,
37541              samples = [];
37542
37543          for (h = 0; h < gops.length; h++) {
37544            currentGop = gops[h];
37545
37546            for (i = 0; i < currentGop.length; i++) {
37547              currentFrame = currentGop[i];
37548
37549              sample = createDefaultSample();
37550
37551              sample.dataOffset = dataOffset;
37552              sample.compositionTimeOffset = currentFrame.pts - currentFrame.dts;
37553              sample.duration = currentFrame.duration;
37554              sample.size = 4 * currentFrame.length; // Space for nal unit size
37555              sample.size += currentFrame.byteLength;
37556
37557              if (currentFrame.keyFrame) {
37558                sample.flags.dependsOn = 2;
37559              }
37560
37561              dataOffset += sample.size;
37562
37563              samples.push(sample);
37564            }
37565          }
37566          return samples;
37567        };
37568
37569        // generate the track's raw mdat data from an array of gops
37570        this.concatenateNalData_ = function (gops) {
37571          var h,
37572              i,
37573              j,
37574              currentGop,
37575              currentFrame,
37576              currentNal,
37577              dataOffset = 0,
37578              nalsByteLength = gops.byteLength,
37579              numberOfNals = gops.nalCount,
37580              totalByteLength = nalsByteLength + 4 * numberOfNals,
37581              data = new Uint8Array(totalByteLength),
37582              view = new DataView(data.buffer);
37583
37584          // For each Gop..
37585          for (h = 0; h < gops.length; h++) {
37586            currentGop = gops[h];
37587
37588            // For each Frame..
37589            for (i = 0; i < currentGop.length; i++) {
37590              currentFrame = currentGop[i];
37591
37592              // For each NAL..
37593              for (j = 0; j < currentFrame.length; j++) {
37594                currentNal = currentFrame[j];
37595
37596                view.setUint32(dataOffset, currentNal.data.byteLength);
37597                dataOffset += 4;
37598                data.set(currentNal.data, dataOffset);
37599                dataOffset += currentNal.data.byteLength;
37600              }
37601            }
37602          }
37603          return data;
37604        };
37605
37606        // trim gop list to the first gop found that has a matching pts with a gop in the list
37607        // of gopsToAlignWith starting from the START of the list
37608        this.alignGopsAtStart_ = function (gops) {
37609          var alignIndex, gopIndex, align, gop, byteLength, nalCount, duration, alignedGops;
37610
37611          byteLength = gops.byteLength;
37612          nalCount = gops.nalCount;
37613          duration = gops.duration;
37614          alignIndex = gopIndex = 0;
37615
37616          while (alignIndex < gopsToAlignWith.length && gopIndex < gops.length) {
37617            align = gopsToAlignWith[alignIndex];
37618            gop = gops[gopIndex];
37619
37620            if (align.pts === gop.pts) {
37621              break;
37622            }
37623
37624            if (gop.pts > align.pts) {
37625              // this current gop starts after the current gop we want to align on, so increment
37626              // align index
37627              alignIndex++;
37628              continue;
37629            }
37630
37631            // current gop starts before the current gop we want to align on. so increment gop
37632            // index
37633            gopIndex++;
37634            byteLength -= gop.byteLength;
37635            nalCount -= gop.nalCount;
37636            duration -= gop.duration;
37637          }
37638
37639          if (gopIndex === 0) {
37640            // no gops to trim
37641            return gops;
37642          }
37643
37644          if (gopIndex === gops.length) {
37645            // all gops trimmed, skip appending all gops
37646            return null;
37647          }
37648
37649          alignedGops = gops.slice(gopIndex);
37650          alignedGops.byteLength = byteLength;
37651          alignedGops.duration = duration;
37652          alignedGops.nalCount = nalCount;
37653          alignedGops.pts = alignedGops[0].pts;
37654          alignedGops.dts = alignedGops[0].dts;
37655
37656          return alignedGops;
37657        };
37658
37659        // trim gop list to the first gop found that has a matching pts with a gop in the list
37660        // of gopsToAlignWith starting from the END of the list
37661        this.alignGopsAtEnd_ = function (gops) {
37662          var alignIndex, gopIndex, align, gop, alignEndIndex, matchFound;
37663
37664          alignIndex = gopsToAlignWith.length - 1;
37665          gopIndex = gops.length - 1;
37666          alignEndIndex = null;
37667          matchFound = false;
37668
37669          while (alignIndex >= 0 && gopIndex >= 0) {
37670            align = gopsToAlignWith[alignIndex];
37671            gop = gops[gopIndex];
37672
37673            if (align.pts === gop.pts) {
37674              matchFound = true;
37675              break;
37676            }
37677
37678            if (align.pts > gop.pts) {
37679              alignIndex--;
37680              continue;
37681            }
37682
37683            if (alignIndex === gopsToAlignWith.length - 1) {
vendor: 6,958 bytes, lines 37684-37886
37684              // gop.pts is greater than the last alignment candidate. If no match is found
37685              // by the end of this loop, we still want to append gops that come after this
37686              // point
37687              alignEndIndex = gopIndex;
37688            }
37689
37690            gopIndex--;
37691          }
37692
37693          if (!matchFound && alignEndIndex === null) {
37694            return null;
37695          }
37696
37697          var trimIndex;
37698
37699          if (matchFound) {
37700            trimIndex = gopIndex;
37701          } else {
37702            trimIndex = alignEndIndex;
37703          }
37704
37705          if (trimIndex === 0) {
37706            return gops;
37707          }
37708
37709          var alignedGops = gops.slice(trimIndex);
37710          var metadata = alignedGops.reduce(function (total, gop) {
37711            total.byteLength += gop.byteLength;
37712            total.duration += gop.duration;
37713            total.nalCount += gop.nalCount;
37714            return total;
37715          }, { byteLength: 0, duration: 0, nalCount: 0 });
37716
37717          alignedGops.byteLength = metadata.byteLength;
37718          alignedGops.duration = metadata.duration;
37719          alignedGops.nalCount = metadata.nalCount;
37720          alignedGops.pts = alignedGops[0].pts;
37721          alignedGops.dts = alignedGops[0].dts;
37722
37723          return alignedGops;
37724        };
37725
37726        this.alignGopsWith = function (newGopsToAlignWith) {
37727          gopsToAlignWith = newGopsToAlignWith;
37728        };
37729      };
37730
37731      _VideoSegmentStream.prototype = new stream();
37732
37733      /**
37734       * Store information about the start and end of the track and the
37735       * duration for each frame/sample we process in order to calculate
37736       * the baseMediaDecodeTime
37737       */
37738      collectDtsInfo = function collectDtsInfo(track, data) {
37739        if (typeof data.pts === 'number') {
37740          if (track.timelineStartInfo.pts === undefined) {
37741            track.timelineStartInfo.pts = data.pts;
37742          }
37743
37744          if (track.minSegmentPts === undefined) {
37745            track.minSegmentPts = data.pts;
37746          } else {
37747            track.minSegmentPts = Math.min(track.minSegmentPts, data.pts);
37748          }
37749
37750          if (track.maxSegmentPts === undefined) {
37751            track.maxSegmentPts = data.pts;
37752          } else {
37753            track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts);
37754          }
37755        }
37756
37757        if (typeof data.dts === 'number') {
37758          if (track.timelineStartInfo.dts === undefined) {
37759            track.timelineStartInfo.dts = data.dts;
37760          }
37761
37762          if (track.minSegmentDts === undefined) {
37763            track.minSegmentDts = data.dts;
37764          } else {
37765            track.minSegmentDts = Math.min(track.minSegmentDts, data.dts);
37766          }
37767
37768          if (track.maxSegmentDts === undefined) {
37769            track.maxSegmentDts = data.dts;
37770          } else {
37771            track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts);
37772          }
37773        }
37774      };
37775
37776      /**
37777       * Clear values used to calculate the baseMediaDecodeTime between
37778       * tracks
37779       */
37780      clearDtsInfo = function clearDtsInfo(track) {
37781        delete track.minSegmentDts;
37782        delete track.maxSegmentDts;
37783        delete track.minSegmentPts;
37784        delete track.maxSegmentPts;
37785      };
37786
37787      /**
37788       * Calculate the track's baseMediaDecodeTime based on the earliest
37789       * DTS the transmuxer has ever seen and the minimum DTS for the
37790       * current track
37791       * @param track {object} track metadata configuration
37792       * @param keepOriginalTimestamps {boolean} If true, keep the timestamps
37793       *        in the source; false to adjust the first segment to start at 0.
37794       */
37795      calculateTrackBaseMediaDecodeTime = function calculateTrackBaseMediaDecodeTime(track, keepOriginalTimestamps) {
37796        var baseMediaDecodeTime,
37797            scale,
37798            minSegmentDts = track.minSegmentDts;
37799
37800        // Optionally adjust the time so the first segment starts at zero.
37801        if (!keepOriginalTimestamps) {
37802          minSegmentDts -= track.timelineStartInfo.dts;
37803        }
37804
37805        // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where
37806        // we want the start of the first segment to be placed
37807        baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime;
37808
37809        // Add to that the distance this segment is from the very first
37810        baseMediaDecodeTime += minSegmentDts;
37811
37812        // baseMediaDecodeTime must not become negative
37813        baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime);
37814
37815        if (track.type === 'audio') {
37816          // Audio has a different clock equal to the sampling_rate so we need to
37817          // scale the PTS values into the clock rate of the track
37818          scale = track.samplerate / ONE_SECOND_IN_TS$1;
37819          baseMediaDecodeTime *= scale;
37820          baseMediaDecodeTime = Math.floor(baseMediaDecodeTime);
37821        }
37822
37823        return baseMediaDecodeTime;
37824      };
37825
37826      /**
37827       * A Stream that can combine multiple streams (ie. audio & video)
37828       * into a single output segment for MSE. Also supports audio-only
37829       * and video-only streams.
37830       */
37831      _CoalesceStream = function CoalesceStream(options, metadataStream) {
37832        // Number of Tracks per output segment
37833        // If greater than 1, we combine multiple
37834        // tracks into a single segment
37835        this.numberOfTracks = 0;
37836        this.metadataStream = metadataStream;
37837
37838        if (typeof options.remux !== 'undefined') {
37839          this.remuxTracks = !!options.remux;
37840        } else {
37841          this.remuxTracks = true;
37842        }
37843
37844        this.pendingTracks = [];
37845        this.videoTrack = null;
37846        this.pendingBoxes = [];
37847        this.pendingCaptions = [];
37848        this.pendingMetadata = [];
37849        this.pendingBytes = 0;
37850        this.emittedTracks = 0;
37851
37852        _CoalesceStream.prototype.init.call(this);
37853
37854        // Take output from multiple
37855        this.push = function (output) {
37856          // buffer incoming captions until the associated video segment
37857          // finishes
37858          if (output.text) {
37859            return this.pendingCaptions.push(output);
37860          }
37861          // buffer incoming id3 tags until the final flush
37862          if (output.frames) {
37863            return this.pendingMetadata.push(output);
37864          }
37865
37866          // Add this track to the list of pending tracks and store
37867          // important information required for the construction of
37868          // the final segment
37869          this.pendingTracks.push(output.track);
37870          this.pendingBoxes.push(output.boxes);
37871          this.pendingBytes += output.boxes.byteLength;
37872
37873          if (output.track.type === 'video') {
37874            this.videoTrack = output.track;
37875          }
37876          if (output.track.type === 'audio') {
37877            this.audioTrack = output.track;
37878          }
37879        };
37880      };
37881
37882      _CoalesceStream.prototype = new stream();
37883      _CoalesceStream.prototype.flush = function (flushSource) {
37884        var offset = 0,
37885            event = {
37886          captions: [],
vendor: 14,628 bytes, lines 37887-38217
37887          captionStreams: {},
37888          metadata: [],
37889          info: {}
37890        },
37891            caption,
37892            id3,
37893            initSegment,
37894            timelineStartPts = 0,
37895            i;
37896
37897        if (this.pendingTracks.length < this.numberOfTracks) {
37898          if (flushSource !== 'VideoSegmentStream' && flushSource !== 'AudioSegmentStream') {
37899            // Return because we haven't received a flush from a data-generating
37900            // portion of the segment (meaning that we have only recieved meta-data
37901            // or captions.)
37902            return;
37903          } else if (this.remuxTracks) {
37904            // Return until we have enough tracks from the pipeline to remux (if we
37905            // are remuxing audio and video into a single MP4)
37906            return;
37907          } else if (this.pendingTracks.length === 0) {
37908            // In the case where we receive a flush without any data having been
37909            // received we consider it an emitted track for the purposes of coalescing
37910            // `done` events.
37911            // We do this for the case where there is an audio and video track in the
37912            // segment but no audio data. (seen in several playlists with alternate
37913            // audio tracks and no audio present in the main TS segments.)
37914            this.emittedTracks++;
37915
37916            if (this.emittedTracks >= this.numberOfTracks) {
37917              this.trigger('done');
37918              this.emittedTracks = 0;
37919            }
37920            return;
37921          }
37922        }
37923
37924        if (this.videoTrack) {
37925          timelineStartPts = this.videoTrack.timelineStartInfo.pts;
37926          VIDEO_PROPERTIES.forEach(function (prop) {
37927            event.info[prop] = this.videoTrack[prop];
37928          }, this);
37929        } else if (this.audioTrack) {
37930          timelineStartPts = this.audioTrack.timelineStartInfo.pts;
37931          AUDIO_PROPERTIES.forEach(function (prop) {
37932            event.info[prop] = this.audioTrack[prop];
37933          }, this);
37934        }
37935
37936        if (this.pendingTracks.length === 1) {
37937          event.type = this.pendingTracks[0].type;
37938        } else {
37939          event.type = 'combined';
37940        }
37941
37942        this.emittedTracks += this.pendingTracks.length;
37943
37944        initSegment = mp4Generator.initSegment(this.pendingTracks);
37945
37946        // Create a new typed array to hold the init segment
37947        event.initSegment = new Uint8Array(initSegment.byteLength);
37948
37949        // Create an init segment containing a moov
37950        // and track definitions
37951        event.initSegment.set(initSegment);
37952
37953        // Create a new typed array to hold the moof+mdats
37954        event.data = new Uint8Array(this.pendingBytes);
37955
37956        // Append each moof+mdat (one per track) together
37957        for (i = 0; i < this.pendingBoxes.length; i++) {
37958          event.data.set(this.pendingBoxes[i], offset);
37959          offset += this.pendingBoxes[i].byteLength;
37960        }
37961
37962        // Translate caption PTS times into second offsets into the
37963        // video timeline for the segment, and add track info
37964        for (i = 0; i < this.pendingCaptions.length; i++) {
37965          caption = this.pendingCaptions[i];
37966          caption.startTime = caption.startPts - timelineStartPts;
37967          caption.startTime /= 90e3;
37968          caption.endTime = caption.endPts - timelineStartPts;
37969          caption.endTime /= 90e3;
37970          event.captionStreams[caption.stream] = true;
37971          event.captions.push(caption);
37972        }
37973
37974        // Translate ID3 frame PTS times into second offsets into the
37975        // video timeline for the segment
37976        for (i = 0; i < this.pendingMetadata.length; i++) {
37977          id3 = this.pendingMetadata[i];
37978          id3.cueTime = id3.pts - timelineStartPts;
37979          id3.cueTime /= 90e3;
37980          event.metadata.push(id3);
37981        }
37982        // We add this to every single emitted segment even though we only need
37983        // it for the first
37984        event.metadata.dispatchType = this.metadataStream.dispatchType;
37985
37986        // Reset stream state
37987        this.pendingTracks.length = 0;
37988        this.videoTrack = null;
37989        this.pendingBoxes.length = 0;
37990        this.pendingCaptions.length = 0;
37991        this.pendingBytes = 0;
37992        this.pendingMetadata.length = 0;
37993
37994        // Emit the built segment
37995        this.trigger('data', event);
37996
37997        // Only emit `done` if all tracks have been flushed and emitted
37998        if (this.emittedTracks >= this.numberOfTracks) {
37999          this.trigger('done');
38000          this.emittedTracks = 0;
38001        }
38002      };
38003      /**
38004       * A Stream that expects MP2T binary data as input and produces
38005       * corresponding media segments, suitable for use with Media Source
38006       * Extension (MSE) implementations that support the ISO BMFF byte
38007       * stream format, like Chrome.
38008       */
38009      _Transmuxer = function Transmuxer(options) {
38010        var self = this,
38011            hasFlushed = true,
38012            videoTrack,
38013            audioTrack;
38014
38015        _Transmuxer.prototype.init.call(this);
38016
38017        options = options || {};
38018        this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0;
38019        this.transmuxPipeline_ = {};
38020
38021        this.setupAacPipeline = function () {
38022          var pipeline = {};
38023          this.transmuxPipeline_ = pipeline;
38024
38025          pipeline.type = 'aac';
38026          pipeline.metadataStream = new m2ts_1.MetadataStream();
38027
38028          // set up the parsing pipeline
38029          pipeline.aacStream = new aac();
38030          pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio');
38031          pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata');
38032          pipeline.adtsStream = new adts();
38033          pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream);
38034          pipeline.headOfPipeline = pipeline.aacStream;
38035
38036          pipeline.aacStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
38037          pipeline.aacStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream);
38038
38039          pipeline.metadataStream.on('timestamp', function (frame) {
38040            pipeline.aacStream.setTimestamp(frame.timeStamp);
38041          });
38042
38043          pipeline.aacStream.on('data', function (data) {
38044            if (data.type === 'timed-metadata' && !pipeline.audioSegmentStream) {
38045              audioTrack = audioTrack || {
38046                timelineStartInfo: {
38047                  baseMediaDecodeTime: self.baseMediaDecodeTime
38048                },
38049                codec: 'adts',
38050                type: 'audio'
38051              };
38052              // hook up the audio segment stream to the first track with aac data
38053              pipeline.coalesceStream.numberOfTracks++;
38054              pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options);
38055              // Set up the final part of the audio pipeline
38056              pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
38057            }
38058          });
38059
38060          // Re-emit any data coming from the coalesce stream to the outside world
38061          pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data'));
38062          // Let the consumer know we have finished flushing the entire pipeline
38063          pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
38064        };
38065
38066        this.setupTsPipeline = function () {
38067          var pipeline = {};
38068          this.transmuxPipeline_ = pipeline;
38069
38070          pipeline.type = 'ts';
38071          pipeline.metadataStream = new m2ts_1.MetadataStream();
38072
38073          // set up the parsing pipeline
38074          pipeline.packetStream = new m2ts_1.TransportPacketStream();
38075          pipeline.parseStream = new m2ts_1.TransportParseStream();
38076          pipeline.elementaryStream = new m2ts_1.ElementaryStream();
38077          pipeline.videoTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('video');
38078          pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio');
38079          pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata');
38080          pipeline.adtsStream = new adts();
38081          pipeline.h264Stream = new H264Stream();
38082          pipeline.captionStream = new m2ts_1.CaptionStream();
38083          pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream);
38084          pipeline.headOfPipeline = pipeline.packetStream;
38085
38086          // disassemble MPEG2-TS packets into elementary streams
38087          pipeline.packetStream.pipe(pipeline.parseStream).pipe(pipeline.elementaryStream);
38088
38089          // !!THIS ORDER IS IMPORTANT!!
38090          // demux the streams
38091          pipeline.elementaryStream.pipe(pipeline.videoTimestampRolloverStream).pipe(pipeline.h264Stream);
38092          pipeline.elementaryStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream);
38093
38094          pipeline.elementaryStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream);
38095
38096          // Hook up CEA-608/708 caption stream
38097          pipeline.h264Stream.pipe(pipeline.captionStream).pipe(pipeline.coalesceStream);
38098
38099          pipeline.elementaryStream.on('data', function (data) {
38100            var i;
38101
38102            if (data.type === 'metadata') {
38103              i = data.tracks.length;
38104
38105              // scan the tracks listed in the metadata
38106              while (i--) {
38107                if (!videoTrack && data.tracks[i].type === 'video') {
38108                  videoTrack = data.tracks[i];
38109                  videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
38110                } else if (!audioTrack && data.tracks[i].type === 'audio') {
38111                  audioTrack = data.tracks[i];
38112                  audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
38113                }
38114              }
38115
38116              // hook up the video segment stream to the first track with h264 data
38117              if (videoTrack && !pipeline.videoSegmentStream) {
38118                pipeline.coalesceStream.numberOfTracks++;
38119                pipeline.videoSegmentStream = new _VideoSegmentStream(videoTrack, options);
38120
38121                pipeline.videoSegmentStream.on('timelineStartInfo', function (timelineStartInfo) {
38122                  // When video emits timelineStartInfo data after a flush, we forward that
38123                  // info to the AudioSegmentStream, if it exists, because video timeline
38124                  // data takes precedence.
38125                  if (audioTrack) {
38126                    audioTrack.timelineStartInfo = timelineStartInfo;
38127                    // On the first segment we trim AAC frames that exist before the
38128                    // very earliest DTS we have seen in video because Chrome will
38129                    // interpret any video track with a baseMediaDecodeTime that is
38130                    // non-zero as a gap.
38131                    pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts);
38132                  }
38133                });
38134
38135                pipeline.videoSegmentStream.on('processedGopsInfo', self.trigger.bind(self, 'gopInfo'));
38136
38137                pipeline.videoSegmentStream.on('baseMediaDecodeTime', function (baseMediaDecodeTime) {
38138                  if (audioTrack) {
38139                    pipeline.audioSegmentStream.setVideoBaseMediaDecodeTime(baseMediaDecodeTime);
38140                  }
38141                });
38142
38143                // Set up the final part of the video pipeline
38144                pipeline.h264Stream.pipe(pipeline.videoSegmentStream).pipe(pipeline.coalesceStream);
38145              }
38146
38147              if (audioTrack && !pipeline.audioSegmentStream) {
38148                // hook up the audio segment stream to the first track with aac data
38149                pipeline.coalesceStream.numberOfTracks++;
38150                pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options);
38151
38152                // Set up the final part of the audio pipeline
38153                pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream);
38154              }
38155            }
38156          });
38157
38158          // Re-emit any data coming from the coalesce stream to the outside world
38159          pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data'));
38160          // Let the consumer know we have finished flushing the entire pipeline
38161          pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
38162        };
38163
38164        // hook up the segment streams once track metadata is delivered
38165        this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) {
38166          var pipeline = this.transmuxPipeline_;
38167
38168          this.baseMediaDecodeTime = baseMediaDecodeTime;
38169          if (audioTrack) {
38170            audioTrack.timelineStartInfo.dts = undefined;
38171            audioTrack.timelineStartInfo.pts = undefined;
38172            clearDtsInfo(audioTrack);
38173            audioTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime;
38174            if (pipeline.audioTimestampRolloverStream) {
38175              pipeline.audioTimestampRolloverStream.discontinuity();
38176            }
38177          }
38178          if (videoTrack) {
38179            if (pipeline.videoSegmentStream) {
38180              pipeline.videoSegmentStream.gopCache_ = [];
38181              pipeline.videoTimestampRolloverStream.discontinuity();
38182            }
38183            videoTrack.timelineStartInfo.dts = undefined;
38184            videoTrack.timelineStartInfo.pts = undefined;
38185            clearDtsInfo(videoTrack);
38186            pipeline.captionStream.reset();
38187            videoTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime;
38188          }
38189
38190          if (pipeline.timedMetadataTimestampRolloverStream) {
38191            pipeline.timedMetadataTimestampRolloverStream.discontinuity();
38192          }
38193        };
38194
38195        this.setAudioAppendStart = function (timestamp) {
38196          if (audioTrack) {
38197            this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(timestamp);
38198          }
38199        };
38200
38201        this.alignGopsWith = function (gopsToAlignWith) {
38202          if (videoTrack && this.transmuxPipeline_.videoSegmentStream) {
38203            this.transmuxPipeline_.videoSegmentStream.alignGopsWith(gopsToAlignWith);
38204          }
38205        };
38206
38207        // feed incoming data to the front of the parsing pipeline
38208        this.push = function (data) {
38209          if (hasFlushed) {
38210            var isAac = isLikelyAacData(data);
38211
38212            if (isAac && this.transmuxPipeline_.type !== 'aac') {
38213              this.setupAacPipeline();
38214            } else if (!isAac && this.transmuxPipeline_.type !== 'ts') {
38215              this.setupTsPipeline();
38216            }
38217            hasFlushed = false;
vendor: 76,965 bytes, lines 38218-40556
38218          }
38219          this.transmuxPipeline_.headOfPipeline.push(data);
38220        };
38221
38222        // flush any buffered data
38223        this.flush = function () {
38224          hasFlushed = true;
38225          // Start at the top of the pipeline and flush all pending work
38226          this.transmuxPipeline_.headOfPipeline.flush();
38227        };
38228
38229        // Caption data has to be reset when seeking outside buffered range
38230        this.resetCaptions = function () {
38231          if (this.transmuxPipeline_.captionStream) {
38232            this.transmuxPipeline_.captionStream.reset();
38233          }
38234        };
38235      };
38236      _Transmuxer.prototype = new stream();
38237
38238      var transmuxer = {
38239        Transmuxer: _Transmuxer,
38240        VideoSegmentStream: _VideoSegmentStream,
38241        AudioSegmentStream: _AudioSegmentStream,
38242        AUDIO_PROPERTIES: AUDIO_PROPERTIES,
38243        VIDEO_PROPERTIES: VIDEO_PROPERTIES
38244      };
38245
38246      var mp4 = {
38247        generator: mp4Generator,
38248        Transmuxer: transmuxer.Transmuxer,
38249        AudioSegmentStream: transmuxer.AudioSegmentStream,
38250        VideoSegmentStream: transmuxer.VideoSegmentStream
38251      };
38252
38253      var classCallCheck$$1 = function classCallCheck$$1(instance, Constructor) {
38254        if (!(instance instanceof Constructor)) {
38255          throw new TypeError("Cannot call a class as a function");
38256        }
38257      };
38258
38259      var createClass$$1 = function () {
38260        function defineProperties(target, props) {
38261          for (var i = 0; i < props.length; i++) {
38262            var descriptor = props[i];
38263            descriptor.enumerable = descriptor.enumerable || false;
38264            descriptor.configurable = true;
38265            if ("value" in descriptor) descriptor.writable = true;
38266            Object.defineProperty(target, descriptor.key, descriptor);
38267          }
38268        }
38269
38270        return function (Constructor, protoProps, staticProps) {
38271          if (protoProps) defineProperties(Constructor.prototype, protoProps);
38272          if (staticProps) defineProperties(Constructor, staticProps);
38273          return Constructor;
38274        };
38275      }();
38276
38277      /**
38278       * @file transmuxer-worker.js
38279       */
38280
38281      /**
38282       * Re-emits transmuxer events by converting them into messages to the
38283       * world outside the worker.
38284       *
38285       * @param {Object} transmuxer the transmuxer to wire events on
38286       * @private
38287       */
38288      var wireTransmuxerEvents = function wireTransmuxerEvents(transmuxer) {
38289        transmuxer.on('data', function (segment) {
38290          // transfer ownership of the underlying ArrayBuffer
38291          // instead of doing a copy to save memory
38292          // ArrayBuffers are transferable but generic TypedArrays are not
38293          // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects)
38294          var initArray = segment.initSegment;
38295
38296          segment.initSegment = {
38297            data: initArray.buffer,
38298            byteOffset: initArray.byteOffset,
38299            byteLength: initArray.byteLength
38300          };
38301
38302          var typedArray = segment.data;
38303
38304          segment.data = typedArray.buffer;
38305          window_1$$1.postMessage({
38306            action: 'data',
38307            segment: segment,
38308            byteOffset: typedArray.byteOffset,
38309            byteLength: typedArray.byteLength
38310          }, [segment.data]);
38311        });
38312
38313        if (transmuxer.captionStream) {
38314          transmuxer.captionStream.on('data', function (caption) {
38315            window_1$$1.postMessage({
38316              action: 'caption',
38317              data: caption
38318            });
38319          });
38320        }
38321
38322        transmuxer.on('done', function (data) {
38323          window_1$$1.postMessage({ action: 'done' });
38324        });
38325
38326        transmuxer.on('gopInfo', function (gopInfo) {
38327          window_1$$1.postMessage({
38328            action: 'gopInfo',
38329            gopInfo: gopInfo
38330          });
38331        });
38332      };
38333
38334      /**
38335       * All incoming messages route through this hash. If no function exists
38336       * to handle an incoming message, then we ignore the message.
38337       *
38338       * @class MessageHandlers
38339       * @param {Object} options the options to initialize with
38340       */
38341
38342      var MessageHandlers = function () {
38343        function MessageHandlers(options) {
38344          classCallCheck$$1(this, MessageHandlers);
38345
38346          this.options = options || {};
38347          this.init();
38348        }
38349
38350        /**
38351         * initialize our web worker and wire all the events.
38352         */
38353
38354        createClass$$1(MessageHandlers, [{
38355          key: 'init',
38356          value: function init() {
38357            if (this.transmuxer) {
38358              this.transmuxer.dispose();
38359            }
38360            this.transmuxer = new mp4.Transmuxer(this.options);
38361            wireTransmuxerEvents(this.transmuxer);
38362          }
38363
38364          /**
38365           * Adds data (a ts segment) to the start of the transmuxer pipeline for
38366           * processing.
38367           *
38368           * @param {ArrayBuffer} data data to push into the muxer
38369           */
38370
38371        }, {
38372          key: 'push',
38373          value: function push(data) {
38374            // Cast array buffer to correct type for transmuxer
38375            var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);
38376
38377            this.transmuxer.push(segment);
38378          }
38379
38380          /**
38381           * Recreate the transmuxer so that the next segment added via `push`
38382           * start with a fresh transmuxer.
38383           */
38384
38385        }, {
38386          key: 'reset',
38387          value: function reset() {
38388            this.init();
38389          }
38390
38391          /**
38392           * Set the value that will be used as the `baseMediaDecodeTime` time for the
38393           * next segment pushed in. Subsequent segments will have their `baseMediaDecodeTime`
38394           * set relative to the first based on the PTS values.
38395           *
38396           * @param {Object} data used to set the timestamp offset in the muxer
38397           */
38398
38399        }, {
38400          key: 'setTimestampOffset',
38401          value: function setTimestampOffset(data) {
38402            var timestampOffset = data.timestampOffset || 0;
38403
38404            this.transmuxer.setBaseMediaDecodeTime(Math.round(timestampOffset * 90000));
38405          }
38406        }, {
38407          key: 'setAudioAppendStart',
38408          value: function setAudioAppendStart(data) {
38409            this.transmuxer.setAudioAppendStart(Math.ceil(data.appendStart * 90000));
38410          }
38411
38412          /**
38413           * Forces the pipeline to finish processing the last segment and emit it's
38414           * results.
38415           *
38416           * @param {Object} data event data, not really used
38417           */
38418
38419        }, {
38420          key: 'flush',
38421          value: function flush(data) {
38422            this.transmuxer.flush();
38423          }
38424        }, {
38425          key: 'resetCaptions',
38426          value: function resetCaptions() {
38427            this.transmuxer.resetCaptions();
38428          }
38429        }, {
38430          key: 'alignGopsWith',
38431          value: function alignGopsWith(data) {
38432            this.transmuxer.alignGopsWith(data.gopsToAlignWith.slice());
38433          }
38434        }]);
38435        return MessageHandlers;
38436      }();
38437
38438      /**
38439       * Our web wroker interface so that things can talk to mux.js
38440       * that will be running in a web worker. the scope is passed to this by
38441       * webworkify.
38442       *
38443       * @param {Object} self the scope for the web worker
38444       */
38445
38446      var TransmuxerWorker = function TransmuxerWorker(self) {
38447        self.onmessage = function (event) {
38448          if (event.data.action === 'init' && event.data.options) {
38449            this.messageHandlers = new MessageHandlers(event.data.options);
38450            return;
38451          }
38452
38453          if (!this.messageHandlers) {
38454            this.messageHandlers = new MessageHandlers();
38455          }
38456
38457          if (event.data && event.data.action && event.data.action !== 'init') {
38458            if (this.messageHandlers[event.data.action]) {
38459              this.messageHandlers[event.data.action](event.data);
38460            }
38461          }
38462        };
38463      };
38464
38465      var transmuxerWorker = new TransmuxerWorker(self);
38466
38467      return transmuxerWorker;
38468    }();
38469  });
38470
38471  /**
38472   * @file codec-utils.js
38473   */
38474
38475  /**
38476   * Check if a codec string refers to an audio codec.
38477   *
38478   * @param {String} codec codec string to check
38479   * @return {Boolean} if this is an audio codec
38480   * @private
38481   */
38482  var isAudioCodec = function isAudioCodec(codec) {
38483    return (/mp4a\.\d+.\d+/i.test(codec)
38484    );
38485  };
38486
38487  /**
38488   * Check if a codec string refers to a video codec.
38489   *
38490   * @param {String} codec codec string to check
38491   * @return {Boolean} if this is a video codec
38492   * @private
38493   */
38494  var isVideoCodec = function isVideoCodec(codec) {
38495    return (/avc1\.[\da-f]+/i.test(codec)
38496    );
38497  };
38498
38499  /**
38500   * Parse a content type header into a type and parameters
38501   * object
38502   *
38503   * @param {String} type the content type header
38504   * @return {Object} the parsed content-type
38505   * @private
38506   */
38507  var parseContentType = function parseContentType(type) {
38508    var object = { type: '', parameters: {} };
38509    var parameters = type.trim().split(';');
38510
38511    // first parameter should always be content-type
38512    object.type = parameters.shift().trim();
38513    parameters.forEach(function (parameter) {
38514      var pair = parameter.trim().split('=');
38515
38516      if (pair.length > 1) {
38517        var name = pair[0].replace(/"/g, '').trim();
38518        var value = pair[1].replace(/"/g, '').trim();
38519
38520        object.parameters[name] = value;
38521      }
38522    });
38523
38524    return object;
38525  };
38526
38527  /**
38528   * Replace the old apple-style `avc1.<dd>.<dd>` codec string with the standard
38529   * `avc1.<hhhhhh>`
38530   *
38531   * @param {Array} codecs an array of codec strings to fix
38532   * @return {Array} the translated codec array
38533   * @private
38534   */
38535  var translateLegacyCodecs = function translateLegacyCodecs(codecs) {
38536    return codecs.map(function (codec) {
38537      return codec.replace(/avc1\.(\d+)\.(\d+)/i, function (orig, profile, avcLevel) {
38538        var profileHex = ('00' + Number(profile).toString(16)).slice(-2);
38539        var avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2);
38540
38541        return 'avc1.' + profileHex + '00' + avcLevelHex;
38542      });
38543    });
38544  };
38545
38546  /**
38547   * @file virtual-source-buffer.js
38548   */
38549
38550  // We create a wrapper around the SourceBuffer so that we can manage the
38551  // state of the `updating` property manually. We have to do this because
38552  // Firefox changes `updating` to false long before triggering `updateend`
38553  // events and that was causing strange problems in videojs-contrib-hls
38554  var makeWrappedSourceBuffer = function makeWrappedSourceBuffer(mediaSource, mimeType) {
38555    var sourceBuffer = mediaSource.addSourceBuffer(mimeType);
38556    var wrapper = Object.create(null);
38557
38558    wrapper.updating = false;
38559    wrapper.realBuffer_ = sourceBuffer;
38560
38561    var _loop = function _loop(key) {
38562      if (typeof sourceBuffer[key] === 'function') {
38563        wrapper[key] = function () {
38564          return sourceBuffer[key].apply(sourceBuffer, arguments);
38565        };
38566      } else if (typeof wrapper[key] === 'undefined') {
38567        Object.defineProperty(wrapper, key, {
38568          get: function get$$1() {
38569            return sourceBuffer[key];
38570          },
38571          set: function set$$1(v) {
38572            return sourceBuffer[key] = v;
38573          }
38574        });
38575      }
38576    };
38577
38578    for (var key in sourceBuffer) {
38579      _loop(key);
38580    }
38581
38582    return wrapper;
38583  };
38584
38585  /**
38586   * Returns a list of gops in the buffer that have a pts value of 3 seconds or more in
38587   * front of current time.
38588   *
38589   * @param {Array} buffer
38590   *        The current buffer of gop information
38591   * @param {Player} player
38592   *        The player instance
38593   * @param {Double} mapping
38594   *        Offset to map display time to stream presentation time
38595   * @return {Array}
38596   *         List of gops considered safe to append over
38597   */
38598  var gopsSafeToAlignWith = function gopsSafeToAlignWith(buffer, player, mapping) {
38599    if (!player || !buffer.length) {
38600      return [];
38601    }
38602
38603    // pts value for current time + 3 seconds to give a bit more wiggle room
38604    var currentTimePts = Math.ceil((player.currentTime() - mapping + 3) * 90000);
38605
38606    var i = void 0;
38607
38608    for (i = 0; i < buffer.length; i++) {
38609      if (buffer[i].pts > currentTimePts) {
38610        break;
38611      }
38612    }
38613
38614    return buffer.slice(i);
38615  };
38616
38617  /**
38618   * Appends gop information (timing and byteLength) received by the transmuxer for the
38619   * gops appended in the last call to appendBuffer
38620   *
38621   * @param {Array} buffer
38622   *        The current buffer of gop information
38623   * @param {Array} gops
38624   *        List of new gop information
38625   * @param {boolean} replace
38626   *        If true, replace the buffer with the new gop information. If false, append the
38627   *        new gop information to the buffer in the right location of time.
38628   * @return {Array}
38629   *         Updated list of gop information
38630   */
38631  var updateGopBuffer = function updateGopBuffer(buffer, gops, replace) {
38632    if (!gops.length) {
38633      return buffer;
38634    }
38635
38636    if (replace) {
38637      // If we are in safe append mode, then completely overwrite the gop buffer
38638      // with the most recent appeneded data. This will make sure that when appending
38639      // future segments, we only try to align with gops that are both ahead of current
38640      // time and in the last segment appended.
38641      return gops.slice();
38642    }
38643
38644    var start = gops[0].pts;
38645
38646    var i = 0;
38647
38648    for (i; i < buffer.length; i++) {
38649      if (buffer[i].pts >= start) {
38650        break;
38651      }
38652    }
38653
38654    return buffer.slice(0, i).concat(gops);
38655  };
38656
38657  /**
38658   * Removes gop information in buffer that overlaps with provided start and end
38659   *
38660   * @param {Array} buffer
38661   *        The current buffer of gop information
38662   * @param {Double} start
38663   *        position to start the remove at
38664   * @param {Double} end
38665   *        position to end the remove at
38666   * @param {Double} mapping
38667   *        Offset to map display time to stream presentation time
38668   */
38669  var removeGopBuffer = function removeGopBuffer(buffer, start, end, mapping) {
38670    var startPts = Math.ceil((start - mapping) * 90000);
38671    var endPts = Math.ceil((end - mapping) * 90000);
38672    var updatedBuffer = buffer.slice();
38673
38674    var i = buffer.length;
38675
38676    while (i--) {
38677      if (buffer[i].pts <= endPts) {
38678        break;
38679      }
38680    }
38681
38682    if (i === -1) {
38683      // no removal because end of remove range is before start of buffer
38684      return updatedBuffer;
38685    }
38686
38687    var j = i + 1;
38688
38689    while (j--) {
38690      if (buffer[j].pts <= startPts) {
38691        break;
38692      }
38693    }
38694
38695    // clamp remove range start to 0 index
38696    j = Math.max(j, 0);
38697
38698    updatedBuffer.splice(j, i - j + 1);
38699
38700    return updatedBuffer;
38701  };
38702
38703  /**
38704   * VirtualSourceBuffers exist so that we can transmux non native formats
38705   * into a native format, but keep the same api as a native source buffer.
38706   * It creates a transmuxer, that works in its own thread (a web worker) and
38707   * that transmuxer muxes the data into a native format. VirtualSourceBuffer will
38708   * then send all of that data to the naive sourcebuffer so that it is
38709   * indestinguishable from a natively supported format.
38710   *
38711   * @param {HtmlMediaSource} mediaSource the parent mediaSource
38712   * @param {Array} codecs array of codecs that we will be dealing with
38713   * @class VirtualSourceBuffer
38714   * @extends video.js.EventTarget
38715   */
38716
38717  var VirtualSourceBuffer = function (_videojs$EventTarget) {
38718    inherits$3(VirtualSourceBuffer, _videojs$EventTarget);
38719
38720    function VirtualSourceBuffer(mediaSource, codecs) {
38721      classCallCheck$3(this, VirtualSourceBuffer);
38722
38723      var _this = possibleConstructorReturn$3(this, (VirtualSourceBuffer.__proto__ || Object.getPrototypeOf(VirtualSourceBuffer)).call(this, videojs$1.EventTarget));
38724
38725      _this.timestampOffset_ = 0;
38726      _this.pendingBuffers_ = [];
38727      _this.bufferUpdating_ = false;
38728
38729      _this.mediaSource_ = mediaSource;
38730      _this.codecs_ = codecs;
38731      _this.audioCodec_ = null;
38732      _this.videoCodec_ = null;
38733      _this.audioDisabled_ = false;
38734      _this.appendAudioInitSegment_ = true;
38735      _this.gopBuffer_ = [];
38736      _this.timeMapping_ = 0;
38737      _this.safeAppend_ = videojs$1.browser.IE_VERSION >= 11;
38738
38739      var options = {
38740        remux: false,
38741        alignGopsAtEnd: _this.safeAppend_
38742      };
38743
38744      _this.codecs_.forEach(function (codec) {
38745        if (isAudioCodec(codec)) {
38746          _this.audioCodec_ = codec;
38747        } else if (isVideoCodec(codec)) {
38748          _this.videoCodec_ = codec;
38749        }
38750      });
38751
38752      // append muxed segments to their respective native buffers as
38753      // soon as they are available
38754      _this.transmuxer_ = new TransmuxWorker();
38755      _this.transmuxer_.postMessage({ action: 'init', options: options });
38756
38757      _this.transmuxer_.onmessage = function (event) {
38758        if (event.data.action === 'data') {
38759          return _this.data_(event);
38760        }
38761
38762        if (event.data.action === 'done') {
38763          return _this.done_(event);
38764        }
38765
38766        if (event.data.action === 'gopInfo') {
38767          return _this.appendGopInfo_(event);
38768        }
38769      };
38770
38771      // this timestampOffset is a property with the side-effect of resetting
38772      // baseMediaDecodeTime in the transmuxer on the setter
38773      Object.defineProperty(_this, 'timestampOffset', {
38774        get: function get$$1() {
38775          return this.timestampOffset_;
38776        },
38777        set: function set$$1(val) {
38778          if (typeof val === 'number' && val >= 0) {
38779            this.timestampOffset_ = val;
38780            this.appendAudioInitSegment_ = true;
38781
38782            // reset gop buffer on timestampoffset as this signals a change in timeline
38783            this.gopBuffer_.length = 0;
38784            this.timeMapping_ = 0;
38785
38786            // We have to tell the transmuxer to set the baseMediaDecodeTime to
38787            // the desired timestampOffset for the next segment
38788            this.transmuxer_.postMessage({
38789              action: 'setTimestampOffset',
38790              timestampOffset: val
38791            });
38792          }
38793        }
38794      });
38795
38796      // setting the append window affects both source buffers
38797      Object.defineProperty(_this, 'appendWindowStart', {
38798        get: function get$$1() {
38799          return (this.videoBuffer_ || this.audioBuffer_).appendWindowStart;
38800        },
38801        set: function set$$1(start) {
38802          if (this.videoBuffer_) {
38803            this.videoBuffer_.appendWindowStart = start;
38804          }
38805          if (this.audioBuffer_) {
38806            this.audioBuffer_.appendWindowStart = start;
38807          }
38808        }
38809      });
38810
38811      // this buffer is "updating" if either of its native buffers are
38812      Object.defineProperty(_this, 'updating', {
38813        get: function get$$1() {
38814          return !!(this.bufferUpdating_ || !this.audioDisabled_ && this.audioBuffer_ && this.audioBuffer_.updating || this.videoBuffer_ && this.videoBuffer_.updating);
38815        }
38816      });
38817
38818      // the buffered property is the intersection of the buffered
38819      // ranges of the native source buffers
38820      Object.defineProperty(_this, 'buffered', {
38821        get: function get$$1() {
38822          var start = null;
38823          var end = null;
38824          var arity = 0;
38825          var extents = [];
38826          var ranges = [];
38827
38828          // neither buffer has been created yet
38829          if (!this.videoBuffer_ && !this.audioBuffer_) {
38830            return videojs$1.createTimeRange();
38831          }
38832
38833          // only one buffer is configured
38834          if (!this.videoBuffer_) {
38835            return this.audioBuffer_.buffered;
38836          }
38837          if (!this.audioBuffer_) {
38838            return this.videoBuffer_.buffered;
38839          }
38840
38841          // both buffers are configured
38842          if (this.audioDisabled_) {
38843            return this.videoBuffer_.buffered;
38844          }
38845
38846          // both buffers are empty
38847          if (this.videoBuffer_.buffered.length === 0 && this.audioBuffer_.buffered.length === 0) {
38848            return videojs$1.createTimeRange();
38849          }
38850
38851          // Handle the case where we have both buffers and create an
38852          // intersection of the two
38853          var videoBuffered = this.videoBuffer_.buffered;
38854          var audioBuffered = this.audioBuffer_.buffered;
38855          var count = videoBuffered.length;
38856
38857          // A) Gather up all start and end times
38858          while (count--) {
38859            extents.push({ time: videoBuffered.start(count), type: 'start' });
38860            extents.push({ time: videoBuffered.end(count), type: 'end' });
38861          }
38862          count = audioBuffered.length;
38863          while (count--) {
38864            extents.push({ time: audioBuffered.start(count), type: 'start' });
38865            extents.push({ time: audioBuffered.end(count), type: 'end' });
38866          }
38867          // B) Sort them by time
38868          extents.sort(function (a, b) {
38869            return a.time - b.time;
38870          });
38871
38872          // C) Go along one by one incrementing arity for start and decrementing
38873          //    arity for ends
38874          for (count = 0; count < extents.length; count++) {
38875            if (extents[count].type === 'start') {
38876              arity++;
38877
38878              // D) If arity is ever incremented to 2 we are entering an
38879              //    overlapping range
38880              if (arity === 2) {
38881                start = extents[count].time;
38882              }
38883            } else if (extents[count].type === 'end') {
38884              arity--;
38885
38886              // E) If arity is ever decremented to 1 we leaving an
38887              //    overlapping range
38888              if (arity === 1) {
38889                end = extents[count].time;
38890              }
38891            }
38892
38893            // F) Record overlapping ranges
38894            if (start !== null && end !== null) {
38895              ranges.push([start, end]);
38896              start = null;
38897              end = null;
38898            }
38899          }
38900
38901          return videojs$1.createTimeRanges(ranges);
38902        }
38903      });
38904      return _this;
38905    }
38906
38907    /**
38908     * When we get a data event from the transmuxer
38909     * we call this function and handle the data that
38910     * was sent to us
38911     *
38912     * @private
38913     * @param {Event} event the data event from the transmuxer
38914     */
38915
38916    createClass$2(VirtualSourceBuffer, [{
38917      key: 'data_',
38918      value: function data_(event) {
38919        var segment = event.data.segment;
38920
38921        // Cast ArrayBuffer to TypedArray
38922        segment.data = new Uint8Array(segment.data, event.data.byteOffset, event.data.byteLength);
38923
38924        segment.initSegment = new Uint8Array(segment.initSegment.data, segment.initSegment.byteOffset, segment.initSegment.byteLength);
38925
38926        createTextTracksIfNecessary(this, this.mediaSource_, segment);
38927
38928        // Add the segments to the pendingBuffers array
38929        this.pendingBuffers_.push(segment);
38930        return;
38931      }
38932
38933      /**
38934       * When we get a done event from the transmuxer
38935       * we call this function and we process all
38936       * of the pending data that we have been saving in the
38937       * data_ function
38938       *
38939       * @private
38940       * @param {Event} event the done event from the transmuxer
38941       */
38942
38943    }, {
38944      key: 'done_',
38945      value: function done_(event) {
38946        // Don't process and append data if the mediaSource is closed
38947        if (this.mediaSource_.readyState === 'closed') {
38948          this.pendingBuffers_.length = 0;
38949          return;
38950        }
38951
38952        // All buffers should have been flushed from the muxer
38953        // start processing anything we have received
38954        this.processPendingSegments_();
38955        return;
38956      }
38957
38958      /**
38959       * Create our internal native audio/video source buffers and add
38960       * event handlers to them with the following conditions:
38961       * 1. they do not already exist on the mediaSource
38962       * 2. this VSB has a codec for them
38963       *
38964       * @private
38965       */
38966
38967    }, {
38968      key: 'createRealSourceBuffers_',
38969      value: function createRealSourceBuffers_() {
38970        var _this2 = this;
38971
38972        var types = ['audio', 'video'];
38973
38974        types.forEach(function (type) {
38975          // Don't create a SourceBuffer of this type if we don't have a
38976          // codec for it
38977          if (!_this2[type + 'Codec_']) {
38978            return;
38979          }
38980
38981          // Do nothing if a SourceBuffer of this type already exists
38982          if (_this2[type + 'Buffer_']) {
38983            return;
38984          }
38985
38986          var buffer = null;
38987
38988          // If the mediasource already has a SourceBuffer for the codec
38989          // use that
38990          if (_this2.mediaSource_[type + 'Buffer_']) {
38991            buffer = _this2.mediaSource_[type + 'Buffer_'];
38992            // In multiple audio track cases, the audio source buffer is disabled
38993            // on the main VirtualSourceBuffer by the HTMLMediaSource much earlier
38994            // than createRealSourceBuffers_ is called to create the second
38995            // VirtualSourceBuffer because that happens as a side-effect of
38996            // videojs-contrib-hls starting the audioSegmentLoader. As a result,
38997            // the audioBuffer is essentially "ownerless" and no one will toggle
38998            // the `updating` state back to false once the `updateend` event is received
38999            //
39000            // Setting `updating` to false manually will work around this
39001            // situation and allow work to continue
39002            buffer.updating = false;
39003          } else {
39004            var codecProperty = type + 'Codec_';
39005            var mimeType = type + '/mp4;codecs="' + _this2[codecProperty] + '"';
39006
39007            buffer = makeWrappedSourceBuffer(_this2.mediaSource_.nativeMediaSource_, mimeType);
39008
39009            _this2.mediaSource_[type + 'Buffer_'] = buffer;
39010          }
39011
39012          _this2[type + 'Buffer_'] = buffer;
39013
39014          // Wire up the events to the SourceBuffer
39015          ['update', 'updatestart', 'updateend'].forEach(function (event) {
39016            buffer.addEventListener(event, function () {
39017              // if audio is disabled
39018              if (type === 'audio' && _this2.audioDisabled_) {
39019                return;
39020              }
39021
39022              if (event === 'updateend') {
39023                _this2[type + 'Buffer_'].updating = false;
39024              }
39025
39026              var shouldTrigger = types.every(function (t) {
39027                // skip checking audio's updating status if audio
39028                // is not enabled
39029                if (t === 'audio' && _this2.audioDisabled_) {
39030                  return true;
39031                }
39032                // if the other type if updating we don't trigger
39033                if (type !== t && _this2[t + 'Buffer_'] && _this2[t + 'Buffer_'].updating) {
39034                  return false;
39035                }
39036                return true;
39037              });
39038
39039              if (shouldTrigger) {
39040                return _this2.trigger(event);
39041              }
39042            });
39043          });
39044        });
39045      }
39046
39047      /**
39048       * Emulate the native mediasource function, but our function will
39049       * send all of the proposed segments to the transmuxer so that we
39050       * can transmux them before we append them to our internal
39051       * native source buffers in the correct format.
39052       *
39053       * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/appendBuffer
39054       * @param {Uint8Array} segment the segment to append to the buffer
39055       */
39056
39057    }, {
39058      key: 'appendBuffer',
39059      value: function appendBuffer(segment) {
39060        // Start the internal "updating" state
39061        this.bufferUpdating_ = true;
39062
39063        if (this.audioBuffer_ && this.audioBuffer_.buffered.length) {
39064          var audioBuffered = this.audioBuffer_.buffered;
39065
39066          this.transmuxer_.postMessage({
39067            action: 'setAudioAppendStart',
39068            appendStart: audioBuffered.end(audioBuffered.length - 1)
39069          });
39070        }
39071
39072        if (this.videoBuffer_) {
39073          this.transmuxer_.postMessage({
39074            action: 'alignGopsWith',
39075            gopsToAlignWith: gopsSafeToAlignWith(this.gopBuffer_, this.mediaSource_.player_, this.timeMapping_)
39076          });
39077        }
39078
39079        this.transmuxer_.postMessage({
39080          action: 'push',
39081          // Send the typed-array of data as an ArrayBuffer so that
39082          // it can be sent as a "Transferable" and avoid the costly
39083          // memory copy
39084          data: segment.buffer,
39085
39086          // To recreate the original typed-array, we need information
39087          // about what portion of the ArrayBuffer it was a view into
39088          byteOffset: segment.byteOffset,
39089          byteLength: segment.byteLength
39090        }, [segment.buffer]);
39091        this.transmuxer_.postMessage({ action: 'flush' });
39092      }
39093
39094      /**
39095       * Appends gop information (timing and byteLength) received by the transmuxer for the
39096       * gops appended in the last call to appendBuffer
39097       *
39098       * @param {Event} event
39099       *        The gopInfo event from the transmuxer
39100       * @param {Array} event.data.gopInfo
39101       *        List of gop info to append
39102       */
39103
39104    }, {
39105      key: 'appendGopInfo_',
39106      value: function appendGopInfo_(event) {
39107        this.gopBuffer_ = updateGopBuffer(this.gopBuffer_, event.data.gopInfo, this.safeAppend_);
39108      }
39109
39110      /**
39111       * Emulate the native mediasource function and remove parts
39112       * of the buffer from any of our internal buffers that exist
39113       *
39114       * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/remove
39115       * @param {Double} start position to start the remove at
39116       * @param {Double} end position to end the remove at
39117       */
39118
39119    }, {
39120      key: 'remove',
39121      value: function remove(start, end) {
39122        if (this.videoBuffer_) {
39123          this.videoBuffer_.updating = true;
39124          this.videoBuffer_.remove(start, end);
39125          this.gopBuffer_ = removeGopBuffer(this.gopBuffer_, start, end, this.timeMapping_);
39126        }
39127        if (!this.audioDisabled_ && this.audioBuffer_) {
39128          this.audioBuffer_.updating = true;
39129          this.audioBuffer_.remove(start, end);
39130        }
39131
39132        // Remove Metadata Cues (id3)
39133        removeCuesFromTrack(start, end, this.metadataTrack_);
39134
39135        // Remove Any Captions
39136        if (this.inbandTextTracks_) {
39137          for (var track in this.inbandTextTracks_) {
39138            removeCuesFromTrack(start, end, this.inbandTextTracks_[track]);
39139          }
39140        }
39141      }
39142
39143      /**
39144       * Process any segments that the muxer has output
39145       * Concatenate segments together based on type and append them into
39146       * their respective sourceBuffers
39147       *
39148       * @private
39149       */
39150
39151    }, {
39152      key: 'processPendingSegments_',
39153      value: function processPendingSegments_() {
39154        var sortedSegments = {
39155          video: {
39156            segments: [],
39157            bytes: 0
39158          },
39159          audio: {
39160            segments: [],
39161            bytes: 0
39162          },
39163          captions: [],
39164          metadata: []
39165        };
39166
39167        // Sort segments into separate video/audio arrays and
39168        // keep track of their total byte lengths
39169        sortedSegments = this.pendingBuffers_.reduce(function (segmentObj, segment) {
39170          var type = segment.type;
39171          var data = segment.data;
39172          var initSegment = segment.initSegment;
39173
39174          segmentObj[type].segments.push(data);
39175          segmentObj[type].bytes += data.byteLength;
39176
39177          segmentObj[type].initSegment = initSegment;
39178
39179          // Gather any captions into a single array
39180          if (segment.captions) {
39181            segmentObj.captions = segmentObj.captions.concat(segment.captions);
39182          }
39183
39184          if (segment.info) {
39185            segmentObj[type].info = segment.info;
39186          }
39187
39188          // Gather any metadata into a single array
39189          if (segment.metadata) {
39190            segmentObj.metadata = segmentObj.metadata.concat(segment.metadata);
39191          }
39192
39193          return segmentObj;
39194        }, sortedSegments);
39195
39196        // Create the real source buffers if they don't exist by now since we
39197        // finally are sure what tracks are contained in the source
39198        if (!this.videoBuffer_ && !this.audioBuffer_) {
39199          // Remove any codecs that may have been specified by default but
39200          // are no longer applicable now
39201          if (sortedSegments.video.bytes === 0) {
39202            this.videoCodec_ = null;
39203          }
39204          if (sortedSegments.audio.bytes === 0) {
39205            this.audioCodec_ = null;
39206          }
39207
39208          this.createRealSourceBuffers_();
39209        }
39210
39211        if (sortedSegments.audio.info) {
39212          this.mediaSource_.trigger({ type: 'audioinfo', info: sortedSegments.audio.info });
39213        }
39214        if (sortedSegments.video.info) {
39215          this.mediaSource_.trigger({ type: 'videoinfo', info: sortedSegments.video.info });
39216        }
39217
39218        if (this.appendAudioInitSegment_) {
39219          if (!this.audioDisabled_ && this.audioBuffer_) {
39220            sortedSegments.audio.segments.unshift(sortedSegments.audio.initSegment);
39221            sortedSegments.audio.bytes += sortedSegments.audio.initSegment.byteLength;
39222          }
39223          this.appendAudioInitSegment_ = false;
39224        }
39225
39226        var triggerUpdateend = false;
39227
39228        // Merge multiple video and audio segments into one and append
39229        if (this.videoBuffer_ && sortedSegments.video.bytes) {
39230          sortedSegments.video.segments.unshift(sortedSegments.video.initSegment);
39231          sortedSegments.video.bytes += sortedSegments.video.initSegment.byteLength;
39232          this.concatAndAppendSegments_(sortedSegments.video, this.videoBuffer_);
39233          // TODO: are video tracks the only ones with text tracks?
39234          addTextTrackData(this, sortedSegments.captions, sortedSegments.metadata);
39235        } else if (this.videoBuffer_ && (this.audioDisabled_ || !this.audioBuffer_)) {
39236          // The transmuxer did not return any bytes of video, meaning it was all trimmed
39237          // for gop alignment. Since we have a video buffer and audio is disabled, updateend
39238          // will never be triggered by this source buffer, which will cause contrib-hls
39239          // to be stuck forever waiting for updateend. If audio is not disabled, updateend
39240          // will be triggered by the audio buffer, which will be sent upwards since the video
39241          // buffer will not be in an updating state.
39242          triggerUpdateend = true;
39243        }
39244
39245        if (!this.audioDisabled_ && this.audioBuffer_) {
39246          this.concatAndAppendSegments_(sortedSegments.audio, this.audioBuffer_);
39247        }
39248
39249        this.pendingBuffers_.length = 0;
39250
39251        if (triggerUpdateend) {
39252          this.trigger('updateend');
39253        }
39254
39255        // We are no longer in the internal "updating" state
39256        this.bufferUpdating_ = false;
39257      }
39258
39259      /**
39260       * Combine all segments into a single Uint8Array and then append them
39261       * to the destination buffer
39262       *
39263       * @param {Object} segmentObj
39264       * @param {SourceBuffer} destinationBuffer native source buffer to append data to
39265       * @private
39266       */
39267
39268    }, {
39269      key: 'concatAndAppendSegments_',
39270      value: function concatAndAppendSegments_(segmentObj, destinationBuffer) {
39271        var offset = 0;
39272        var tempBuffer = void 0;
39273
39274        if (segmentObj.bytes) {
39275          tempBuffer = new Uint8Array(segmentObj.bytes);
39276
39277          // Combine the individual segments into one large typed-array
39278          segmentObj.segments.forEach(function (segment) {
39279            tempBuffer.set(segment, offset);
39280            offset += segment.byteLength;
39281          });
39282
39283          try {
39284            destinationBuffer.updating = true;
39285            destinationBuffer.appendBuffer(tempBuffer);
39286          } catch (error) {
39287            if (this.mediaSource_.player_) {
39288              this.mediaSource_.player_.error({
39289                code: -3,
39290                type: 'APPEND_BUFFER_ERR',
39291                message: error.message,
39292                originalError: error
39293              });
39294            }
39295          }
39296        }
39297      }
39298
39299      /**
39300       * Emulate the native mediasource function. abort any soureBuffer
39301       * actions and throw out any un-appended data.
39302       *
39303       * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/abort
39304       */
39305
39306    }, {
39307      key: 'abort',
39308      value: function abort() {
39309        if (this.videoBuffer_) {
39310          this.videoBuffer_.abort();
39311        }
39312        if (!this.audioDisabled_ && this.audioBuffer_) {
39313          this.audioBuffer_.abort();
39314        }
39315        if (this.transmuxer_) {
39316          this.transmuxer_.postMessage({ action: 'reset' });
39317        }
39318        this.pendingBuffers_.length = 0;
39319        this.bufferUpdating_ = false;
39320      }
39321    }]);
39322    return VirtualSourceBuffer;
39323  }(videojs$1.EventTarget);
39324
39325  /**
39326   * @file html-media-source.js
39327   */
39328
39329  /**
39330   * Our MediaSource implementation in HTML, mimics native
39331   * MediaSource where/if possible.
39332   *
39333   * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource
39334   * @class HtmlMediaSource
39335   * @extends videojs.EventTarget
39336   */
39337
39338  var HtmlMediaSource = function (_videojs$EventTarget) {
39339    inherits$3(HtmlMediaSource, _videojs$EventTarget);
39340
39341    function HtmlMediaSource() {
39342      classCallCheck$3(this, HtmlMediaSource);
39343
39344      var _this = possibleConstructorReturn$3(this, (HtmlMediaSource.__proto__ || Object.getPrototypeOf(HtmlMediaSource)).call(this));
39345
39346      var property = void 0;
39347
39348      _this.nativeMediaSource_ = new window_1.MediaSource();
39349      // delegate to the native MediaSource's methods by default
39350      for (property in _this.nativeMediaSource_) {
39351        if (!(property in HtmlMediaSource.prototype) && typeof _this.nativeMediaSource_[property] === 'function') {
39352          _this[property] = _this.nativeMediaSource_[property].bind(_this.nativeMediaSource_);
39353        }
39354      }
39355
39356      // emulate `duration` and `seekable` until seeking can be
39357      // handled uniformly for live streams
39358      // see https://github.com/w3c/media-source/issues/5
39359      _this.duration_ = NaN;
39360      Object.defineProperty(_this, 'duration', {
39361        get: function get$$1() {
39362          if (this.duration_ === Infinity) {
39363            return this.duration_;
39364          }
39365          return this.nativeMediaSource_.duration;
39366        },
39367        set: function set$$1(duration) {
39368          this.duration_ = duration;
39369          if (duration !== Infinity) {
39370            this.nativeMediaSource_.duration = duration;
39371            return;
39372          }
39373        }
39374      });
39375      Object.defineProperty(_this, 'seekable', {
39376        get: function get$$1() {
39377          if (this.duration_ === Infinity) {
39378            return videojs$1.createTimeRanges([[0, this.nativeMediaSource_.duration]]);
39379          }
39380          return this.nativeMediaSource_.seekable;
39381        }
39382      });
39383
39384      Object.defineProperty(_this, 'readyState', {
39385        get: function get$$1() {
39386          return this.nativeMediaSource_.readyState;
39387        }
39388      });
39389
39390      Object.defineProperty(_this, 'activeSourceBuffers', {
39391        get: function get$$1() {
39392          return this.activeSourceBuffers_;
39393        }
39394      });
39395
39396      // the list of virtual and native SourceBuffers created by this
39397      // MediaSource
39398      _this.sourceBuffers = [];
39399
39400      _this.activeSourceBuffers_ = [];
39401
39402      /**
39403       * update the list of active source buffers based upon various
39404       * imformation from HLS and video.js
39405       *
39406       * @private
39407       */
39408      _this.updateActiveSourceBuffers_ = function () {
39409        // Retain the reference but empty the array
39410        _this.activeSourceBuffers_.length = 0;
39411
39412        // If there is only one source buffer, then it will always be active and audio will
39413        // be disabled based on the codec of the source buffer
39414        if (_this.sourceBuffers.length === 1) {
39415          var sourceBuffer = _this.sourceBuffers[0];
39416
39417          sourceBuffer.appendAudioInitSegment_ = true;
39418          sourceBuffer.audioDisabled_ = !sourceBuffer.audioCodec_;
39419          _this.activeSourceBuffers_.push(sourceBuffer);
39420          return;
39421        }
39422
39423        // There are 2 source buffers, a combined (possibly video only) source buffer and
39424        // and an audio only source buffer.
39425        // By default, the audio in the combined virtual source buffer is enabled
39426        // and the audio-only source buffer (if it exists) is disabled.
39427        var disableCombined = false;
39428        var disableAudioOnly = true;
39429
39430        // TODO: maybe we can store the sourcebuffers on the track objects?
39431        // safari may do something like this
39432        for (var i = 0; i < _this.player_.audioTracks().length; i++) {
39433          var track = _this.player_.audioTracks()[i];
39434
39435          if (track.enabled && track.kind !== 'main') {
39436            // The enabled track is an alternate audio track so disable the audio in
39437            // the combined source buffer and enable the audio-only source buffer.
39438            disableCombined = true;
39439            disableAudioOnly = false;
39440            break;
39441          }
39442        }
39443
39444        _this.sourceBuffers.forEach(function (sourceBuffer, index) {
39445          /* eslinst-disable */
39446          // TODO once codecs are required, we can switch to using the codecs to determine
39447          //      what stream is the video stream, rather than relying on videoTracks
39448          /* eslinst-enable */
39449
39450          sourceBuffer.appendAudioInitSegment_ = true;
39451
39452          if (sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) {
39453            // combined
39454            sourceBuffer.audioDisabled_ = disableCombined;
39455          } else if (sourceBuffer.videoCodec_ && !sourceBuffer.audioCodec_) {
39456            // If the "combined" source buffer is video only, then we do not want
39457            // disable the audio-only source buffer (this is mostly for demuxed
39458            // audio and video hls)
39459            sourceBuffer.audioDisabled_ = true;
39460            disableAudioOnly = false;
39461          } else if (!sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) {
39462            // audio only
39463            // In the case of audio only with alternate audio and disableAudioOnly is true
39464            // this means we want to disable the audio on the alternate audio sourcebuffer
39465            // but not the main "combined" source buffer. The "combined" source buffer is
39466            // always at index 0, so this ensures audio won't be disabled in both source
39467            // buffers.
39468            sourceBuffer.audioDisabled_ = index ? disableAudioOnly : !disableAudioOnly;
39469            if (sourceBuffer.audioDisabled_) {
39470              return;
39471            }
39472          }
39473
39474          _this.activeSourceBuffers_.push(sourceBuffer);
39475        });
39476      };
39477
39478      _this.onPlayerMediachange_ = function () {
39479        _this.sourceBuffers.forEach(function (sourceBuffer) {
39480          sourceBuffer.appendAudioInitSegment_ = true;
39481        });
39482      };
39483
39484      _this.onHlsReset_ = function () {
39485        _this.sourceBuffers.forEach(function (sourceBuffer) {
39486          if (sourceBuffer.transmuxer_) {
39487            sourceBuffer.transmuxer_.postMessage({ action: 'resetCaptions' });
39488          }
39489        });
39490      };
39491
39492      _this.onHlsSegmentTimeMapping_ = function (event) {
39493        _this.sourceBuffers.forEach(function (buffer) {
39494          return buffer.timeMapping_ = event.mapping;
39495        });
39496      };
39497
39498      // Re-emit MediaSource events on the polyfill
39499      ['sourceopen', 'sourceclose', 'sourceended'].forEach(function (eventName) {
39500        this.nativeMediaSource_.addEventListener(eventName, this.trigger.bind(this));
39501      }, _this);
39502
39503      // capture the associated player when the MediaSource is
39504      // successfully attached
39505      _this.on('sourceopen', function (event) {
39506        // Get the player this MediaSource is attached to
39507        var video = document_1.querySelector('[src="' + _this.url_ + '"]');
39508
39509        if (!video) {
39510          return;
39511        }
39512
39513        _this.player_ = videojs$1(video.parentNode);
39514
39515        // hls-reset is fired by videojs.Hls on to the tech after the main SegmentLoader
39516        // resets its state and flushes the buffer
39517        _this.player_.tech_.on('hls-reset', _this.onHlsReset_);
39518        // hls-segment-time-mapping is fired by videojs.Hls on to the tech after the main
39519        // SegmentLoader inspects an MTS segment and has an accurate stream to display
39520        // time mapping
39521        _this.player_.tech_.on('hls-segment-time-mapping', _this.onHlsSegmentTimeMapping_);
39522
39523        if (_this.player_.audioTracks && _this.player_.audioTracks()) {
39524          _this.player_.audioTracks().on('change', _this.updateActiveSourceBuffers_);
39525          _this.player_.audioTracks().on('addtrack', _this.updateActiveSourceBuffers_);
39526          _this.player_.audioTracks().on('removetrack', _this.updateActiveSourceBuffers_);
39527        }
39528
39529        _this.player_.on('mediachange', _this.onPlayerMediachange_);
39530      });
39531
39532      _this.on('sourceended', function (event) {
39533        var duration = durationOfVideo(_this.duration);
39534
39535        for (var i = 0; i < _this.sourceBuffers.length; i++) {
39536          var sourcebuffer = _this.sourceBuffers[i];
39537          var cues = sourcebuffer.metadataTrack_ && sourcebuffer.metadataTrack_.cues;
39538
39539          if (cues && cues.length) {
39540            cues[cues.length - 1].endTime = duration;
39541          }
39542        }
39543      });
39544
39545      // explicitly terminate any WebWorkers that were created
39546      // by SourceHandlers
39547      _this.on('sourceclose', function (event) {
39548        this.sourceBuffers.forEach(function (sourceBuffer) {
39549          if (sourceBuffer.transmuxer_) {
39550            sourceBuffer.transmuxer_.terminate();
39551          }
39552        });
39553
39554        this.sourceBuffers.length = 0;
39555        if (!this.player_) {
39556          return;
39557        }
39558
39559        if (this.player_.audioTracks && this.player_.audioTracks()) {
39560          this.player_.audioTracks().off('change', this.updateActiveSourceBuffers_);
39561          this.player_.audioTracks().off('addtrack', this.updateActiveSourceBuffers_);
39562          this.player_.audioTracks().off('removetrack', this.updateActiveSourceBuffers_);
39563        }
39564
39565        // We can only change this if the player hasn't been disposed of yet
39566        // because `off` eventually tries to use the el_ property. If it has
39567        // been disposed of, then don't worry about it because there are no
39568        // event handlers left to unbind anyway
39569        if (this.player_.el_) {
39570          this.player_.off('mediachange', this.onPlayerMediachange_);
39571          this.player_.tech_.off('hls-reset', this.onHlsReset_);
39572          this.player_.tech_.off('hls-segment-time-mapping', this.onHlsSegmentTimeMapping_);
39573        }
39574      });
39575      return _this;
39576    }
39577
39578    /**
39579     * Add a range that that can now be seeked to.
39580     *
39581     * @param {Double} start where to start the addition
39582     * @param {Double} end where to end the addition
39583     * @private
39584     */
39585
39586    createClass$2(HtmlMediaSource, [{
39587      key: 'addSeekableRange_',
39588      value: function addSeekableRange_(start, end) {
39589        var error = void 0;
39590
39591        if (this.duration !== Infinity) {
39592          error = new Error('MediaSource.addSeekableRange() can only be invoked ' + 'when the duration is Infinity');
39593          error.name = 'InvalidStateError';
39594          error.code = 11;
39595          throw error;
39596        }
39597
39598        if (end > this.nativeMediaSource_.duration || isNaN(this.nativeMediaSource_.duration)) {
39599          this.nativeMediaSource_.duration = end;
39600        }
39601      }
39602
39603      /**
39604       * Add a source buffer to the media source.
39605       *
39606       * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/addSourceBuffer
39607       * @param {String} type the content-type of the content
39608       * @return {Object} the created source buffer
39609       */
39610
39611    }, {
39612      key: 'addSourceBuffer',
39613      value: function addSourceBuffer(type) {
39614        var buffer = void 0;
39615        var parsedType = parseContentType(type);
39616
39617        // Create a VirtualSourceBuffer to transmux MPEG-2 transport
39618        // stream segments into fragmented MP4s
39619        if (/^(video|audio)\/mp2t$/i.test(parsedType.type)) {
39620          var codecs = [];
39621
39622          if (parsedType.parameters && parsedType.parameters.codecs) {
39623            codecs = parsedType.parameters.codecs.split(',');
39624            codecs = translateLegacyCodecs(codecs);
39625            codecs = codecs.filter(function (codec) {
39626              return isAudioCodec(codec) || isVideoCodec(codec);
39627            });
39628          }
39629
39630          if (codecs.length === 0) {
39631            codecs = ['avc1.4d400d', 'mp4a.40.2'];
39632          }
39633
39634          buffer = new VirtualSourceBuffer(this, codecs);
39635
39636          if (this.sourceBuffers.length !== 0) {
39637            // If another VirtualSourceBuffer already exists, then we are creating a
39638            // SourceBuffer for an alternate audio track and therefore we know that
39639            // the source has both an audio and video track.
39640            // That means we should trigger the manual creation of the real
39641            // SourceBuffers instead of waiting for the transmuxer to return data
39642            this.sourceBuffers[0].createRealSourceBuffers_();
39643            buffer.createRealSourceBuffers_();
39644
39645            // Automatically disable the audio on the first source buffer if
39646            // a second source buffer is ever created
39647            this.sourceBuffers[0].audioDisabled_ = true;
39648          }
39649        } else {
39650          // delegate to the native implementation
39651          buffer = this.nativeMediaSource_.addSourceBuffer(type);
39652        }
39653
39654        this.sourceBuffers.push(buffer);
39655        return buffer;
39656      }
39657    }]);
39658    return HtmlMediaSource;
39659  }(videojs$1.EventTarget);
39660
39661  /**
39662   * @file videojs-contrib-media-sources.js
39663   */
39664  var urlCount = 0;
39665
39666  // ------------
39667  // Media Source
39668  // ------------
39669
39670  // store references to the media sources so they can be connected
39671  // to a video element (a swf object)
39672  // TODO: can we store this somewhere local to this module?
39673  videojs$1.mediaSources = {};
39674
39675  /**
39676   * Provide a method for a swf object to notify JS that a
39677   * media source is now open.
39678   *
39679   * @param {String} msObjectURL string referencing the MSE Object URL
39680   * @param {String} swfId the swf id
39681   */
39682  var open = function open(msObjectURL, swfId) {
39683    var mediaSource = videojs$1.mediaSources[msObjectURL];
39684
39685    if (mediaSource) {
39686      mediaSource.trigger({ type: 'sourceopen', swfId: swfId });
39687    } else {
39688      throw new Error('Media Source not found (Video.js)');
39689    }
39690  };
39691
39692  /**
39693   * Check to see if the native MediaSource object exists and supports
39694   * an MP4 container with both H.264 video and AAC-LC audio.
39695   *
39696   * @return {Boolean} if  native media sources are supported
39697   */
39698  var supportsNativeMediaSources = function supportsNativeMediaSources() {
39699    return !!window_1.MediaSource && !!window_1.MediaSource.isTypeSupported && window_1.MediaSource.isTypeSupported('video/mp4;codecs="avc1.4d400d,mp4a.40.2"');
39700  };
39701
39702  /**
39703   * An emulation of the MediaSource API so that we can support
39704   * native and non-native functionality. returns an instance of
39705   * HtmlMediaSource.
39706   *
39707   * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/MediaSource
39708   */
39709  var MediaSource = function MediaSource() {
39710    this.MediaSource = {
39711      open: open,
39712      supportsNativeMediaSources: supportsNativeMediaSources
39713    };
39714
39715    if (supportsNativeMediaSources()) {
39716      return new HtmlMediaSource();
39717    }
39718
39719    throw new Error('Cannot use create a virtual MediaSource for this video');
39720  };
39721
39722  MediaSource.open = open;
39723  MediaSource.supportsNativeMediaSources = supportsNativeMediaSources;
39724
39725  /**
39726   * A wrapper around the native URL for our MSE object
39727   * implementation, this object is exposed under videojs.URL
39728   *
39729   * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/URL
39730   */
39731  var URL$1 = {
39732    /**
39733     * A wrapper around the native createObjectURL for our objects.
39734     * This function maps a native or emulated mediaSource to a blob
39735     * url so that it can be loaded into video.js
39736     *
39737     * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL
39738     * @param {MediaSource} object the object to create a blob url to
39739     */
39740    createObjectURL: function createObjectURL(object) {
39741      var objectUrlPrefix = 'blob:vjs-media-source/';
39742      var url = void 0;
39743
39744      // use the native MediaSource to generate an object URL
39745      if (object instanceof HtmlMediaSource) {
39746        url = window_1.URL.createObjectURL(object.nativeMediaSource_);
39747        object.url_ = url;
39748        return url;
39749      }
39750      // if the object isn't an emulated MediaSource, delegate to the
39751      // native implementation
39752      if (!(object instanceof HtmlMediaSource)) {
39753        url = window_1.URL.createObjectURL(object);
39754        object.url_ = url;
39755        return url;
39756      }
39757
39758      // build a URL that can be used to map back to the emulated
39759      // MediaSource
39760      url = objectUrlPrefix + urlCount;
39761
39762      urlCount++;
39763
39764      // setup the mapping back to object
39765      videojs$1.mediaSources[url] = object;
39766
39767      return url;
39768    }
39769  };
39770
39771  videojs$1.MediaSource = MediaSource;
39772  videojs$1.URL = URL$1;
39773
39774  var EventTarget$1$1 = videojs$1.EventTarget,
39775      mergeOptions$2 = videojs$1.mergeOptions;
39776
39777  /**
39778   * Returns a new master manifest that is the result of merging an updated master manifest
39779   * into the original version.
39780   *
39781   * @param {Object} oldMaster
39782   *        The old parsed mpd object
39783   * @param {Object} newMaster
39784   *        The updated parsed mpd object
39785   * @return {Object}
39786   *         A new object representing the original master manifest with the updated media
39787   *         playlists merged in
39788   */
39789
39790  var updateMaster$1 = function updateMaster$$1(oldMaster, newMaster) {
39791    var update = mergeOptions$2(oldMaster, {
39792      // These are top level properties that can be updated
39793      duration: newMaster.duration,
39794      minimumUpdatePeriod: newMaster.minimumUpdatePeriod
39795    });
39796
39797    // First update the playlists in playlist list
39798    for (var i = 0; i < newMaster.playlists.length; i++) {
39799      var playlistUpdate = updateMaster(update, newMaster.playlists[i]);
39800
39801      if (playlistUpdate) {
39802        update = playlistUpdate;
39803      }
39804    }
39805
39806    // Then update media group playlists
39807    forEachMediaGroup(newMaster, function (properties, type, group, label) {
39808      if (properties.playlists && properties.playlists.length) {
39809        var uri = properties.playlists[0].uri;
39810        var _playlistUpdate = updateMaster(update, properties.playlists[0]);
39811
39812        if (_playlistUpdate) {
39813          update = _playlistUpdate;
39814          // update the playlist reference within media groups
39815          update.mediaGroups[type][group][label].playlists[0] = update.playlists[uri];
39816        }
39817      }
39818    });
39819
39820    return update;
39821  };
39822
39823  var DashPlaylistLoader = function (_EventTarget) {
39824    inherits$3(DashPlaylistLoader, _EventTarget);
39825
39826    // DashPlaylistLoader must accept either a src url or a playlist because subsequent
39827    // playlist loader setups from media groups will expect to be able to pass a playlist
39828    // (since there aren't external URLs to media playlists with DASH)
39829    function DashPlaylistLoader(srcUrlOrPlaylist, hls, withCredentials, masterPlaylistLoader) {
39830      classCallCheck$3(this, DashPlaylistLoader);
39831
39832      var _this = possibleConstructorReturn$3(this, (DashPlaylistLoader.__proto__ || Object.getPrototypeOf(DashPlaylistLoader)).call(this));
39833
39834      _this.hls_ = hls;
39835      _this.withCredentials = withCredentials;
39836
39837      if (!srcUrlOrPlaylist) {
39838        throw new Error('A non-empty playlist URL or playlist is required');
39839      }
39840
39841      // event naming?
39842      _this.on('minimumUpdatePeriod', function () {
39843        _this.refreshXml_();
39844      });
39845
39846      // live playlist staleness timeout
39847      _this.on('mediaupdatetimeout', function () {
39848        _this.refreshMedia_();
39849      });
39850
39851      // initialize the loader state
39852      if (typeof srcUrlOrPlaylist === 'string') {
39853        _this.srcUrl = srcUrlOrPlaylist;
39854        _this.state = 'HAVE_NOTHING';
39855        return possibleConstructorReturn$3(_this);
39856      }
39857
39858      _this.masterPlaylistLoader_ = masterPlaylistLoader;
39859
39860      _this.state = 'HAVE_METADATA';
39861      _this.started = true;
39862      // we only should have one playlist so select it
39863      _this.media(srcUrlOrPlaylist);
39864      // trigger async to mimic behavior of HLS, where it must request a playlist
39865      window_1.setTimeout(function () {
39866        _this.trigger('loadedmetadata');
39867      }, 0);
39868      return _this;
39869    }
39870
39871    createClass$2(DashPlaylistLoader, [{
39872      key: 'dispose',
39873      value: function dispose() {
39874        this.stopRequest();
39875        window_1.clearTimeout(this.mediaUpdateTimeout);
39876      }
39877    }, {
39878      key: 'stopRequest',
39879      value: function stopRequest() {
39880        if (this.request) {
39881          var oldRequest = this.request;
39882
39883          this.request = null;
39884          oldRequest.onreadystatechange = null;
39885          oldRequest.abort();
39886        }
39887      }
39888    }, {
39889      key: 'media',
39890      value: function media(playlist) {
39891        // getter
39892        if (!playlist) {
39893          return this.media_;
39894        }
39895
39896        // setter
39897        if (this.state === 'HAVE_NOTHING') {
39898          throw new Error('Cannot switch media playlist from ' + this.state);
39899        }
39900
39901        var startingState = this.state;
39902
39903        // find the playlist object if the target playlist has been specified by URI
39904        if (typeof playlist === 'string') {
39905          if (!this.master.playlists[playlist]) {
39906            throw new Error('Unknown playlist URI: ' + playlist);
39907          }
39908          playlist = this.master.playlists[playlist];
39909        }
39910
39911        var mediaChange = !this.media_ || playlist.uri !== this.media_.uri;
39912
39913        this.state = 'HAVE_METADATA';
39914
39915        // switching to the active playlist is a no-op
39916        if (!mediaChange) {
39917          return;
39918        }
39919
39920        // switching from an already loaded playlist
39921        if (this.media_) {
39922          this.trigger('mediachanging');
39923        }
39924
39925        this.media_ = playlist;
39926
39927        this.refreshMedia_();
39928
39929        // trigger media change if the active media has been updated
39930        if (startingState !== 'HAVE_MASTER') {
39931          this.trigger('mediachange');
39932        }
39933      }
39934    }, {
39935      key: 'pause',
39936      value: function pause() {
39937        this.stopRequest();
39938        if (this.state === 'HAVE_NOTHING') {
39939          // If we pause the loader before any data has been retrieved, its as if we never
39940          // started, so reset to an unstarted state.
39941          this.started = false;
39942        }
39943      }
39944    }, {
39945      key: 'load',
39946      value: function load() {
39947        // because the playlists are internal to the manifest, load should either load the
39948        // main manifest, or do nothing but trigger an event
39949        if (!this.started) {
39950          this.start();
39951          return;
39952        }
39953
39954        this.trigger('loadedplaylist');
39955      }
39956
39957      /**
39958       * Parses the master xml string and updates playlist uri references
39959       *
39960       * @return {Object}
39961       *         The parsed mpd manifest object
39962       */
39963
39964    }, {
39965      key: 'parseMasterXml',
39966      value: function parseMasterXml() {
39967        var master = parse(this.masterXml_, {
39968          manifestUri: this.srcUrl,
39969          clientOffset: this.clientOffset_
39970        });
39971
39972        master.uri = this.srcUrl;
39973
39974        // Set up phony URIs for the playlists since we won't have external URIs for DASH
39975        // but reference playlists by their URI throughout the project
39976        // TODO: Should we create the dummy uris in mpd-parser as well (leaning towards yes).
39977        for (var i = 0; i < master.playlists.length; i++) {
39978          var phonyUri = 'placeholder-uri-' + i;
39979
39980          master.playlists[i].uri = phonyUri;
39981          // set up by URI references
39982          master.playlists[phonyUri] = master.playlists[i];
39983        }
39984
39985        // set up phony URIs for the media group playlists since we won't have external
39986        // URIs for DASH but reference playlists by their URI throughout the project
39987        forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) {
39988          if (properties.playlists && properties.playlists.length) {
39989            var _phonyUri = 'placeholder-uri-' + mediaType + '-' + groupKey + '-' + labelKey;
39990
39991            properties.playlists[0].uri = _phonyUri;
39992            // setup URI references
39993            master.playlists[_phonyUri] = properties.playlists[0];
39994          }
39995        });
39996
39997        setupMediaPlaylists(master);
39998        resolveMediaGroupUris(master);
39999
40000        return master;
40001      }
40002    }, {
40003      key: 'start',
40004      value: function start() {
40005        var _this2 = this;
40006
40007        this.started = true;
40008
40009        // request the specified URL
40010        this.request = this.hls_.xhr({
40011          uri: this.srcUrl,
40012          withCredentials: this.withCredentials
40013        }, function (error, req) {
40014          // disposed
40015          if (!_this2.request) {
40016            return;
40017          }
40018
40019          // clear the loader's request reference
40020          _this2.request = null;
40021
40022          if (error) {
40023            _this2.error = {
40024              status: req.status,
40025              message: 'DASH playlist request error at URL: ' + _this2.srcUrl,
40026              responseText: req.responseText,
40027              // MEDIA_ERR_NETWORK
40028              code: 2
40029            };
40030            if (_this2.state === 'HAVE_NOTHING') {
40031              _this2.started = false;
40032            }
40033            return _this2.trigger('error');
40034          }
40035
40036          _this2.masterXml_ = req.responseText;
40037
40038          if (req.responseHeaders && req.responseHeaders.date) {
40039            _this2.masterLoaded_ = Date.parse(req.responseHeaders.date);
40040          } else {
40041            _this2.masterLoaded_ = Date.now();
40042          }
40043
40044          _this2.syncClientServerClock_(_this2.onClientServerClockSync_.bind(_this2));
40045        });
40046      }
40047
40048      /**
40049       * Parses the master xml for UTCTiming node to sync the client clock to the server
40050       * clock. If the UTCTiming node requires a HEAD or GET request, that request is made.
40051       *
40052       * @param {Function} done
40053       *        Function to call when clock sync has completed
40054       */
40055
40056    }, {
40057      key: 'syncClientServerClock_',
40058      value: function syncClientServerClock_(done) {
40059        var _this3 = this;
40060
40061        var utcTiming = parseUTCTiming(this.masterXml_);
40062
40063        // No UTCTiming element found in the mpd. Use Date header from mpd request as the
40064        // server clock
40065        if (utcTiming === null) {
40066          this.clientOffset_ = this.masterLoaded_ - Date.now();
40067          return done();
40068        }
40069
40070        if (utcTiming.method === 'DIRECT') {
40071          this.clientOffset_ = utcTiming.value - Date.now();
40072          return done();
40073        }
40074
40075        this.request = this.hls_.xhr({
40076          uri: resolveUrl$1(this.srcUrl, utcTiming.value),
40077          method: utcTiming.method,
40078          withCredentials: this.withCredentials
40079        }, function (error, req) {
40080          // disposed
40081          if (!_this3.request) {
40082            return;
40083          }
40084
40085          if (error) {
40086            // sync request failed, fall back to using date header from mpd
40087            // TODO: log warning
40088            _this3.clientOffset_ = _this3.masterLoaded_ - Date.now();
40089            return done();
40090          }
40091
40092          var serverTime = void 0;
40093
40094          if (utcTiming.method === 'HEAD') {
40095            if (!req.responseHeaders || !req.responseHeaders.date) {
40096              // expected date header not preset, fall back to using date header from mpd
40097              // TODO: log warning
40098              serverTime = _this3.masterLoaded_;
40099            } else {
40100              serverTime = Date.parse(req.responseHeaders.date);
40101            }
40102          } else {
40103            serverTime = Date.parse(req.responseText);
40104          }
40105
40106          _this3.clientOffset_ = serverTime - Date.now();
40107
40108          done();
40109        });
40110      }
40111
40112      /**
40113       * Handler for after client/server clock synchronization has happened. Sets up
40114       * xml refresh timer if specificed by the manifest.
40115       */
40116
40117    }, {
40118      key: 'onClientServerClockSync_',
40119      value: function onClientServerClockSync_() {
40120        var _this4 = this;
40121
40122        this.master = this.parseMasterXml();
40123
40124        this.state = 'HAVE_MASTER';
40125
40126        this.trigger('loadedplaylist');
40127
40128        if (!this.media_) {
40129          // no media playlist was specifically selected so start
40130          // from the first listed one
40131          this.media(this.master.playlists[0]);
40132        }
40133        // trigger loadedmetadata to resolve setup of media groups
40134        // trigger async to mimic behavior of HLS, where it must request a playlist
40135        window_1.setTimeout(function () {
40136          _this4.trigger('loadedmetadata');
40137        }, 0);
40138
40139        // TODO: minimumUpdatePeriod can have a value of 0. Currently the manifest will not
40140        // be refreshed when this is the case. The inter-op guide says that when the
40141        // minimumUpdatePeriod is 0, the manifest should outline all currently available
40142        // segments, but future segments may require an update. I think a good solution
40143        // would be to update the manifest at the same rate that the media playlists
40144        // are "refreshed", i.e. every targetDuration.
40145        if (this.master.minimumUpdatePeriod) {
40146          window_1.setTimeout(function () {
40147            _this4.trigger('minimumUpdatePeriod');
40148          }, this.master.minimumUpdatePeriod);
40149        }
40150      }
40151
40152      /**
40153       * Sends request to refresh the master xml and updates the parsed master manifest
40154       * TODO: Does the client offset need to be recalculated when the xml is refreshed?
40155       */
40156
40157    }, {
40158      key: 'refreshXml_',
40159      value: function refreshXml_() {
40160        var _this5 = this;
40161
40162        this.request = this.hls_.xhr({
40163          uri: this.srcUrl,
40164          withCredentials: this.withCredentials
40165        }, function (error, req) {
40166          // disposed
40167          if (!_this5.request) {
40168            return;
40169          }
40170
40171          // clear the loader's request reference
40172          _this5.request = null;
40173
40174          if (error) {
40175            _this5.error = {
40176              status: req.status,
40177              message: 'DASH playlist request error at URL: ' + _this5.srcUrl,
40178              responseText: req.responseText,
40179              // MEDIA_ERR_NETWORK
40180              code: 2
40181            };
40182            if (_this5.state === 'HAVE_NOTHING') {
40183              _this5.started = false;
40184            }
40185            return _this5.trigger('error');
40186          }
40187
40188          _this5.masterXml_ = req.responseText;
40189
40190          var newMaster = _this5.parseMasterXml();
40191
40192          _this5.master = updateMaster$1(_this5.master, newMaster);
40193
40194          window_1.setTimeout(function () {
40195            _this5.trigger('minimumUpdatePeriod');
40196          }, _this5.master.minimumUpdatePeriod);
40197        });
40198      }
40199
40200      /**
40201       * Refreshes the media playlist by re-parsing the master xml and updating playlist
40202       * references. If this is an alternate loader, the updated parsed manifest is retrieved
40203       * from the master loader.
40204       */
40205
40206    }, {
40207      key: 'refreshMedia_',
40208      value: function refreshMedia_() {
40209        var _this6 = this;
40210
40211        var oldMaster = void 0;
40212        var newMaster = void 0;
40213
40214        if (this.masterPlaylistLoader_) {
40215          oldMaster = this.masterPlaylistLoader_.master;
40216          newMaster = this.masterPlaylistLoader_.parseMasterXml();
40217        } else {
40218          oldMaster = this.master;
40219          newMaster = this.parseMasterXml();
40220        }
40221
40222        var updatedMaster = updateMaster$1(oldMaster, newMaster);
40223
40224        if (updatedMaster) {
40225          if (this.masterPlaylistLoader_) {
40226            this.masterPlaylistLoader_.master = updatedMaster;
40227          } else {
40228            this.master = updatedMaster;
40229          }
40230          this.media_ = updatedMaster.playlists[this.media_.uri];
40231        } else {
40232          this.trigger('playlistunchanged');
40233        }
40234
40235        if (!this.media().endList) {
40236          this.mediaUpdateTimeout = window_1.setTimeout(function () {
40237            _this6.trigger('mediaupdatetimeout');
40238          }, refreshDelay(this.media(), !!updatedMaster));
40239        }
40240
40241        this.trigger('loadedplaylist');
40242      }
40243    }]);
40244    return DashPlaylistLoader;
40245  }(EventTarget$1$1);
40246
40247  var logger = function logger(source) {
40248    if (videojs$1.log.debug) {
40249      return videojs$1.log.debug.bind(videojs$1, 'VHS:', source + ' >');
40250    }
40251
40252    return function () {};
40253  };
40254
40255  function noop$1() {}
40256
40257  /**
40258   * @file source-updater.js
40259   */
40260
40261  /**
40262   * A queue of callbacks to be serialized and applied when a
40263   * MediaSource and its associated SourceBuffers are not in the
40264   * updating state. It is used by the segment loader to update the
40265   * underlying SourceBuffers when new data is loaded, for instance.
40266   *
40267   * @class SourceUpdater
40268   * @param {MediaSource} mediaSource the MediaSource to create the
40269   * SourceBuffer from
40270   * @param {String} mimeType the desired MIME type of the underlying
40271   * SourceBuffer
40272   * @param {Object} sourceBufferEmitter an event emitter that fires when a source buffer is
40273   * added to the media source
40274   */
40275
40276  var SourceUpdater = function () {
40277    function SourceUpdater(mediaSource, mimeType, type, sourceBufferEmitter) {
40278      classCallCheck$3(this, SourceUpdater);
40279
40280      this.callbacks_ = [];
40281      this.pendingCallback_ = null;
40282      this.timestampOffset_ = 0;
40283      this.mediaSource = mediaSource;
40284      this.processedAppend_ = false;
40285      this.type_ = type;
40286      this.mimeType_ = mimeType;
40287      this.logger_ = logger('SourceUpdater[' + type + '][' + mimeType + ']');
40288
40289      if (mediaSource.readyState === 'closed') {
40290        mediaSource.addEventListener('sourceopen', this.createSourceBuffer_.bind(this, mimeType, sourceBufferEmitter));
40291      } else {
40292        this.createSourceBuffer_(mimeType, sourceBufferEmitter);
40293      }
40294    }
40295
40296    createClass$2(SourceUpdater, [{
40297      key: 'createSourceBuffer_',
40298      value: function createSourceBuffer_(mimeType, sourceBufferEmitter) {
40299        var _this = this;
40300
40301        this.sourceBuffer_ = this.mediaSource.addSourceBuffer(mimeType);
40302
40303        this.logger_('created SourceBuffer');
40304
40305        if (sourceBufferEmitter) {
40306          sourceBufferEmitter.trigger('sourcebufferadded');
40307
40308          if (this.mediaSource.sourceBuffers.length < 2) {
40309            // There's another source buffer we must wait for before we can start updating
40310            // our own (or else we can get into a bad state, i.e., appending video/audio data
40311            // before the other video/audio source buffer is available and leading to a video
40312            // or audio only buffer).
40313            sourceBufferEmitter.on('sourcebufferadded', function () {
40314              _this.start_();
40315            });
40316            return;
40317          }
40318        }
40319
40320        this.start_();
40321      }
40322    }, {
40323      key: 'start_',
40324      value: function start_() {
40325        var _this2 = this;
40326
40327        this.started_ = true;
40328
40329        // run completion handlers and process callbacks as updateend
40330        // events fire
40331        this.onUpdateendCallback_ = function () {
40332          var pendingCallback = _this2.pendingCallback_;
40333
40334          _this2.pendingCallback_ = null;
40335
40336          _this2.logger_('buffered [' + printableRange(_this2.buffered()) + ']');
40337
40338          if (pendingCallback) {
40339            pendingCallback();
40340          }
40341
40342          _this2.runCallback_();
40343        };
40344
40345        this.sourceBuffer_.addEventListener('updateend', this.onUpdateendCallback_);
40346
40347        this.runCallback_();
40348      }
40349
40350      /**
40351       * Aborts the current segment and resets the segment parser.
40352       *
40353       * @param {Function} done function to call when done
40354       * @see http://w3c.github.io/media-source/#widl-SourceBuffer-abort-void
40355       */
40356
40357    }, {
40358      key: 'abort',
40359      value: function abort(done) {
40360        var _this3 = this;
40361
40362        if (this.processedAppend_) {
40363          this.queueCallback_(function () {
40364            _this3.sourceBuffer_.abort();
40365          }, done);
40366        }
40367      }
40368
40369      /**
40370       * Queue an update to append an ArrayBuffer.
40371       *
40372       * @param {ArrayBuffer} bytes
40373       * @param {Function} done the function to call when done
40374       * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data
40375       */
40376
40377    }, {
40378      key: 'appendBuffer',
40379      value: function appendBuffer(bytes, done) {
40380        var _this4 = this;
40381
40382        this.processedAppend_ = true;
40383        this.queueCallback_(function () {
40384          _this4.sourceBuffer_.appendBuffer(bytes);
40385        }, done);
40386      }
40387
40388      /**
40389       * Indicates what TimeRanges are buffered in the managed SourceBuffer.
40390       *
40391       * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-buffered
40392       */
40393
40394    }, {
40395      key: 'buffered',
40396      value: function buffered() {
40397        if (!this.sourceBuffer_) {
40398          return videojs$1.createTimeRanges();
40399        }
40400        return this.sourceBuffer_.buffered;
40401      }
40402
40403      /**
40404       * Queue an update to remove a time range from the buffer.
40405       *
40406       * @param {Number} start where to start the removal
40407       * @param {Number} end where to end the removal
40408       * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end
40409       */
40410
40411    }, {
40412      key: 'remove',
40413      value: function remove(start, end) {
40414        var _this5 = this;
40415
40416        if (this.processedAppend_) {
40417          this.queueCallback_(function () {
40418            _this5.logger_('remove [' + start + ' => ' + end + ']');
40419            _this5.sourceBuffer_.remove(start, end);
40420          }, noop$1);
40421        }
40422      }
40423
40424      /**
40425       * Whether the underlying sourceBuffer is updating or not
40426       *
40427       * @return {Boolean} the updating status of the SourceBuffer
40428       */
40429
40430    }, {
40431      key: 'updating',
40432      value: function updating() {
40433        return !this.sourceBuffer_ || this.sourceBuffer_.updating || this.pendingCallback_;
40434      }
40435
40436      /**
40437       * Set/get the timestampoffset on the SourceBuffer
40438       *
40439       * @return {Number} the timestamp offset
40440       */
40441
40442    }, {
40443      key: 'timestampOffset',
40444      value: function timestampOffset(offset) {
40445        var _this6 = this;
40446
40447        if (typeof offset !== 'undefined') {
40448          this.queueCallback_(function () {
40449            _this6.sourceBuffer_.timestampOffset = offset;
40450          });
40451          this.timestampOffset_ = offset;
40452        }
40453        return this.timestampOffset_;
40454      }
40455
40456      /**
40457       * Queue a callback to run
40458       */
40459
40460    }, {
40461      key: 'queueCallback_',
40462      value: function queueCallback_(callback, done) {
40463        this.callbacks_.push([callback.bind(this), done]);
40464        this.runCallback_();
40465      }
40466
40467      /**
40468       * Run a queued callback
40469       */
40470
40471    }, {
40472      key: 'runCallback_',
40473      value: function runCallback_() {
40474        var callbacks = void 0;
40475
40476        if (!this.updating() && this.callbacks_.length && this.started_) {
40477          callbacks = this.callbacks_.shift();
40478          this.pendingCallback_ = callbacks[1];
40479          callbacks[0]();
40480        }
40481      }
40482
40483      /**
40484       * dispose of the source updater and the underlying sourceBuffer
40485       */
40486
40487    }, {
40488      key: 'dispose',
40489      value: function dispose() {
40490        this.sourceBuffer_.removeEventListener('updateend', this.onUpdateendCallback_);
40491        if (this.sourceBuffer_ && this.mediaSource.readyState === 'open') {
40492          this.sourceBuffer_.abort();
40493        }
40494      }
40495    }]);
40496    return SourceUpdater;
40497  }();
40498
40499  var Config = {
40500    GOAL_BUFFER_LENGTH: 30,
40501    MAX_GOAL_BUFFER_LENGTH: 60,
40502    GOAL_BUFFER_LENGTH_RATE: 1,
40503    // A fudge factor to apply to advertised playlist bitrates to account for
40504    // temporary flucations in client bandwidth
40505    BANDWIDTH_VARIANCE: 1.2,
40506    // How much of the buffer must be filled before we consider upswitching
40507    BUFFER_LOW_WATER_LINE: 0,
40508    MAX_BUFFER_LOW_WATER_LINE: 30,
40509    BUFFER_LOW_WATER_LINE_RATE: 1
40510  };
40511
40512  var REQUEST_ERRORS = {
40513    FAILURE: 2,
40514    TIMEOUT: -101,
40515    ABORTED: -102
40516  };
40517
40518  /**
40519   * Turns segment byterange into a string suitable for use in
40520   * HTTP Range requests
40521   *
40522   * @param {Object} byterange - an object with two values defining the start and end
40523   *                             of a byte-range
40524   */
40525  var byterangeStr = function byterangeStr(byterange) {
40526    var byterangeStart = void 0;
40527    var byterangeEnd = void 0;
40528
40529    // `byterangeEnd` is one less than `offset + length` because the HTTP range
40530    // header uses inclusive ranges
40531    byterangeEnd = byterange.offset + byterange.length - 1;
40532    byterangeStart = byterange.offset;
40533    return 'bytes=' + byterangeStart + '-' + byterangeEnd;
40534  };
40535
40536  /**
40537   * Defines headers for use in the xhr request for a particular segment.
40538   *
40539   * @param {Object} segment - a simplified copy of the segmentInfo object
40540   *                           from SegmentLoader
40541   */
40542  var segmentXhrHeaders = function segmentXhrHeaders(segment) {
40543    var headers = {};
40544
40545    if (segment.byterange) {
40546      headers.Range = byterangeStr(segment.byterange);
40547    }
40548    return headers;
40549  };
40550
40551  /**
40552   * Abort all requests
40553   *
40554   * @param {Object} activeXhrs - an object that tracks all XHR requests
40555   */
40556  var abortAll = function abortAll(activeXhrs) {
vendor: 4,629 bytes, lines 40557-40704
40557    activeXhrs.forEach(function (xhr) {
40558      xhr.abort();
40559    });
40560  };
40561
40562  /**
40563   * Gather important bandwidth stats once a request has completed
40564   *
40565   * @param {Object} request - the XHR request from which to gather stats
40566   */
40567  var getRequestStats = function getRequestStats(request) {
40568    return {
40569      bandwidth: request.bandwidth,
40570      bytesReceived: request.bytesReceived || 0,
40571      roundTripTime: request.roundTripTime || 0
40572    };
40573  };
40574
40575  /**
40576   * If possible gather bandwidth stats as a request is in
40577   * progress
40578   *
40579   * @param {Event} progressEvent - an event object from an XHR's progress event
40580   */
40581  var getProgressStats = function getProgressStats(progressEvent) {
40582    var request = progressEvent.target;
40583    var roundTripTime = Date.now() - request.requestTime;
40584    var stats = {
40585      bandwidth: Infinity,
40586      bytesReceived: 0,
40587      roundTripTime: roundTripTime || 0
40588    };
40589
40590    stats.bytesReceived = progressEvent.loaded;
40591    // This can result in Infinity if stats.roundTripTime is 0 but that is ok
40592    // because we should only use bandwidth stats on progress to determine when
40593    // abort a request early due to insufficient bandwidth
40594    stats.bandwidth = Math.floor(stats.bytesReceived / stats.roundTripTime * 8 * 1000);
40595
40596    return stats;
40597  };
40598
40599  /**
40600   * Handle all error conditions in one place and return an object
40601   * with all the information
40602   *
40603   * @param {Error|null} error - if non-null signals an error occured with the XHR
40604   * @param {Object} request -  the XHR request that possibly generated the error
40605   */
40606  var handleErrors = function handleErrors(error, request) {
40607    if (request.timedout) {
40608      return {
40609        status: request.status,
40610        message: 'HLS request timed-out at URL: ' + request.uri,
40611        code: REQUEST_ERRORS.TIMEOUT,
40612        xhr: request
40613      };
40614    }
40615
40616    if (request.aborted) {
40617      return {
40618        status: request.status,
40619        message: 'HLS request aborted at URL: ' + request.uri,
40620        code: REQUEST_ERRORS.ABORTED,
40621        xhr: request
40622      };
40623    }
40624
40625    if (error) {
40626      return {
40627        status: request.status,
40628        message: 'HLS request errored at URL: ' + request.uri,
40629        code: REQUEST_ERRORS.FAILURE,
40630        xhr: request
40631      };
40632    }
40633
40634    return null;
40635  };
40636
40637  /**
40638   * Handle responses for key data and convert the key data to the correct format
40639   * for the decryption step later
40640   *
40641   * @param {Object} segment - a simplified copy of the segmentInfo object
40642   *                           from SegmentLoader
40643   * @param {Function} finishProcessingFn - a callback to execute to continue processing
40644   *                                        this request
40645   */
40646  var handleKeyResponse = function handleKeyResponse(segment, finishProcessingFn) {
40647    return function (error, request) {
40648      var response = request.response;
40649      var errorObj = handleErrors(error, request);
40650
40651      if (errorObj) {
40652        return finishProcessingFn(errorObj, segment);
40653      }
40654
40655      if (response.byteLength !== 16) {
40656        return finishProcessingFn({
40657          status: request.status,
40658          message: 'Invalid HLS key at URL: ' + request.uri,
40659          code: REQUEST_ERRORS.FAILURE,
40660          xhr: request
40661        }, segment);
40662      }
40663
40664      var view = new DataView(response);
40665
40666      segment.key.bytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]);
40667      return finishProcessingFn(null, segment);
40668    };
40669  };
40670
40671  /**
40672   * Handle init-segment responses
40673   *
40674   * @param {Object} segment - a simplified copy of the segmentInfo object
40675   *                           from SegmentLoader
40676   * @param {Function} finishProcessingFn - a callback to execute to continue processing
40677   *                                        this request
40678   */
40679  var handleInitSegmentResponse = function handleInitSegmentResponse(segment, finishProcessingFn) {
40680    return function (error, request) {
40681      var response = request.response;
40682      var errorObj = handleErrors(error, request);
40683
40684      if (errorObj) {
40685        return finishProcessingFn(errorObj, segment);
40686      }
40687
40688      // stop processing if received empty content
40689      if (response.byteLength === 0) {
40690        return finishProcessingFn({
40691          status: request.status,
40692          message: 'Empty HLS segment content at URL: ' + request.uri,
40693          code: REQUEST_ERRORS.FAILURE,
40694          xhr: request
40695        }, segment);
40696      }
40697
40698      segment.map.bytes = new Uint8Array(request.response);
40699      return finishProcessingFn(null, segment);
40700    };
40701  };
40702
40703  /**
40704   * Response handler for segment-requests being sure to set the correct
vendor: 207,496 bytes, lines 40705-46609
40705   * property depending on whether the segment is encryped or not
40706   * Also records and keeps track of stats that are used for ABR purposes
40707   *
40708   * @param {Object} segment - a simplified copy of the segmentInfo object
40709   *                           from SegmentLoader
40710   * @param {Function} finishProcessingFn - a callback to execute to continue processing
40711   *                                        this request
40712   */
40713  var handleSegmentResponse = function handleSegmentResponse(segment, finishProcessingFn) {
40714    return function (error, request) {
40715      var response = request.response;
40716      var errorObj = handleErrors(error, request);
40717
40718      if (errorObj) {
40719        return finishProcessingFn(errorObj, segment);
40720      }
40721
40722      // stop processing if received empty content
40723      if (response.byteLength === 0) {
40724        return finishProcessingFn({
40725          status: request.status,
40726          message: 'Empty HLS segment content at URL: ' + request.uri,
40727          code: REQUEST_ERRORS.FAILURE,
40728          xhr: request
40729        }, segment);
40730      }
40731
40732      segment.stats = getRequestStats(request);
40733
40734      if (segment.key) {
40735        segment.encryptedBytes = new Uint8Array(request.response);
40736      } else {
40737        segment.bytes = new Uint8Array(request.response);
40738      }
40739
40740      return finishProcessingFn(null, segment);
40741    };
40742  };
40743
40744  /**
40745   * Decrypt the segment via the decryption web worker
40746   *
40747   * @param {WebWorker} decrypter - a WebWorker interface to AES-128 decryption routines
40748   * @param {Object} segment - a simplified copy of the segmentInfo object
40749   *                           from SegmentLoader
40750   * @param {Function} doneFn - a callback that is executed after decryption has completed
40751   */
40752  var decryptSegment = function decryptSegment(decrypter, segment, doneFn) {
40753    var decryptionHandler = function decryptionHandler(event) {
40754      if (event.data.source === segment.requestId) {
40755        decrypter.removeEventListener('message', decryptionHandler);
40756        var decrypted = event.data.decrypted;
40757
40758        segment.bytes = new Uint8Array(decrypted.bytes, decrypted.byteOffset, decrypted.byteLength);
40759        return doneFn(null, segment);
40760      }
40761    };
40762
40763    decrypter.addEventListener('message', decryptionHandler);
40764
40765    // this is an encrypted segment
40766    // incrementally decrypt the segment
40767    decrypter.postMessage(createTransferableMessage({
40768      source: segment.requestId,
40769      encrypted: segment.encryptedBytes,
40770      key: segment.key.bytes,
40771      iv: segment.key.iv
40772    }), [segment.encryptedBytes.buffer, segment.key.bytes.buffer]);
40773  };
40774
40775  /**
40776   * The purpose of this function is to get the most pertinent error from the
40777   * array of errors.
40778   * For instance if a timeout and two aborts occur, then the aborts were
40779   * likely triggered by the timeout so return that error object.
40780   */
40781  var getMostImportantError = function getMostImportantError(errors) {
40782    return errors.reduce(function (prev, err) {
40783      return err.code > prev.code ? err : prev;
40784    });
40785  };
40786
40787  /**
40788   * This function waits for all XHRs to finish (with either success or failure)
40789   * before continueing processing via it's callback. The function gathers errors
40790   * from each request into a single errors array so that the error status for
40791   * each request can be examined later.
40792   *
40793   * @param {Object} activeXhrs - an object that tracks all XHR requests
40794   * @param {WebWorker} decrypter - a WebWorker interface to AES-128 decryption routines
40795   * @param {Function} doneFn - a callback that is executed after all resources have been
40796   *                            downloaded and any decryption completed
40797   */
40798  var waitForCompletion = function waitForCompletion(activeXhrs, decrypter, doneFn) {
40799    var errors = [];
40800    var count = 0;
40801
40802    return function (error, segment) {
40803      if (error) {
40804        // If there are errors, we have to abort any outstanding requests
40805        abortAll(activeXhrs);
40806        errors.push(error);
40807      }
40808      count += 1;
40809
40810      if (count === activeXhrs.length) {
40811        // Keep track of when *all* of the requests have completed
40812        segment.endOfAllRequests = Date.now();
40813
40814        if (errors.length > 0) {
40815          var worstError = getMostImportantError(errors);
40816
40817          return doneFn(worstError, segment);
40818        }
40819        if (segment.encryptedBytes) {
40820          return decryptSegment(decrypter, segment, doneFn);
40821        }
40822        // Otherwise, everything is ready just continue
40823        return doneFn(null, segment);
40824      }
40825    };
40826  };
40827
40828  /**
40829   * Simple progress event callback handler that gathers some stats before
40830   * executing a provided callback with the `segment` object
40831   *
40832   * @param {Object} segment - a simplified copy of the segmentInfo object
40833   *                           from SegmentLoader
40834   * @param {Function} progressFn - a callback that is executed each time a progress event
40835   *                                is received
40836   * @param {Event} event - the progress event object from XMLHttpRequest
40837   */
40838  var handleProgress = function handleProgress(segment, progressFn) {
40839    return function (event) {
40840      segment.stats = videojs$1.mergeOptions(segment.stats, getProgressStats(event));
40841
40842      // record the time that we receive the first byte of data
40843      if (!segment.stats.firstBytesReceivedAt && segment.stats.bytesReceived) {
40844        segment.stats.firstBytesReceivedAt = Date.now();
40845      }
40846
40847      return progressFn(event, segment);
40848    };
40849  };
40850
40851  /**
40852   * Load all resources and does any processing necessary for a media-segment
40853   *
40854   * Features:
40855   *   decrypts the media-segment if it has a key uri and an iv
40856   *   aborts *all* requests if *any* one request fails
40857   *
40858   * The segment object, at minimum, has the following format:
40859   * {
40860   *   resolvedUri: String,
40861   *   [byterange]: {
40862   *     offset: Number,
40863   *     length: Number
40864   *   },
40865   *   [key]: {
40866   *     resolvedUri: String
40867   *     [byterange]: {
40868   *       offset: Number,
40869   *       length: Number
40870   *     },
40871   *     iv: {
40872   *       bytes: Uint32Array
40873   *     }
40874   *   },
40875   *   [map]: {
40876   *     resolvedUri: String,
40877   *     [byterange]: {
40878   *       offset: Number,
40879   *       length: Number
40880   *     },
40881   *     [bytes]: Uint8Array
40882   *   }
40883   * }
40884   * ...where [name] denotes optional properties
40885   *
40886   * @param {Function} xhr - an instance of the xhr wrapper in xhr.js
40887   * @param {Object} xhrOptions - the base options to provide to all xhr requests
40888   * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128
40889   *                                       decryption routines
40890   * @param {Object} segment - a simplified copy of the segmentInfo object
40891   *                           from SegmentLoader
40892   * @param {Function} progressFn - a callback that receives progress events from the main
40893   *                                segment's xhr request
40894   * @param {Function} doneFn - a callback that is executed only once all requests have
40895   *                            succeeded or failed
40896   * @returns {Function} a function that, when invoked, immediately aborts all
40897   *                     outstanding requests
40898   */
40899  var mediaSegmentRequest = function mediaSegmentRequest(xhr, xhrOptions, decryptionWorker, segment, progressFn, doneFn) {
40900    var activeXhrs = [];
40901    var finishProcessingFn = waitForCompletion(activeXhrs, decryptionWorker, doneFn);
40902
40903    // optionally, request the decryption key
40904    if (segment.key) {
40905      var keyRequestOptions = videojs$1.mergeOptions(xhrOptions, {
40906        uri: segment.key.resolvedUri,
40907        responseType: 'arraybuffer'
40908      });
40909      var keyRequestCallback = handleKeyResponse(segment, finishProcessingFn);
40910      var keyXhr = xhr(keyRequestOptions, keyRequestCallback);
40911
40912      activeXhrs.push(keyXhr);
40913    }
40914
40915    // optionally, request the associated media init segment
40916    if (segment.map && !segment.map.bytes) {
40917      var initSegmentOptions = videojs$1.mergeOptions(xhrOptions, {
40918        uri: segment.map.resolvedUri,
40919        responseType: 'arraybuffer',
40920        headers: segmentXhrHeaders(segment.map)
40921      });
40922      var initSegmentRequestCallback = handleInitSegmentResponse(segment, finishProcessingFn);
40923      var initSegmentXhr = xhr(initSegmentOptions, initSegmentRequestCallback);
40924
40925      activeXhrs.push(initSegmentXhr);
40926    }
40927
40928    var segmentRequestOptions = videojs$1.mergeOptions(xhrOptions, {
40929      uri: segment.resolvedUri,
40930      responseType: 'arraybuffer',
40931      headers: segmentXhrHeaders(segment)
40932    });
40933    var segmentRequestCallback = handleSegmentResponse(segment, finishProcessingFn);
40934    var segmentXhr = xhr(segmentRequestOptions, segmentRequestCallback);
40935
40936    segmentXhr.addEventListener('progress', handleProgress(segment, progressFn));
40937    activeXhrs.push(segmentXhr);
40938
40939    return function () {
40940      return abortAll(activeXhrs);
40941    };
40942  };
40943
40944  /**
40945   * @file - codecs.js - Handles tasks regarding codec strings such as translating them to
40946   * codec strings, or translating codec strings into objects that can be examined.
40947   */
40948
40949  // Default codec parameters if none were provided for video and/or audio
40950  var defaultCodecs = {
40951    videoCodec: 'avc1',
40952    videoObjectTypeIndicator: '.4d400d',
40953    // AAC-LC
40954    audioProfile: '2'
40955  };
40956
40957  /**
40958   * Parses a codec string to retrieve the number of codecs specified,
40959   * the video codec and object type indicator, and the audio profile.
40960   */
40961
40962  var parseCodecs = function parseCodecs() {
40963    var codecs = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
40964
40965    var result = {
40966      codecCount: 0
40967    };
40968    var parsed = void 0;
40969
40970    result.codecCount = codecs.split(',').length;
40971    result.codecCount = result.codecCount || 2;
40972
40973    // parse the video codec
40974    parsed = /(^|\s|,)+(avc[13])([^ ,]*)/i.exec(codecs);
40975    if (parsed) {
40976      result.videoCodec = parsed[2];
40977      result.videoObjectTypeIndicator = parsed[3];
40978    }
40979
40980    // parse the last field of the audio codec
40981    result.audioProfile = /(^|\s|,)+mp4a.[0-9A-Fa-f]+\.([0-9A-Fa-f]+)/i.exec(codecs);
40982    result.audioProfile = result.audioProfile && result.audioProfile[2];
40983
40984    return result;
40985  };
40986
40987  /**
40988   * Replace codecs in the codec string with the old apple-style `avc1.<dd>.<dd>` to the
40989   * standard `avc1.<hhhhhh>`.
40990   *
40991   * @param codecString {String} the codec string
40992   * @return {String} the codec string with old apple-style codecs replaced
40993   *
40994   * @private
40995   */
40996  var mapLegacyAvcCodecs = function mapLegacyAvcCodecs(codecString) {
40997    return codecString.replace(/avc1\.(\d+)\.(\d+)/i, function (match) {
40998      return translateLegacyCodecs([match])[0];
40999    });
41000  };
41001
41002  /**
41003   * Build a media mime-type string from a set of parameters
41004   * @param {String} type either 'audio' or 'video'
41005   * @param {String} container either 'mp2t' or 'mp4'
41006   * @param {Array} codecs an array of codec strings to add
41007   * @return {String} a valid media mime-type
41008   */
41009  var makeMimeTypeString = function makeMimeTypeString(type, container, codecs) {
41010    // The codecs array is filtered so that falsey values are
41011    // dropped and don't cause Array#join to create spurious
41012    // commas
41013    return type + '/' + container + '; codecs="' + codecs.filter(function (c) {
41014      return !!c;
41015    }).join(', ') + '"';
41016  };
41017
41018  /**
41019   * Returns the type container based on information in the playlist
41020   * @param {Playlist} media the current media playlist
41021   * @return {String} a valid media container type
41022   */
41023  var getContainerType = function getContainerType(media) {
41024    // An initialization segment means the media playlist is an iframe
41025    // playlist or is using the mp4 container. We don't currently
41026    // support iframe playlists, so assume this is signalling mp4
41027    // fragments.
41028    if (media.segments && media.segments.length && media.segments[0].map) {
41029      return 'mp4';
41030    }
41031    return 'mp2t';
41032  };
41033
41034  /**
41035   * Returns a set of codec strings parsed from the playlist or the default
41036   * codec strings if no codecs were specified in the playlist
41037   * @param {Playlist} media the current media playlist
41038   * @return {Object} an object with the video and audio codecs
41039   */
41040  var getCodecs = function getCodecs(media) {
41041    // if the codecs were explicitly specified, use them instead of the
41042    // defaults
41043    var mediaAttributes = media.attributes || {};
41044
41045    if (mediaAttributes.CODECS) {
41046      return parseCodecs(mediaAttributes.CODECS);
41047    }
41048    return defaultCodecs;
41049  };
41050
41051  var audioProfileFromDefault = function audioProfileFromDefault(master, audioGroupId) {
41052    if (!master.mediaGroups.AUDIO || !audioGroupId) {
41053      return null;
41054    }
41055
41056    var audioGroup = master.mediaGroups.AUDIO[audioGroupId];
41057
41058    if (!audioGroup) {
41059      return null;
41060    }
41061
41062    for (var name in audioGroup) {
41063      var audioType = audioGroup[name];
41064
41065      if (audioType.default && audioType.playlists) {
41066        // codec should be the same for all playlists within the audio type
41067        return parseCodecs(audioType.playlists[0].attributes.CODECS).audioProfile;
41068      }
41069    }
41070
41071    return null;
41072  };
41073
41074  /**
41075   * Calculates the MIME type strings for a working configuration of
41076   * SourceBuffers to play variant streams in a master playlist. If
41077   * there is no possible working configuration, an empty array will be
41078   * returned.
41079   *
41080   * @param master {Object} the m3u8 object for the master playlist
41081   * @param media {Object} the m3u8 object for the variant playlist
41082   * @return {Array} the MIME type strings. If the array has more than
41083   * one entry, the first element should be applied to the video
41084   * SourceBuffer and the second to the audio SourceBuffer.
41085   *
41086   * @private
41087   */
41088  var mimeTypesForPlaylist = function mimeTypesForPlaylist(master, media) {
41089    var containerType = getContainerType(media);
41090    var codecInfo = getCodecs(media);
41091    var mediaAttributes = media.attributes || {};
41092    // Default condition for a traditional HLS (no demuxed audio/video)
41093    var isMuxed = true;
41094    var isMaat = false;
41095
41096    if (!media) {
41097      // Not enough information
41098      return [];
41099    }
41100
41101    if (master.mediaGroups.AUDIO && mediaAttributes.AUDIO) {
41102      var audioGroup = master.mediaGroups.AUDIO[mediaAttributes.AUDIO];
41103
41104      // Handle the case where we are in a multiple-audio track scenario
41105      if (audioGroup) {
41106        isMaat = true;
41107        // Start with the everything demuxed then...
41108        isMuxed = false;
41109        // ...check to see if any audio group tracks are muxed (ie. lacking a uri)
41110        for (var groupId in audioGroup) {
41111          // either a uri is present (if the case of HLS and an external playlist), or
41112          // playlists is present (in the case of DASH where we don't have external audio
41113          // playlists)
41114          if (!audioGroup[groupId].uri && !audioGroup[groupId].playlists) {
41115            isMuxed = true;
41116            break;
41117          }
41118        }
41119      }
41120    }
41121
41122    // HLS with multiple-audio tracks must always get an audio codec.
41123    // Put another way, there is no way to have a video-only multiple-audio HLS!
41124    if (isMaat && !codecInfo.audioProfile) {
41125      if (!isMuxed) {
41126        // It is possible for codecs to be specified on the audio media group playlist but
41127        // not on the rendition playlist. This is mostly the case for DASH, where audio and
41128        // video are always separate (and separately specified).
41129        codecInfo.audioProfile = audioProfileFromDefault(master, mediaAttributes.AUDIO);
41130      }
41131
41132      if (!codecInfo.audioProfile) {
41133        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)');
41134        codecInfo.audioProfile = defaultCodecs.audioProfile;
41135      }
41136    }
41137
41138    // Generate the final codec strings from the codec object generated above
41139    var codecStrings = {};
41140
41141    if (codecInfo.videoCodec) {
41142      codecStrings.video = '' + codecInfo.videoCodec + codecInfo.videoObjectTypeIndicator;
41143    }
41144
41145    if (codecInfo.audioProfile) {
41146      codecStrings.audio = 'mp4a.40.' + codecInfo.audioProfile;
41147    }
41148
41149    // Finally, make and return an array with proper mime-types depending on
41150    // the configuration
41151    var justAudio = makeMimeTypeString('audio', containerType, [codecStrings.audio]);
41152    var justVideo = makeMimeTypeString('video', containerType, [codecStrings.video]);
41153    var bothVideoAudio = makeMimeTypeString('video', containerType, [codecStrings.video, codecStrings.audio]);
41154
41155    if (isMaat) {
41156      if (!isMuxed && codecStrings.video) {
41157        return [justVideo, justAudio];
41158      }
41159
41160      if (!isMuxed && !codecStrings.video) {
41161        // There is no muxed content and no video codec string, so this is an audio only
41162        // stream with alternate audio.
41163        return [justAudio, justAudio];
41164      }
41165
41166      // There exists the possiblity that this will return a `video/container`
41167      // mime-type for the first entry in the array even when there is only audio.
41168      // This doesn't appear to be a problem and simplifies the code.
41169      return [bothVideoAudio, justAudio];
41170    }
41171
41172    // If there is no video codec at all, always just return a single
41173    // audio/<container> mime-type
41174    if (!codecStrings.video) {
41175      return [justAudio];
41176    }
41177
41178    // When not using separate audio media groups, audio and video is
41179    // *always* muxed
41180    return [bothVideoAudio];
41181  };
41182
41183  // Utilities
41184
41185  /**
41186   * Returns the CSS value for the specified property on an element
41187   * using `getComputedStyle`. Firefox has a long-standing issue where
41188   * getComputedStyle() may return null when running in an iframe with
41189   * `display: none`.
41190   *
41191   * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397
41192   * @param {HTMLElement} el the htmlelement to work on
41193   * @param {string} the proprety to get the style for
41194   */
41195  var safeGetComputedStyle = function safeGetComputedStyle(el, property) {
41196    var result = void 0;
41197
41198    if (!el) {
41199      return '';
41200    }
41201
41202    result = window_1.getComputedStyle(el);
41203    if (!result) {
41204      return '';
41205    }
41206
41207    return result[property];
41208  };
41209
41210  /**
41211   * Resuable stable sort function
41212   *
41213   * @param {Playlists} array
41214   * @param {Function} sortFn Different comparators
41215   * @function stableSort
41216   */
41217  var stableSort = function stableSort(array, sortFn) {
41218    var newArray = array.slice();
41219
41220    array.sort(function (left, right) {
41221      var cmp = sortFn(left, right);
41222
41223      if (cmp === 0) {
41224        return newArray.indexOf(left) - newArray.indexOf(right);
41225      }
41226      return cmp;
41227    });
41228  };
41229
41230  /**
41231   * A comparator function to sort two playlist object by bandwidth.
41232   *
41233   * @param {Object} left a media playlist object
41234   * @param {Object} right a media playlist object
41235   * @return {Number} Greater than zero if the bandwidth attribute of
41236   * left is greater than the corresponding attribute of right. Less
41237   * than zero if the bandwidth of right is greater than left and
41238   * exactly zero if the two are equal.
41239   */
41240  var comparePlaylistBandwidth = function comparePlaylistBandwidth(left, right) {
41241    var leftBandwidth = void 0;
41242    var rightBandwidth = void 0;
41243
41244    if (left.attributes.BANDWIDTH) {
41245      leftBandwidth = left.attributes.BANDWIDTH;
41246    }
41247    leftBandwidth = leftBandwidth || window_1.Number.MAX_VALUE;
41248    if (right.attributes.BANDWIDTH) {
41249      rightBandwidth = right.attributes.BANDWIDTH;
41250    }
41251    rightBandwidth = rightBandwidth || window_1.Number.MAX_VALUE;
41252
41253    return leftBandwidth - rightBandwidth;
41254  };
41255
41256  /**
41257   * A comparator function to sort two playlist object by resolution (width).
41258   * @param {Object} left a media playlist object
41259   * @param {Object} right a media playlist object
41260   * @return {Number} Greater than zero if the resolution.width attribute of
41261   * left is greater than the corresponding attribute of right. Less
41262   * than zero if the resolution.width of right is greater than left and
41263   * exactly zero if the two are equal.
41264   */
41265  var comparePlaylistResolution = function comparePlaylistResolution(left, right) {
41266    var leftWidth = void 0;
41267    var rightWidth = void 0;
41268
41269    if (left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) {
41270      leftWidth = left.attributes.RESOLUTION.width;
41271    }
41272
41273    leftWidth = leftWidth || window_1.Number.MAX_VALUE;
41274
41275    if (right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) {
41276      rightWidth = right.attributes.RESOLUTION.width;
41277    }
41278
41279    rightWidth = rightWidth || window_1.Number.MAX_VALUE;
41280
41281    // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions
41282    // have the same media dimensions/ resolution
41283    if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) {
41284      return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH;
41285    }
41286    return leftWidth - rightWidth;
41287  };
41288
41289  /**
41290   * Chooses the appropriate media playlist based on bandwidth and player size
41291   *
41292   * @param {Object} master
41293   *        Object representation of the master manifest
41294   * @param {Number} playerBandwidth
41295   *        Current calculated bandwidth of the player
41296   * @param {Number} playerWidth
41297   *        Current width of the player element
41298   * @param {Number} playerHeight
41299   *        Current height of the player element
41300   * @return {Playlist} the highest bitrate playlist less than the
41301   * currently detected bandwidth, accounting for some amount of
41302   * bandwidth variance
41303   */
41304  var simpleSelector = function simpleSelector(master, playerBandwidth, playerWidth, playerHeight) {
41305    // convert the playlists to an intermediary representation to make comparisons easier
41306    var sortedPlaylistReps = master.playlists.map(function (playlist) {
41307      var width = void 0;
41308      var height = void 0;
41309      var bandwidth = void 0;
41310
41311      width = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.width;
41312      height = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height;
41313      bandwidth = playlist.attributes.BANDWIDTH;
41314
41315      bandwidth = bandwidth || window_1.Number.MAX_VALUE;
41316
41317      return {
41318        bandwidth: bandwidth,
41319        width: width,
41320        height: height,
41321        playlist: playlist
41322      };
41323    });
41324
41325    stableSort(sortedPlaylistReps, function (left, right) {
41326      return left.bandwidth - right.bandwidth;
41327    });
41328
41329    // filter out any playlists that have been excluded due to
41330    // incompatible configurations
41331    sortedPlaylistReps = sortedPlaylistReps.filter(function (rep) {
41332      return !Playlist.isIncompatible(rep.playlist);
41333    });
41334
41335    // filter out any playlists that have been disabled manually through the representations
41336    // api or blacklisted temporarily due to playback errors.
41337    var enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) {
41338      return Playlist.isEnabled(rep.playlist);
41339    });
41340
41341    if (!enabledPlaylistReps.length) {
41342      // if there are no enabled playlists, then they have all been blacklisted or disabled
41343      // by the user through the representations api. In this case, ignore blacklisting and
41344      // fallback to what the user wants by using playlists the user has not disabled.
41345      enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) {
41346        return !Playlist.isDisabled(rep.playlist);
41347      });
41348    }
41349
41350    // filter out any variant that has greater effective bitrate
41351    // than the current estimated bandwidth
41352    var bandwidthPlaylistReps = enabledPlaylistReps.filter(function (rep) {
41353      return rep.bandwidth * Config.BANDWIDTH_VARIANCE < playerBandwidth;
41354    });
41355
41356    var highestRemainingBandwidthRep = bandwidthPlaylistReps[bandwidthPlaylistReps.length - 1];
41357
41358    // get all of the renditions with the same (highest) bandwidth
41359    // and then taking the very first element
41360    var bandwidthBestRep = bandwidthPlaylistReps.filter(function (rep) {
41361      return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
41362    })[0];
41363
41364    // filter out playlists without resolution information
41365    var haveResolution = bandwidthPlaylistReps.filter(function (rep) {
41366      return rep.width && rep.height;
41367    });
41368
41369    // sort variants by resolution
41370    stableSort(haveResolution, function (left, right) {
41371      return left.width - right.width;
41372    });
41373
41374    // if we have the exact resolution as the player use it
41375    var resolutionBestRepList = haveResolution.filter(function (rep) {
41376      return rep.width === playerWidth && rep.height === playerHeight;
41377    });
41378
41379    highestRemainingBandwidthRep = resolutionBestRepList[resolutionBestRepList.length - 1];
41380    // ensure that we pick the highest bandwidth variant that have exact resolution
41381    var resolutionBestRep = resolutionBestRepList.filter(function (rep) {
41382      return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
41383    })[0];
41384
41385    var resolutionPlusOneList = void 0;
41386    var resolutionPlusOneSmallest = void 0;
41387    var resolutionPlusOneRep = void 0;
41388
41389    // find the smallest variant that is larger than the player
41390    // if there is no match of exact resolution
41391    if (!resolutionBestRep) {
41392      resolutionPlusOneList = haveResolution.filter(function (rep) {
41393        return rep.width > playerWidth || rep.height > playerHeight;
41394      });
41395
41396      // find all the variants have the same smallest resolution
41397      resolutionPlusOneSmallest = resolutionPlusOneList.filter(function (rep) {
41398        return rep.width === resolutionPlusOneList[0].width && rep.height === resolutionPlusOneList[0].height;
41399      });
41400
41401      // ensure that we also pick the highest bandwidth variant that
41402      // is just-larger-than the video player
41403      highestRemainingBandwidthRep = resolutionPlusOneSmallest[resolutionPlusOneSmallest.length - 1];
41404      resolutionPlusOneRep = resolutionPlusOneSmallest.filter(function (rep) {
41405        return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
41406      })[0];
41407    }
41408
41409    // fallback chain of variants
41410    var chosenRep = resolutionPlusOneRep || resolutionBestRep || bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0];
41411
41412    return chosenRep ? chosenRep.playlist : null;
41413  };
41414
41415  // Playlist Selectors
41416
41417  /**
41418   * Chooses the appropriate media playlist based on the most recent
41419   * bandwidth estimate and the player size.
41420   *
41421   * Expects to be called within the context of an instance of HlsHandler
41422   *
41423   * @return {Playlist} the highest bitrate playlist less than the
41424   * currently detected bandwidth, accounting for some amount of
41425   * bandwidth variance
41426   */
41427  var lastBandwidthSelector = function lastBandwidthSelector() {
41428    return simpleSelector(this.playlists.master, this.systemBandwidth, parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10), parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10));
41429  };
41430
41431  /**
41432   * Chooses the appropriate media playlist based on the potential to rebuffer
41433   *
41434   * @param {Object} settings
41435   *        Object of information required to use this selector
41436   * @param {Object} settings.master
41437   *        Object representation of the master manifest
41438   * @param {Number} settings.currentTime
41439   *        The current time of the player
41440   * @param {Number} settings.bandwidth
41441   *        Current measured bandwidth
41442   * @param {Number} settings.duration
41443   *        Duration of the media
41444   * @param {Number} settings.segmentDuration
41445   *        Segment duration to be used in round trip time calculations
41446   * @param {Number} settings.timeUntilRebuffer
41447   *        Time left in seconds until the player has to rebuffer
41448   * @param {Number} settings.currentTimeline
41449   *        The current timeline segments are being loaded from
41450   * @param {SyncController} settings.syncController
41451   *        SyncController for determining if we have a sync point for a given playlist
41452   * @return {Object|null}
41453   *         {Object} return.playlist
41454   *         The highest bandwidth playlist with the least amount of rebuffering
41455   *         {Number} return.rebufferingImpact
41456   *         The amount of time in seconds switching to this playlist will rebuffer. A
41457   *         negative value means that switching will cause zero rebuffering.
41458   */
41459  var minRebufferMaxBandwidthSelector = function minRebufferMaxBandwidthSelector(settings) {
41460    var master = settings.master,
41461        currentTime = settings.currentTime,
41462        bandwidth = settings.bandwidth,
41463        duration$$1 = settings.duration,
41464        segmentDuration = settings.segmentDuration,
41465        timeUntilRebuffer = settings.timeUntilRebuffer,
41466        currentTimeline = settings.currentTimeline,
41467        syncController = settings.syncController;
41468
41469    // filter out any playlists that have been excluded due to
41470    // incompatible configurations
41471
41472    var compatiblePlaylists = master.playlists.filter(function (playlist) {
41473      return !Playlist.isIncompatible(playlist);
41474    });
41475
41476    // filter out any playlists that have been disabled manually through the representations
41477    // api or blacklisted temporarily due to playback errors.
41478    var enabledPlaylists = compatiblePlaylists.filter(Playlist.isEnabled);
41479
41480    if (!enabledPlaylists.length) {
41481      // if there are no enabled playlists, then they have all been blacklisted or disabled
41482      // by the user through the representations api. In this case, ignore blacklisting and
41483      // fallback to what the user wants by using playlists the user has not disabled.
41484      enabledPlaylists = compatiblePlaylists.filter(function (playlist) {
41485        return !Playlist.isDisabled(playlist);
41486      });
41487    }
41488
41489    var bandwidthPlaylists = enabledPlaylists.filter(Playlist.hasAttribute.bind(null, 'BANDWIDTH'));
41490
41491    var rebufferingEstimates = bandwidthPlaylists.map(function (playlist) {
41492      var syncPoint = syncController.getSyncPoint(playlist, duration$$1, currentTimeline, currentTime);
41493      // If there is no sync point for this playlist, switching to it will require a
41494      // sync request first. This will double the request time
41495      var numRequests = syncPoint ? 1 : 2;
41496      var requestTimeEstimate = Playlist.estimateSegmentRequestTime(segmentDuration, bandwidth, playlist);
41497      var rebufferingImpact = requestTimeEstimate * numRequests - timeUntilRebuffer;
41498
41499      return {
41500        playlist: playlist,
41501        rebufferingImpact: rebufferingImpact
41502      };
41503    });
41504
41505    var noRebufferingPlaylists = rebufferingEstimates.filter(function (estimate) {
41506      return estimate.rebufferingImpact <= 0;
41507    });
41508
41509    // Sort by bandwidth DESC
41510    stableSort(noRebufferingPlaylists, function (a, b) {
41511      return comparePlaylistBandwidth(b.playlist, a.playlist);
41512    });
41513
41514    if (noRebufferingPlaylists.length) {
41515      return noRebufferingPlaylists[0];
41516    }
41517
41518    stableSort(rebufferingEstimates, function (a, b) {
41519      return a.rebufferingImpact - b.rebufferingImpact;
41520    });
41521
41522    return rebufferingEstimates[0] || null;
41523  };
41524
41525  /**
41526   * Chooses the appropriate media playlist, which in this case is the lowest bitrate
41527   * one with video.  If no renditions with video exist, return the lowest audio rendition.
41528   *
41529   * Expects to be called within the context of an instance of HlsHandler
41530   *
41531   * @return {Object|null}
41532   *         {Object} return.playlist
41533   *         The lowest bitrate playlist that contains a video codec.  If no such rendition
41534   *         exists pick the lowest audio rendition.
41535   */
41536  var lowestBitrateCompatibleVariantSelector = function lowestBitrateCompatibleVariantSelector() {
41537    // filter out any playlists that have been excluded due to
41538    // incompatible configurations or playback errors
41539    var playlists = this.playlists.master.playlists.filter(Playlist.isEnabled);
41540
41541    // Sort ascending by bitrate
41542    stableSort(playlists, function (a, b) {
41543      return comparePlaylistBandwidth(a, b);
41544    });
41545
41546    // Parse and assume that playlists with no video codec have no video
41547    // (this is not necessarily true, although it is generally true).
41548    //
41549    // If an entire manifest has no valid videos everything will get filtered
41550    // out.
41551    var playlistsWithVideo = playlists.filter(function (playlist) {
41552      return parseCodecs(playlist.attributes.CODECS).videoCodec;
41553    });
41554
41555    return playlistsWithVideo[0] || null;
41556  };
41557
41558  /**
41559   * @file segment-loader.js
41560   */
41561
41562  // in ms
41563  var CHECK_BUFFER_DELAY = 500;
41564
41565  /**
41566   * Determines if we should call endOfStream on the media source based
41567   * on the state of the buffer or if appened segment was the final
41568   * segment in the playlist.
41569   *
41570   * @param {Object} playlist a media playlist object
41571   * @param {Object} mediaSource the MediaSource object
41572   * @param {Number} segmentIndex the index of segment we last appended
41573   * @returns {Boolean} do we need to call endOfStream on the MediaSource
41574   */
41575  var detectEndOfStream = function detectEndOfStream(playlist, mediaSource, segmentIndex) {
41576    if (!playlist || !mediaSource) {
41577      return false;
41578    }
41579
41580    var segments = playlist.segments;
41581
41582    // determine a few boolean values to help make the branch below easier
41583    // to read
41584    var appendedLastSegment = segmentIndex === segments.length;
41585
41586    // if we've buffered to the end of the video, we need to call endOfStream
41587    // so that MediaSources can trigger the `ended` event when it runs out of
41588    // buffered data instead of waiting for me
41589    return playlist.endList && mediaSource.readyState === 'open' && appendedLastSegment;
41590  };
41591
41592  var finite = function finite(num) {
41593    return typeof num === 'number' && isFinite(num);
41594  };
41595
41596  var illegalMediaSwitch = function illegalMediaSwitch(loaderType, startingMedia, newSegmentMedia) {
41597    // Although these checks should most likely cover non 'main' types, for now it narrows
41598    // the scope of our checks.
41599    if (loaderType !== 'main' || !startingMedia || !newSegmentMedia) {
41600      return null;
41601    }
41602
41603    if (!newSegmentMedia.containsAudio && !newSegmentMedia.containsVideo) {
41604      return 'Neither audio nor video found in segment.';
41605    }
41606
41607    if (startingMedia.containsVideo && !newSegmentMedia.containsVideo) {
41608      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.';
41609    }
41610
41611    if (!startingMedia.containsVideo && newSegmentMedia.containsVideo) {
41612      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.';
41613    }
41614
41615    return null;
41616  };
41617
41618  /**
41619   * Calculates a time value that is safe to remove from the back buffer without interupting
41620   * playback.
41621   *
41622   * @param {TimeRange} seekable
41623   *        The current seekable range
41624   * @param {Number} currentTime
41625   *        The current time of the player
41626   * @param {Number} targetDuration
41627   *        The target duration of the current playlist
41628   * @return {Number}
41629   *         Time that is safe to remove from the back buffer without interupting playback
41630   */
41631  var safeBackBufferTrimTime = function safeBackBufferTrimTime(seekable$$1, currentTime, targetDuration) {
41632    var removeToTime = void 0;
41633
41634    if (seekable$$1.length && seekable$$1.start(0) > 0 && seekable$$1.start(0) < currentTime) {
41635      // If we have a seekable range use that as the limit for what can be removed safely
41636      removeToTime = seekable$$1.start(0);
41637    } else {
41638      // otherwise remove anything older than 30 seconds before the current play head
41639      removeToTime = currentTime - 30;
41640    }
41641
41642    // Don't allow removing from the buffer within target duration of current time
41643    // to avoid the possibility of removing the GOP currently being played which could
41644    // cause playback stalls.
41645    return Math.min(removeToTime, currentTime - targetDuration);
41646  };
41647
41648  var segmentInfoString = function segmentInfoString(segmentInfo) {
41649    var _segmentInfo$segment = segmentInfo.segment,
41650        start = _segmentInfo$segment.start,
41651        end = _segmentInfo$segment.end,
41652        _segmentInfo$playlist = segmentInfo.playlist,
41653        seq = _segmentInfo$playlist.mediaSequence,
41654        id = _segmentInfo$playlist.id,
41655        _segmentInfo$playlist2 = _segmentInfo$playlist.segments,
41656        segments = _segmentInfo$playlist2 === undefined ? [] : _segmentInfo$playlist2,
41657        index = segmentInfo.mediaIndex,
41658        timeline = segmentInfo.timeline;
41659
41660    return ['appending [' + index + '] of [' + seq + ', ' + (seq + segments.length) + '] from playlist [' + id + ']', '[' + start + ' => ' + end + '] in timeline [' + timeline + ']'].join(' ');
41661  };
41662
41663  /**
41664   * An object that manages segment loading and appending.
41665   *
41666   * @class SegmentLoader
41667   * @param {Object} options required and optional options
41668   * @extends videojs.EventTarget
41669   */
41670
41671  var SegmentLoader = function (_videojs$EventTarget) {
41672    inherits$3(SegmentLoader, _videojs$EventTarget);
41673
41674    function SegmentLoader(settings) {
41675      classCallCheck$3(this, SegmentLoader);
41676
41677      // check pre-conditions
41678      var _this = possibleConstructorReturn$3(this, (SegmentLoader.__proto__ || Object.getPrototypeOf(SegmentLoader)).call(this));
41679
41680      if (!settings) {
41681        throw new TypeError('Initialization settings are required');
41682      }
41683      if (typeof settings.currentTime !== 'function') {
41684        throw new TypeError('No currentTime getter specified');
41685      }
41686      if (!settings.mediaSource) {
41687        throw new TypeError('No MediaSource specified');
41688      }
41689      // public properties
41690      _this.bandwidth = settings.bandwidth;
41691      _this.throughput = { rate: 0, count: 0 };
41692      _this.roundTrip = NaN;
41693      _this.resetStats_();
41694      _this.mediaIndex = null;
41695
41696      // private settings
41697      _this.hasPlayed_ = settings.hasPlayed;
41698      _this.currentTime_ = settings.currentTime;
41699      _this.seekable_ = settings.seekable;
41700      _this.seeking_ = settings.seeking;
41701      _this.duration_ = settings.duration;
41702      _this.mediaSource_ = settings.mediaSource;
41703      _this.hls_ = settings.hls;
41704      _this.loaderType_ = settings.loaderType;
41705      _this.startingMedia_ = void 0;
41706      _this.segmentMetadataTrack_ = settings.segmentMetadataTrack;
41707      _this.goalBufferLength_ = settings.goalBufferLength;
41708      _this.sourceType_ = settings.sourceType;
41709      _this.state_ = 'INIT';
41710
41711      // private instance variables
41712      _this.checkBufferTimeout_ = null;
41713      _this.error_ = void 0;
41714      _this.currentTimeline_ = -1;
41715      _this.pendingSegment_ = null;
41716      _this.mimeType_ = null;
41717      _this.sourceUpdater_ = null;
41718      _this.xhrOptions_ = null;
41719
41720      // Fragmented mp4 playback
41721      _this.activeInitSegmentId_ = null;
41722      _this.initSegments_ = {};
41723
41724      _this.decrypter_ = settings.decrypter;
41725
41726      // Manages the tracking and generation of sync-points, mappings
41727      // between a time in the display time and a segment index within
41728      // a playlist
41729      _this.syncController_ = settings.syncController;
41730      _this.syncPoint_ = {
41731        segmentIndex: 0,
41732        time: 0
41733      };
41734
41735      _this.syncController_.on('syncinfoupdate', function () {
41736        return _this.trigger('syncinfoupdate');
41737      });
41738
41739      _this.mediaSource_.addEventListener('sourceopen', function () {
41740        return _this.ended_ = false;
41741      });
41742
41743      // ...for determining the fetch location
41744      _this.fetchAtBuffer_ = false;
41745
41746      _this.logger_ = logger('SegmentLoader[' + _this.loaderType_ + ']');
41747
41748      Object.defineProperty(_this, 'state', {
41749        get: function get$$1() {
41750          return this.state_;
41751        },
41752        set: function set$$1(newState) {
41753          if (newState !== this.state_) {
41754            this.logger_(this.state_ + ' -> ' + newState);
41755            this.state_ = newState;
41756          }
41757        }
41758      });
41759      return _this;
41760    }
41761
41762    /**
41763     * reset all of our media stats
41764     *
41765     * @private
41766     */
41767
41768    createClass$2(SegmentLoader, [{
41769      key: 'resetStats_',
41770      value: function resetStats_() {
41771        this.mediaBytesTransferred = 0;
41772        this.mediaRequests = 0;
41773        this.mediaRequestsAborted = 0;
41774        this.mediaRequestsTimedout = 0;
41775        this.mediaRequestsErrored = 0;
41776        this.mediaTransferDuration = 0;
41777        this.mediaSecondsLoaded = 0;
41778      }
41779
41780      /**
41781       * dispose of the SegmentLoader and reset to the default state
41782       */
41783
41784    }, {
41785      key: 'dispose',
41786      value: function dispose() {
41787        this.state = 'DISPOSED';
41788        this.pause();
41789        this.abort_();
41790        if (this.sourceUpdater_) {
41791          this.sourceUpdater_.dispose();
41792        }
41793        this.resetStats_();
41794      }
41795
41796      /**
41797       * abort anything that is currently doing on with the SegmentLoader
41798       * and reset to a default state
41799       */
41800
41801    }, {
41802      key: 'abort',
41803      value: function abort() {
41804        if (this.state !== 'WAITING') {
41805          if (this.pendingSegment_) {
41806            this.pendingSegment_ = null;
41807          }
41808          return;
41809        }
41810
41811        this.abort_();
41812
41813        // We aborted the requests we were waiting on, so reset the loader's state to READY
41814        // since we are no longer "waiting" on any requests. XHR callback is not always run
41815        // when the request is aborted. This will prevent the loader from being stuck in the
41816        // WAITING state indefinitely.
41817        this.state = 'READY';
41818
41819        // don't wait for buffer check timeouts to begin fetching the
41820        // next segment
41821        if (!this.paused()) {
41822          this.monitorBuffer_();
41823        }
41824      }
41825
41826      /**
41827       * abort all pending xhr requests and null any pending segements
41828       *
41829       * @private
41830       */
41831
41832    }, {
41833      key: 'abort_',
41834      value: function abort_() {
41835        if (this.pendingSegment_) {
41836          this.pendingSegment_.abortRequests();
41837        }
41838
41839        // clear out the segment being processed
41840        this.pendingSegment_ = null;
41841      }
41842
41843      /**
41844       * set an error on the segment loader and null out any pending segements
41845       *
41846       * @param {Error} error the error to set on the SegmentLoader
41847       * @return {Error} the error that was set or that is currently set
41848       */
41849
41850    }, {
41851      key: 'error',
41852      value: function error(_error) {
41853        if (typeof _error !== 'undefined') {
41854          this.error_ = _error;
41855        }
41856
41857        this.pendingSegment_ = null;
41858        return this.error_;
41859      }
41860    }, {
41861      key: 'endOfStream',
41862      value: function endOfStream() {
41863        this.ended_ = true;
41864        this.pause();
41865        this.trigger('ended');
41866      }
41867
41868      /**
41869       * Indicates which time ranges are buffered
41870       *
41871       * @return {TimeRange}
41872       *         TimeRange object representing the current buffered ranges
41873       */
41874
41875    }, {
41876      key: 'buffered_',
41877      value: function buffered_() {
41878        if (!this.sourceUpdater_) {
41879          return videojs$1.createTimeRanges();
41880        }
41881
41882        return this.sourceUpdater_.buffered();
41883      }
41884
41885      /**
41886       * Gets and sets init segment for the provided map
41887       *
41888       * @param {Object} map
41889       *        The map object representing the init segment to get or set
41890       * @param {Boolean=} set
41891       *        If true, the init segment for the provided map should be saved
41892       * @return {Object}
41893       *         map object for desired init segment
41894       */
41895
41896    }, {
41897      key: 'initSegment',
41898      value: function initSegment(map) {
41899        var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
41900
41901        if (!map) {
41902          return null;
41903        }
41904
41905        var id = initSegmentId(map);
41906        var storedMap = this.initSegments_[id];
41907
41908        if (set$$1 && !storedMap && map.bytes) {
41909          this.initSegments_[id] = storedMap = {
41910            resolvedUri: map.resolvedUri,
41911            byterange: map.byterange,
41912            bytes: map.bytes
41913          };
41914        }
41915
41916        return storedMap || map;
41917      }
41918
41919      /**
41920       * Returns true if all configuration required for loading is present, otherwise false.
41921       *
41922       * @return {Boolean} True if the all configuration is ready for loading
41923       * @private
41924       */
41925
41926    }, {
41927      key: 'couldBeginLoading_',
41928      value: function couldBeginLoading_() {
41929        return this.playlist_ && (
41930        // the source updater is created when init_ is called, so either having a
41931        // source updater or being in the INIT state with a mimeType is enough
41932        // to say we have all the needed configuration to start loading.
41933        this.sourceUpdater_ || this.mimeType_ && this.state === 'INIT') && !this.paused();
41934      }
41935
41936      /**
41937       * load a playlist and start to fill the buffer
41938       */
41939
41940    }, {
41941      key: 'load',
41942      value: function load() {
41943        // un-pause
41944        this.monitorBuffer_();
41945
41946        // if we don't have a playlist yet, keep waiting for one to be
41947        // specified
41948        if (!this.playlist_) {
41949          return;
41950        }
41951
41952        // not sure if this is the best place for this
41953        this.syncController_.setDateTimeMapping(this.playlist_);
41954
41955        // if all the configuration is ready, initialize and begin loading
41956        if (this.state === 'INIT' && this.couldBeginLoading_()) {
41957          return this.init_();
41958        }
41959
41960        // if we're in the middle of processing a segment already, don't
41961        // kick off an additional segment request
41962        if (!this.couldBeginLoading_() || this.state !== 'READY' && this.state !== 'INIT') {
41963          return;
41964        }
41965
41966        this.state = 'READY';
41967      }
41968
41969      /**
41970       * Once all the starting parameters have been specified, begin
41971       * operation. This method should only be invoked from the INIT
41972       * state.
41973       *
41974       * @private
41975       */
41976
41977    }, {
41978      key: 'init_',
41979      value: function init_() {
41980        this.state = 'READY';
41981        this.sourceUpdater_ = new SourceUpdater(this.mediaSource_, this.mimeType_, this.loaderType_, this.sourceBufferEmitter_);
41982        this.resetEverything();
41983        return this.monitorBuffer_();
41984      }
41985
41986      /**
41987       * set a playlist on the segment loader
41988       *
41989       * @param {PlaylistLoader} media the playlist to set on the segment loader
41990       */
41991
41992    }, {
41993      key: 'playlist',
41994      value: function playlist(newPlaylist) {
41995        var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
41996
41997        if (!newPlaylist) {
41998          return;
41999        }
42000
42001        var oldPlaylist = this.playlist_;
42002        var segmentInfo = this.pendingSegment_;
42003
42004        this.playlist_ = newPlaylist;
42005        this.xhrOptions_ = options;
42006
42007        // when we haven't started playing yet, the start of a live playlist
42008        // is always our zero-time so force a sync update each time the playlist
42009        // is refreshed from the server
42010        if (!this.hasPlayed_()) {
42011          newPlaylist.syncInfo = {
42012            mediaSequence: newPlaylist.mediaSequence,
42013            time: 0
42014          };
42015        }
42016
42017        var oldId = oldPlaylist ? oldPlaylist.id : null;
42018
42019        this.logger_('playlist update [' + oldId + ' => ' + newPlaylist.id + ']');
42020
42021        // in VOD, this is always a rendition switch (or we updated our syncInfo above)
42022        // in LIVE, we always want to update with new playlists (including refreshes)
42023        this.trigger('syncinfoupdate');
42024
42025        // if we were unpaused but waiting for a playlist, start
42026        // buffering now
42027        if (this.state === 'INIT' && this.couldBeginLoading_()) {
42028          return this.init_();
42029        }
42030
42031        if (!oldPlaylist || oldPlaylist.uri !== newPlaylist.uri) {
42032          if (this.mediaIndex !== null) {
42033            // we must "resync" the segment loader when we switch renditions and
42034            // the segment loader is already synced to the previous rendition
42035            this.resyncLoader();
42036          }
42037
42038          // the rest of this function depends on `oldPlaylist` being defined
42039          return;
42040        }
42041
42042        // we reloaded the same playlist so we are in a live scenario
42043        // and we will likely need to adjust the mediaIndex
42044        var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence;
42045
42046        this.logger_('live window shift [' + mediaSequenceDiff + ']');
42047
42048        // update the mediaIndex on the SegmentLoader
42049        // this is important because we can abort a request and this value must be
42050        // equal to the last appended mediaIndex
42051        if (this.mediaIndex !== null) {
42052          this.mediaIndex -= mediaSequenceDiff;
42053        }
42054
42055        // update the mediaIndex on the SegmentInfo object
42056        // this is important because we will update this.mediaIndex with this value
42057        // in `handleUpdateEnd_` after the segment has been successfully appended
42058        if (segmentInfo) {
42059          segmentInfo.mediaIndex -= mediaSequenceDiff;
42060
42061          // we need to update the referenced segment so that timing information is
42062          // saved for the new playlist's segment, however, if the segment fell off the
42063          // playlist, we can leave the old reference and just lose the timing info
42064          if (segmentInfo.mediaIndex >= 0) {
42065            segmentInfo.segment = newPlaylist.segments[segmentInfo.mediaIndex];
42066          }
42067        }
42068
42069        this.syncController_.saveExpiredSegmentInfo(oldPlaylist, newPlaylist);
42070      }
42071
42072      /**
42073       * Prevent the loader from fetching additional segments. If there
42074       * is a segment request outstanding, it will finish processing
42075       * before the loader halts. A segment loader can be unpaused by
42076       * calling load().
42077       */
42078
42079    }, {
42080      key: 'pause',
42081      value: function pause() {
42082        if (this.checkBufferTimeout_) {
42083          window_1.clearTimeout(this.checkBufferTimeout_);
42084
42085          this.checkBufferTimeout_ = null;
42086        }
42087      }
42088
42089      /**
42090       * Returns whether the segment loader is fetching additional
42091       * segments when given the opportunity. This property can be
42092       * modified through calls to pause() and load().
42093       */
42094
42095    }, {
42096      key: 'paused',
42097      value: function paused() {
42098        return this.checkBufferTimeout_ === null;
42099      }
42100
42101      /**
42102       * create/set the following mimetype on the SourceBuffer through a
42103       * SourceUpdater
42104       *
42105       * @param {String} mimeType the mime type string to use
42106       * @param {Object} sourceBufferEmitter an event emitter that fires when a source buffer
42107       * is added to the media source
42108       */
42109
42110    }, {
42111      key: 'mimeType',
42112      value: function mimeType(_mimeType, sourceBufferEmitter) {
42113        if (this.mimeType_) {
42114          return;
42115        }
42116
42117        this.mimeType_ = _mimeType;
42118        this.sourceBufferEmitter_ = sourceBufferEmitter;
42119        // if we were unpaused but waiting for a sourceUpdater, start
42120        // buffering now
42121        if (this.state === 'INIT' && this.couldBeginLoading_()) {
42122          this.init_();
42123        }
42124      }
42125
42126      /**
42127       * Delete all the buffered data and reset the SegmentLoader
42128       */
42129
42130    }, {
42131      key: 'resetEverything',
42132      value: function resetEverything() {
42133        this.ended_ = false;
42134        this.resetLoader();
42135        this.remove(0, this.duration_());
42136        this.trigger('reseteverything');
42137      }
42138
42139      /**
42140       * Force the SegmentLoader to resync and start loading around the currentTime instead
42141       * of starting at the end of the buffer
42142       *
42143       * Useful for fast quality changes
42144       */
42145
42146    }, {
42147      key: 'resetLoader',
42148      value: function resetLoader() {
42149        this.fetchAtBuffer_ = false;
42150        this.resyncLoader();
42151      }
42152
42153      /**
42154       * Force the SegmentLoader to restart synchronization and make a conservative guess
42155       * before returning to the simple walk-forward method
42156       */
42157
42158    }, {
42159      key: 'resyncLoader',
42160      value: function resyncLoader() {
42161        this.mediaIndex = null;
42162        this.syncPoint_ = null;
42163        this.abort();
42164      }
42165
42166      /**
42167       * Remove any data in the source buffer between start and end times
42168       * @param {Number} start - the start time of the region to remove from the buffer
42169       * @param {Number} end - the end time of the region to remove from the buffer
42170       */
42171
42172    }, {
42173      key: 'remove',
42174      value: function remove(start, end) {
42175        if (this.sourceUpdater_) {
42176          this.sourceUpdater_.remove(start, end);
42177        }
42178        removeCuesFromTrack(start, end, this.segmentMetadataTrack_);
42179      }
42180
42181      /**
42182       * (re-)schedule monitorBufferTick_ to run as soon as possible
42183       *
42184       * @private
42185       */
42186
42187    }, {
42188      key: 'monitorBuffer_',
42189      value: function monitorBuffer_() {
42190        if (this.checkBufferTimeout_) {
42191          window_1.clearTimeout(this.checkBufferTimeout_);
42192        }
42193
42194        this.checkBufferTimeout_ = window_1.setTimeout(this.monitorBufferTick_.bind(this), 1);
42195      }
42196
42197      /**
42198       * As long as the SegmentLoader is in the READY state, periodically
42199       * invoke fillBuffer_().
42200       *
42201       * @private
42202       */
42203
42204    }, {
42205      key: 'monitorBufferTick_',
42206      value: function monitorBufferTick_() {
42207        if (this.state === 'READY') {
42208          this.fillBuffer_();
42209        }
42210
42211        if (this.checkBufferTimeout_) {
42212          window_1.clearTimeout(this.checkBufferTimeout_);
42213        }
42214
42215        this.checkBufferTimeout_ = window_1.setTimeout(this.monitorBufferTick_.bind(this), CHECK_BUFFER_DELAY);
42216      }
42217
42218      /**
42219       * fill the buffer with segements unless the sourceBuffers are
42220       * currently updating
42221       *
42222       * Note: this function should only ever be called by monitorBuffer_
42223       * and never directly
42224       *
42225       * @private
42226       */
42227
42228    }, {
42229      key: 'fillBuffer_',
42230      value: function fillBuffer_() {
42231        if (this.sourceUpdater_.updating()) {
42232          return;
42233        }
42234
42235        if (!this.syncPoint_) {
42236          this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_());
42237        }
42238
42239        // see if we need to begin loading immediately
42240        var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_);
42241
42242        if (!segmentInfo) {
42243          return;
42244        }
42245
42246        var isEndOfStream = detectEndOfStream(this.playlist_, this.mediaSource_, segmentInfo.mediaIndex);
42247
42248        if (isEndOfStream) {
42249          this.endOfStream();
42250          return;
42251        }
42252
42253        if (segmentInfo.mediaIndex === this.playlist_.segments.length - 1 && this.mediaSource_.readyState === 'ended' && !this.seeking_()) {
42254          return;
42255        }
42256
42257        // We will need to change timestampOffset of the sourceBuffer if either of
42258        // the following conditions are true:
42259        // - The segment.timeline !== this.currentTimeline
42260        //   (we are crossing a discontinuity somehow)
42261        // - The "timestampOffset" for the start of this segment is less than
42262        //   the currently set timestampOffset
42263        if (segmentInfo.timeline !== this.currentTimeline_ || segmentInfo.startOfSegment !== null && segmentInfo.startOfSegment < this.sourceUpdater_.timestampOffset()) {
42264          this.syncController_.reset();
42265          segmentInfo.timestampOffset = segmentInfo.startOfSegment;
42266        }
42267
42268        this.loadSegment_(segmentInfo);
42269      }
42270
42271      /**
42272       * Determines what segment request should be made, given current playback
42273       * state.
42274       *
42275       * @param {TimeRanges} buffered - the state of the buffer
42276       * @param {Object} playlist - the playlist object to fetch segments from
42277       * @param {Number} mediaIndex - the previous mediaIndex fetched or null
42278       * @param {Boolean} hasPlayed - a flag indicating whether we have played or not
42279       * @param {Number} currentTime - the playback position in seconds
42280       * @param {Object} syncPoint - a segment info object that describes the
42281       * @returns {Object} a segment request object that describes the segment to load
42282       */
42283
42284    }, {
42285      key: 'checkBuffer_',
42286      value: function checkBuffer_(buffered, playlist, mediaIndex, hasPlayed, currentTime, syncPoint) {
42287        var lastBufferedEnd = 0;
42288        var startOfSegment = void 0;
42289
42290        if (buffered.length) {
42291          lastBufferedEnd = buffered.end(buffered.length - 1);
42292        }
42293
42294        var bufferedTime = Math.max(0, lastBufferedEnd - currentTime);
42295
42296        if (!playlist.segments.length) {
42297          return null;
42298        }
42299
42300        // if there is plenty of content buffered, and the video has
42301        // been played before relax for awhile
42302        if (bufferedTime >= this.goalBufferLength_()) {
42303          return null;
42304        }
42305
42306        // if the video has not yet played once, and we already have
42307        // one segment downloaded do nothing
42308        if (!hasPlayed && bufferedTime >= 1) {
42309          return null;
42310        }
42311
42312        // When the syncPoint is null, there is no way of determining a good
42313        // conservative segment index to fetch from
42314        // The best thing to do here is to get the kind of sync-point data by
42315        // making a request
42316        if (syncPoint === null) {
42317          mediaIndex = this.getSyncSegmentCandidate_(playlist);
42318          return this.generateSegmentInfo_(playlist, mediaIndex, null, true);
42319        }
42320
42321        // Under normal playback conditions fetching is a simple walk forward
42322        if (mediaIndex !== null) {
42323          var segment = playlist.segments[mediaIndex];
42324
42325          if (segment && segment.end) {
42326            startOfSegment = segment.end;
42327          } else {
42328            startOfSegment = lastBufferedEnd;
42329          }
42330          return this.generateSegmentInfo_(playlist, mediaIndex + 1, startOfSegment, false);
42331        }
42332
42333        // There is a sync-point but the lack of a mediaIndex indicates that
42334        // we need to make a good conservative guess about which segment to
42335        // fetch
42336        if (this.fetchAtBuffer_) {
42337          // Find the segment containing the end of the buffer
42338          var mediaSourceInfo = Playlist.getMediaInfoForTime(playlist, lastBufferedEnd, syncPoint.segmentIndex, syncPoint.time);
42339
42340          mediaIndex = mediaSourceInfo.mediaIndex;
42341          startOfSegment = mediaSourceInfo.startTime;
42342        } else {
42343          // Find the segment containing currentTime
42344          var _mediaSourceInfo = Playlist.getMediaInfoForTime(playlist, currentTime, syncPoint.segmentIndex, syncPoint.time);
42345
42346          mediaIndex = _mediaSourceInfo.mediaIndex;
42347          startOfSegment = _mediaSourceInfo.startTime;
42348        }
42349
42350        return this.generateSegmentInfo_(playlist, mediaIndex, startOfSegment, false);
42351      }
42352
42353      /**
42354       * The segment loader has no recourse except to fetch a segment in the
42355       * current playlist and use the internal timestamps in that segment to
42356       * generate a syncPoint. This function returns a good candidate index
42357       * for that process.
42358       *
42359       * @param {Object} playlist - the playlist object to look for a
42360       * @returns {Number} An index of a segment from the playlist to load
42361       */
42362
42363    }, {
42364      key: 'getSyncSegmentCandidate_',
42365      value: function getSyncSegmentCandidate_(playlist) {
42366        var _this2 = this;
42367
42368        if (this.currentTimeline_ === -1) {
42369          return 0;
42370        }
42371
42372        var segmentIndexArray = playlist.segments.map(function (s, i) {
42373          return {
42374            timeline: s.timeline,
42375            segmentIndex: i
42376          };
42377        }).filter(function (s) {
42378          return s.timeline === _this2.currentTimeline_;
42379        });
42380
42381        if (segmentIndexArray.length) {
42382          return segmentIndexArray[Math.min(segmentIndexArray.length - 1, 1)].segmentIndex;
42383        }
42384
42385        return Math.max(playlist.segments.length - 1, 0);
42386      }
42387    }, {
42388      key: 'generateSegmentInfo_',
42389      value: function generateSegmentInfo_(playlist, mediaIndex, startOfSegment, isSyncRequest) {
42390        if (mediaIndex < 0 || mediaIndex >= playlist.segments.length) {
42391          return null;
42392        }
42393
42394        var segment = playlist.segments[mediaIndex];
42395
42396        return {
42397          requestId: 'segment-loader-' + Math.random(),
42398          // resolve the segment URL relative to the playlist
42399          uri: segment.resolvedUri,
42400          // the segment's mediaIndex at the time it was requested
42401          mediaIndex: mediaIndex,
42402          // whether or not to update the SegmentLoader's state with this
42403          // segment's mediaIndex
42404          isSyncRequest: isSyncRequest,
42405          startOfSegment: startOfSegment,
42406          // the segment's playlist
42407          playlist: playlist,
42408          // unencrypted bytes of the segment
42409          bytes: null,
42410          // when a key is defined for this segment, the encrypted bytes
42411          encryptedBytes: null,
42412          // The target timestampOffset for this segment when we append it
42413          // to the source buffer
42414          timestampOffset: null,
42415          // The timeline that the segment is in
42416          timeline: segment.timeline,
42417          // The expected duration of the segment in seconds
42418          duration: segment.duration,
42419          // retain the segment in case the playlist updates while doing an async process
42420          segment: segment
42421        };
42422      }
42423
42424      /**
42425       * Determines if the network has enough bandwidth to complete the current segment
42426       * request in a timely manner. If not, the request will be aborted early and bandwidth
42427       * updated to trigger a playlist switch.
42428       *
42429       * @param {Object} stats
42430       *        Object containing stats about the request timing and size
42431       * @return {Boolean} True if the request was aborted, false otherwise
42432       * @private
42433       */
42434
42435    }, {
42436      key: 'abortRequestEarly_',
42437      value: function abortRequestEarly_(stats) {
42438        if (this.hls_.tech_.paused() ||
42439        // Don't abort if the current playlist is on the lowestEnabledRendition
42440        // TODO: Replace using timeout with a boolean indicating whether this playlist is
42441        //       the lowestEnabledRendition.
42442        !this.xhrOptions_.timeout ||
42443        // Don't abort if we have no bandwidth information to estimate segment sizes
42444        !this.playlist_.attributes.BANDWIDTH) {
42445          return false;
42446        }
42447
42448        // Wait at least 1 second since the first byte of data has been received before
42449        // using the calculated bandwidth from the progress event to allow the bitrate
42450        // to stabilize
42451        if (Date.now() - (stats.firstBytesReceivedAt || Date.now()) < 1000) {
42452          return false;
42453        }
42454
42455        var currentTime = this.currentTime_();
42456        var measuredBandwidth = stats.bandwidth;
42457        var segmentDuration = this.pendingSegment_.duration;
42458
42459        var requestTimeRemaining = Playlist.estimateSegmentRequestTime(segmentDuration, measuredBandwidth, this.playlist_, stats.bytesReceived);
42460
42461        // Subtract 1 from the timeUntilRebuffer so we still consider an early abort
42462        // if we are only left with less than 1 second when the request completes.
42463        // A negative timeUntilRebuffering indicates we are already rebuffering
42464        var timeUntilRebuffer$$1 = timeUntilRebuffer(this.buffered_(), currentTime, this.hls_.tech_.playbackRate()) - 1;
42465
42466        // Only consider aborting early if the estimated time to finish the download
42467        // is larger than the estimated time until the player runs out of forward buffer
42468        if (requestTimeRemaining <= timeUntilRebuffer$$1) {
42469          return false;
42470        }
42471
42472        var switchCandidate = minRebufferMaxBandwidthSelector({
42473          master: this.hls_.playlists.master,
42474          currentTime: currentTime,
42475          bandwidth: measuredBandwidth,
42476          duration: this.duration_(),
42477          segmentDuration: segmentDuration,
42478          timeUntilRebuffer: timeUntilRebuffer$$1,
42479          currentTimeline: this.currentTimeline_,
42480          syncController: this.syncController_
42481        });
42482
42483        if (!switchCandidate) {
42484          return;
42485        }
42486
42487        var rebufferingImpact = requestTimeRemaining - timeUntilRebuffer$$1;
42488
42489        var timeSavedBySwitching = rebufferingImpact - switchCandidate.rebufferingImpact;
42490
42491        var minimumTimeSaving = 0.5;
42492
42493        // If we are already rebuffering, increase the amount of variance we add to the
42494        // potential round trip time of the new request so that we are not too aggressive
42495        // with switching to a playlist that might save us a fraction of a second.
42496        if (timeUntilRebuffer$$1 <= TIME_FUDGE_FACTOR) {
42497          minimumTimeSaving = 1;
42498        }
42499
42500        if (!switchCandidate.playlist || switchCandidate.playlist.uri === this.playlist_.uri || timeSavedBySwitching < minimumTimeSaving) {
42501          return false;
42502        }
42503
42504        // set the bandwidth to that of the desired playlist being sure to scale by
42505        // BANDWIDTH_VARIANCE and add one so the playlist selector does not exclude it
42506        // don't trigger a bandwidthupdate as the bandwidth is artifial
42507        this.bandwidth = switchCandidate.playlist.attributes.BANDWIDTH * Config.BANDWIDTH_VARIANCE + 1;
42508        this.abort();
42509        this.trigger('earlyabort');
42510        return true;
42511      }
42512
42513      /**
42514       * XHR `progress` event handler
42515       *
42516       * @param {Event}
42517       *        The XHR `progress` event
42518       * @param {Object} simpleSegment
42519       *        A simplified segment object copy
42520       * @private
42521       */
42522
42523    }, {
42524      key: 'handleProgress_',
42525      value: function handleProgress_(event, simpleSegment) {
42526        if (!this.pendingSegment_ || simpleSegment.requestId !== this.pendingSegment_.requestId || this.abortRequestEarly_(simpleSegment.stats)) {
42527          return;
42528        }
42529
42530        this.trigger('progress');
42531      }
42532
42533      /**
42534       * load a specific segment from a request into the buffer
42535       *
42536       * @private
42537       */
42538
42539    }, {
42540      key: 'loadSegment_',
42541      value: function loadSegment_(segmentInfo) {
42542        this.state = 'WAITING';
42543        this.pendingSegment_ = segmentInfo;
42544        this.trimBackBuffer_(segmentInfo);
42545
42546        segmentInfo.abortRequests = mediaSegmentRequest(this.hls_.xhr, this.xhrOptions_, this.decrypter_, this.createSimplifiedSegmentObj_(segmentInfo),
42547        // progress callback
42548        this.handleProgress_.bind(this), this.segmentRequestFinished_.bind(this));
42549      }
42550
42551      /**
42552       * trim the back buffer so that we don't have too much data
42553       * in the source buffer
42554       *
42555       * @private
42556       *
42557       * @param {Object} segmentInfo - the current segment
42558       */
42559
42560    }, {
42561      key: 'trimBackBuffer_',
42562      value: function trimBackBuffer_(segmentInfo) {
42563        var removeToTime = safeBackBufferTrimTime(this.seekable_(), this.currentTime_(), this.playlist_.targetDuration || 10);
42564
42565        // Chrome has a hard limit of 150MB of
42566        // buffer and a very conservative "garbage collector"
42567        // We manually clear out the old buffer to ensure
42568        // we don't trigger the QuotaExceeded error
42569        // on the source buffer during subsequent appends
42570
42571        if (removeToTime > 0) {
42572          this.remove(0, removeToTime);
42573        }
42574      }
42575
42576      /**
42577       * created a simplified copy of the segment object with just the
42578       * information necessary to perform the XHR and decryption
42579       *
42580       * @private
42581       *
42582       * @param {Object} segmentInfo - the current segment
42583       * @returns {Object} a simplified segment object copy
42584       */
42585
42586    }, {
42587      key: 'createSimplifiedSegmentObj_',
42588      value: function createSimplifiedSegmentObj_(segmentInfo) {
42589        var segment = segmentInfo.segment;
42590        var simpleSegment = {
42591          resolvedUri: segment.resolvedUri,
42592          byterange: segment.byterange,
42593          requestId: segmentInfo.requestId
42594        };
42595
42596        if (segment.key) {
42597          // if the media sequence is greater than 2^32, the IV will be incorrect
42598          // assuming 10s segments, that would be about 1300 years
42599          var iv = segment.key.iv || new Uint32Array([0, 0, 0, segmentInfo.mediaIndex + segmentInfo.playlist.mediaSequence]);
42600
42601          simpleSegment.key = {
42602            resolvedUri: segment.key.resolvedUri,
42603            iv: iv
42604          };
42605        }
42606
42607        if (segment.map) {
42608          simpleSegment.map = this.initSegment(segment.map);
42609        }
42610
42611        return simpleSegment;
42612      }
42613
42614      /**
42615       * Handle the callback from the segmentRequest function and set the
42616       * associated SegmentLoader state and errors if necessary
42617       *
42618       * @private
42619       */
42620
42621    }, {
42622      key: 'segmentRequestFinished_',
42623      value: function segmentRequestFinished_(error, simpleSegment) {
42624        // every request counts as a media request even if it has been aborted
42625        // or canceled due to a timeout
42626        this.mediaRequests += 1;
42627
42628        if (simpleSegment.stats) {
42629          this.mediaBytesTransferred += simpleSegment.stats.bytesReceived;
42630          this.mediaTransferDuration += simpleSegment.stats.roundTripTime;
42631        }
42632
42633        // The request was aborted and the SegmentLoader has already been reset
42634        if (!this.pendingSegment_) {
42635          this.mediaRequestsAborted += 1;
42636          return;
42637        }
42638
42639        // the request was aborted and the SegmentLoader has already started
42640        // another request. this can happen when the timeout for an aborted
42641        // request triggers due to a limitation in the XHR library
42642        // do not count this as any sort of request or we risk double-counting
42643        if (simpleSegment.requestId !== this.pendingSegment_.requestId) {
42644          return;
42645        }
42646
42647        // an error occurred from the active pendingSegment_ so reset everything
42648        if (error) {
42649          this.pendingSegment_ = null;
42650          this.state = 'READY';
42651
42652          // the requests were aborted just record the aborted stat and exit
42653          // this is not a true error condition and nothing corrective needs
42654          // to be done
42655          if (error.code === REQUEST_ERRORS.ABORTED) {
42656            this.mediaRequestsAborted += 1;
42657            return;
42658          }
42659
42660          this.pause();
42661
42662          // the error is really just that at least one of the requests timed-out
42663          // set the bandwidth to a very low value and trigger an ABR switch to
42664          // take emergency action
42665          if (error.code === REQUEST_ERRORS.TIMEOUT) {
42666            this.mediaRequestsTimedout += 1;
42667            this.bandwidth = 1;
42668            this.roundTrip = NaN;
42669            this.trigger('bandwidthupdate');
42670            return;
42671          }
42672
42673          // if control-flow has arrived here, then the error is real
42674          // emit an error event to blacklist the current playlist
42675          this.mediaRequestsErrored += 1;
42676          this.error(error);
42677          this.trigger('error');
42678          return;
42679        }
42680
42681        // the response was a success so set any bandwidth stats the request
42682        // generated for ABR purposes
42683        this.bandwidth = simpleSegment.stats.bandwidth;
42684        this.roundTrip = simpleSegment.stats.roundTripTime;
42685
42686        // if this request included an initialization segment, save that data
42687        // to the initSegment cache
42688        if (simpleSegment.map) {
42689          simpleSegment.map = this.initSegment(simpleSegment.map, true);
42690        }
42691
42692        this.processSegmentResponse_(simpleSegment);
42693      }
42694
42695      /**
42696       * Move any important data from the simplified segment object
42697       * back to the real segment object for future phases
42698       *
42699       * @private
42700       */
42701
42702    }, {
42703      key: 'processSegmentResponse_',
42704      value: function processSegmentResponse_(simpleSegment) {
42705        var segmentInfo = this.pendingSegment_;
42706
42707        segmentInfo.bytes = simpleSegment.bytes;
42708        if (simpleSegment.map) {
42709          segmentInfo.segment.map.bytes = simpleSegment.map.bytes;
42710        }
42711
42712        segmentInfo.endOfAllRequests = simpleSegment.endOfAllRequests;
42713        this.handleSegment_();
42714      }
42715
42716      /**
42717       * append a decrypted segement to the SourceBuffer through a SourceUpdater
42718       *
42719       * @private
42720       */
42721
42722    }, {
42723      key: 'handleSegment_',
42724      value: function handleSegment_() {
42725        var _this3 = this;
42726
42727        if (!this.pendingSegment_) {
42728          this.state = 'READY';
42729          return;
42730        }
42731
42732        var segmentInfo = this.pendingSegment_;
42733        var segment = segmentInfo.segment;
42734        var timingInfo = this.syncController_.probeSegmentInfo(segmentInfo);
42735
42736        // When we have our first timing info, determine what media types this loader is
42737        // dealing with. Although we're maintaining extra state, it helps to preserve the
42738        // separation of segment loader from the actual source buffers.
42739        if (typeof this.startingMedia_ === 'undefined' && timingInfo && (
42740        // Guard against cases where we're not getting timing info at all until we are
42741        // certain that all streams will provide it.
42742        timingInfo.containsAudio || timingInfo.containsVideo)) {
42743          this.startingMedia_ = {
42744            containsAudio: timingInfo.containsAudio,
42745            containsVideo: timingInfo.containsVideo
42746          };
42747        }
42748
42749        var illegalMediaSwitchError = illegalMediaSwitch(this.loaderType_, this.startingMedia_, timingInfo);
42750
42751        if (illegalMediaSwitchError) {
42752          this.error({
42753            message: illegalMediaSwitchError,
42754            blacklistDuration: Infinity
42755          });
42756          this.trigger('error');
42757          return;
42758        }
42759
42760        if (segmentInfo.isSyncRequest) {
42761          this.trigger('syncinfoupdate');
42762          this.pendingSegment_ = null;
42763          this.state = 'READY';
42764          return;
42765        }
42766
42767        if (segmentInfo.timestampOffset !== null && segmentInfo.timestampOffset !== this.sourceUpdater_.timestampOffset()) {
42768          this.sourceUpdater_.timestampOffset(segmentInfo.timestampOffset);
42769          // fired when a timestamp offset is set in HLS (can also identify discontinuities)
42770          this.trigger('timestampoffset');
42771        }
42772
42773        var timelineMapping = this.syncController_.mappingForTimeline(segmentInfo.timeline);
42774
42775        if (timelineMapping !== null) {
42776          this.trigger({
42777            type: 'segmenttimemapping',
42778            mapping: timelineMapping
42779          });
42780        }
42781
42782        this.state = 'APPENDING';
42783
42784        // if the media initialization segment is changing, append it
42785        // before the content segment
42786        if (segment.map) {
42787          var initId = initSegmentId(segment.map);
42788
42789          if (!this.activeInitSegmentId_ || this.activeInitSegmentId_ !== initId) {
42790            var initSegment = this.initSegment(segment.map);
42791
42792            this.sourceUpdater_.appendBuffer(initSegment.bytes, function () {
42793              _this3.activeInitSegmentId_ = initId;
42794            });
42795          }
42796        }
42797
42798        segmentInfo.byteLength = segmentInfo.bytes.byteLength;
42799        if (typeof segment.start === 'number' && typeof segment.end === 'number') {
42800          this.mediaSecondsLoaded += segment.end - segment.start;
42801        } else {
42802          this.mediaSecondsLoaded += segment.duration;
42803        }
42804
42805        this.logger_(segmentInfoString(segmentInfo));
42806
42807        this.sourceUpdater_.appendBuffer(segmentInfo.bytes, this.handleUpdateEnd_.bind(this));
42808      }
42809
42810      /**
42811       * callback to run when appendBuffer is finished. detects if we are
42812       * in a good state to do things with the data we got, or if we need
42813       * to wait for more
42814       *
42815       * @private
42816       */
42817
42818    }, {
42819      key: 'handleUpdateEnd_',
42820      value: function handleUpdateEnd_() {
42821        if (!this.pendingSegment_) {
42822          this.state = 'READY';
42823          if (!this.paused()) {
42824            this.monitorBuffer_();
42825          }
42826          return;
42827        }
42828
42829        var segmentInfo = this.pendingSegment_;
42830        var segment = segmentInfo.segment;
42831        var isWalkingForward = this.mediaIndex !== null;
42832
42833        this.pendingSegment_ = null;
42834        this.recordThroughput_(segmentInfo);
42835        this.addSegmentMetadataCue_(segmentInfo);
42836
42837        this.state = 'READY';
42838
42839        this.mediaIndex = segmentInfo.mediaIndex;
42840        this.fetchAtBuffer_ = true;
42841        this.currentTimeline_ = segmentInfo.timeline;
42842
42843        // We must update the syncinfo to recalculate the seekable range before
42844        // the following conditional otherwise it may consider this a bad "guess"
42845        // and attempt to resync when the post-update seekable window and live
42846        // point would mean that this was the perfect segment to fetch
42847        this.trigger('syncinfoupdate');
42848
42849        // If we previously appended a segment that ends more than 3 targetDurations before
42850        // the currentTime_ that means that our conservative guess was too conservative.
42851        // In that case, reset the loader state so that we try to use any information gained
42852        // from the previous request to create a new, more accurate, sync-point.
42853        if (segment.end && this.currentTime_() - segment.end > segmentInfo.playlist.targetDuration * 3) {
42854          this.resetEverything();
42855          return;
42856        }
42857
42858        // Don't do a rendition switch unless we have enough time to get a sync segment
42859        // and conservatively guess
42860        if (isWalkingForward) {
42861          this.trigger('bandwidthupdate');
42862        }
42863        this.trigger('progress');
42864
42865        // any time an update finishes and the last segment is in the
42866        // buffer, end the stream. this ensures the "ended" event will
42867        // fire if playback reaches that point.
42868        var isEndOfStream = detectEndOfStream(segmentInfo.playlist, this.mediaSource_, segmentInfo.mediaIndex + 1);
42869
42870        if (isEndOfStream) {
42871          this.endOfStream();
42872        }
42873
42874        if (!this.paused()) {
42875          this.monitorBuffer_();
42876        }
42877      }
42878
42879      /**
42880       * Records the current throughput of the decrypt, transmux, and append
42881       * portion of the semgment pipeline. `throughput.rate` is a the cumulative
42882       * moving average of the throughput. `throughput.count` is the number of
42883       * data points in the average.
42884       *
42885       * @private
42886       * @param {Object} segmentInfo the object returned by loadSegment
42887       */
42888
42889    }, {
42890      key: 'recordThroughput_',
42891      value: function recordThroughput_(segmentInfo) {
42892        var rate = this.throughput.rate;
42893        // Add one to the time to ensure that we don't accidentally attempt to divide
42894        // by zero in the case where the throughput is ridiculously high
42895        var segmentProcessingTime = Date.now() - segmentInfo.endOfAllRequests + 1;
42896        // Multiply by 8000 to convert from bytes/millisecond to bits/second
42897        var segmentProcessingThroughput = Math.floor(segmentInfo.byteLength / segmentProcessingTime * 8 * 1000);
42898
42899        // This is just a cumulative moving average calculation:
42900        //   newAvg = oldAvg + (sample - oldAvg) / (sampleCount + 1)
42901        this.throughput.rate += (segmentProcessingThroughput - rate) / ++this.throughput.count;
42902      }
42903
42904      /**
42905       * Adds a cue to the segment-metadata track with some metadata information about the
42906       * segment
42907       *
42908       * @private
42909       * @param {Object} segmentInfo
42910       *        the object returned by loadSegment
42911       * @method addSegmentMetadataCue_
42912       */
42913
42914    }, {
42915      key: 'addSegmentMetadataCue_',
42916      value: function addSegmentMetadataCue_(segmentInfo) {
42917        if (!this.segmentMetadataTrack_) {
42918          return;
42919        }
42920
42921        var segment = segmentInfo.segment;
42922        var start = segment.start;
42923        var end = segment.end;
42924
42925        // Do not try adding the cue if the start and end times are invalid.
42926        if (!finite(start) || !finite(end)) {
42927          return;
42928        }
42929
42930        removeCuesFromTrack(start, end, this.segmentMetadataTrack_);
42931
42932        var Cue = window_1.WebKitDataCue || window_1.VTTCue;
42933        var value = {
42934          bandwidth: segmentInfo.playlist.attributes.BANDWIDTH,
42935          resolution: segmentInfo.playlist.attributes.RESOLUTION,
42936          codecs: segmentInfo.playlist.attributes.CODECS,
42937          byteLength: segmentInfo.byteLength,
42938          uri: segmentInfo.uri,
42939          timeline: segmentInfo.timeline,
42940          playlist: segmentInfo.playlist.uri,
42941          start: start,
42942          end: end
42943        };
42944        var data = JSON.stringify(value);
42945        var cue = new Cue(start, end, data);
42946
42947        // Attach the metadata to the value property of the cue to keep consistency between
42948        // the differences of WebKitDataCue in safari and VTTCue in other browsers
42949        cue.value = value;
42950
42951        this.segmentMetadataTrack_.addCue(cue);
42952      }
42953    }]);
42954    return SegmentLoader;
42955  }(videojs$1.EventTarget);
42956
42957  /**
42958   * @file vtt-segment-loader.js
42959   */
42960
42961  var VTT_LINE_TERMINATORS = new Uint8Array('\n\n'.split('').map(function (char) {
42962    return char.charCodeAt(0);
42963  }));
42964
42965  var uintToString = function uintToString(uintArray) {
42966    return String.fromCharCode.apply(null, uintArray);
42967  };
42968
42969  /**
42970   * An object that manages segment loading and appending.
42971   *
42972   * @class VTTSegmentLoader
42973   * @param {Object} options required and optional options
42974   * @extends videojs.EventTarget
42975   */
42976
42977  var VTTSegmentLoader = function (_SegmentLoader) {
42978    inherits$3(VTTSegmentLoader, _SegmentLoader);
42979
42980    function VTTSegmentLoader(settings) {
42981      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
42982      classCallCheck$3(this, VTTSegmentLoader);
42983
42984      // SegmentLoader requires a MediaSource be specified or it will throw an error;
42985      // however, VTTSegmentLoader has no need of a media source, so delete the reference
42986      var _this = possibleConstructorReturn$3(this, (VTTSegmentLoader.__proto__ || Object.getPrototypeOf(VTTSegmentLoader)).call(this, settings, options));
42987
42988      _this.mediaSource_ = null;
42989
42990      _this.subtitlesTrack_ = null;
42991      return _this;
42992    }
42993
42994    /**
42995     * Indicates which time ranges are buffered
42996     *
42997     * @return {TimeRange}
42998     *         TimeRange object representing the current buffered ranges
42999     */
43000
43001    createClass$2(VTTSegmentLoader, [{
43002      key: 'buffered_',
43003      value: function buffered_() {
43004        if (!this.subtitlesTrack_ || !this.subtitlesTrack_.cues.length) {
43005          return videojs$1.createTimeRanges();
43006        }
43007
43008        var cues = this.subtitlesTrack_.cues;
43009        var start = cues[0].startTime;
43010        var end = cues[cues.length - 1].startTime;
43011
43012        return videojs$1.createTimeRanges([[start, end]]);
43013      }
43014
43015      /**
43016       * Gets and sets init segment for the provided map
43017       *
43018       * @param {Object} map
43019       *        The map object representing the init segment to get or set
43020       * @param {Boolean=} set
43021       *        If true, the init segment for the provided map should be saved
43022       * @return {Object}
43023       *         map object for desired init segment
43024       */
43025
43026    }, {
43027      key: 'initSegment',
43028      value: function initSegment(map) {
43029        var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
43030
43031        if (!map) {
43032          return null;
43033        }
43034
43035        var id = initSegmentId(map);
43036        var storedMap = this.initSegments_[id];
43037
43038        if (set$$1 && !storedMap && map.bytes) {
43039          // append WebVTT line terminators to the media initialization segment if it exists
43040          // to follow the WebVTT spec (https://w3c.github.io/webvtt/#file-structure) that
43041          // requires two or more WebVTT line terminators between the WebVTT header and the
43042          // rest of the file
43043          var combinedByteLength = VTT_LINE_TERMINATORS.byteLength + map.bytes.byteLength;
43044          var combinedSegment = new Uint8Array(combinedByteLength);
43045
43046          combinedSegment.set(map.bytes);
43047          combinedSegment.set(VTT_LINE_TERMINATORS, map.bytes.byteLength);
43048
43049          this.initSegments_[id] = storedMap = {
43050            resolvedUri: map.resolvedUri,
43051            byterange: map.byterange,
43052            bytes: combinedSegment
43053          };
43054        }
43055
43056        return storedMap || map;
43057      }
43058
43059      /**
43060       * Returns true if all configuration required for loading is present, otherwise false.
43061       *
43062       * @return {Boolean} True if the all configuration is ready for loading
43063       * @private
43064       */
43065
43066    }, {
43067      key: 'couldBeginLoading_',
43068      value: function couldBeginLoading_() {
43069        return this.playlist_ && this.subtitlesTrack_ && !this.paused();
43070      }
43071
43072      /**
43073       * Once all the starting parameters have been specified, begin
43074       * operation. This method should only be invoked from the INIT
43075       * state.
43076       *
43077       * @private
43078       */
43079
43080    }, {
43081      key: 'init_',
43082      value: function init_() {
43083        this.state = 'READY';
43084        this.resetEverything();
43085        return this.monitorBuffer_();
43086      }
43087
43088      /**
43089       * Set a subtitle track on the segment loader to add subtitles to
43090       *
43091       * @param {TextTrack=} track
43092       *        The text track to add loaded subtitles to
43093       * @return {TextTrack}
43094       *        Returns the subtitles track
43095       */
43096
43097    }, {
43098      key: 'track',
43099      value: function track(_track) {
43100        if (typeof _track === 'undefined') {
43101          return this.subtitlesTrack_;
43102        }
43103
43104        this.subtitlesTrack_ = _track;
43105
43106        // if we were unpaused but waiting for a sourceUpdater, start
43107        // buffering now
43108        if (this.state === 'INIT' && this.couldBeginLoading_()) {
43109          this.init_();
43110        }
43111
43112        return this.subtitlesTrack_;
43113      }
43114
43115      /**
43116       * Remove any data in the source buffer between start and end times
43117       * @param {Number} start - the start time of the region to remove from the buffer
43118       * @param {Number} end - the end time of the region to remove from the buffer
43119       */
43120
43121    }, {
43122      key: 'remove',
43123      value: function remove(start, end) {
43124        removeCuesFromTrack(start, end, this.subtitlesTrack_);
43125      }
43126
43127      /**
43128       * fill the buffer with segements unless the sourceBuffers are
43129       * currently updating
43130       *
43131       * Note: this function should only ever be called by monitorBuffer_
43132       * and never directly
43133       *
43134       * @private
43135       */
43136
43137    }, {
43138      key: 'fillBuffer_',
43139      value: function fillBuffer_() {
43140        var _this2 = this;
43141
43142        if (!this.syncPoint_) {
43143          this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_());
43144        }
43145
43146        // see if we need to begin loading immediately
43147        var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_);
43148
43149        segmentInfo = this.skipEmptySegments_(segmentInfo);
43150
43151        if (!segmentInfo) {
43152          return;
43153        }
43154
43155        if (this.syncController_.timestampOffsetForTimeline(segmentInfo.timeline) === null) {
43156          // We don't have the timestamp offset that we need to sync subtitles.
43157          // Rerun on a timestamp offset or user interaction.
43158          var checkTimestampOffset = function checkTimestampOffset() {
43159            _this2.state = 'READY';
43160            if (!_this2.paused()) {
43161              // if not paused, queue a buffer check as soon as possible
43162              _this2.monitorBuffer_();
43163            }
43164          };
43165
43166          this.syncController_.one('timestampoffset', checkTimestampOffset);
43167          this.state = 'WAITING_ON_TIMELINE';
43168          return;
43169        }
43170
43171        this.loadSegment_(segmentInfo);
43172      }
43173
43174      /**
43175       * Prevents the segment loader from requesting segments we know contain no subtitles
43176       * by walking forward until we find the next segment that we don't know whether it is
43177       * empty or not.
43178       *
43179       * @param {Object} segmentInfo
43180       *        a segment info object that describes the current segment
43181       * @return {Object}
43182       *         a segment info object that describes the current segment
43183       */
43184
43185    }, {
43186      key: 'skipEmptySegments_',
43187      value: function skipEmptySegments_(segmentInfo) {
43188        while (segmentInfo && segmentInfo.segment.empty) {
43189          segmentInfo = this.generateSegmentInfo_(segmentInfo.playlist, segmentInfo.mediaIndex + 1, segmentInfo.startOfSegment + segmentInfo.duration, segmentInfo.isSyncRequest);
43190        }
43191        return segmentInfo;
43192      }
43193
43194      /**
43195       * append a decrypted segement to the SourceBuffer through a SourceUpdater
43196       *
43197       * @private
43198       */
43199
43200    }, {
43201      key: 'handleSegment_',
43202      value: function handleSegment_() {
43203        var _this3 = this;
43204
43205        if (!this.pendingSegment_ || !this.subtitlesTrack_) {
43206          this.state = 'READY';
43207          return;
43208        }
43209
43210        this.state = 'APPENDING';
43211
43212        var segmentInfo = this.pendingSegment_;
43213        var segment = segmentInfo.segment;
43214
43215        // Make sure that vttjs has loaded, otherwise, wait till it finished loading
43216        if (typeof window_1.WebVTT !== 'function' && this.subtitlesTrack_ && this.subtitlesTrack_.tech_) {
43217
43218          var loadHandler = function loadHandler() {
43219            _this3.handleSegment_();
43220          };
43221
43222          this.state = 'WAITING_ON_VTTJS';
43223          this.subtitlesTrack_.tech_.one('vttjsloaded', loadHandler);
43224          this.subtitlesTrack_.tech_.one('vttjserror', function () {
43225            _this3.subtitlesTrack_.tech_.off('vttjsloaded', loadHandler);
43226            _this3.error({
43227              message: 'Error loading vtt.js'
43228            });
43229            _this3.state = 'READY';
43230            _this3.pause();
43231            _this3.trigger('error');
43232          });
43233
43234          return;
43235        }
43236
43237        segment.requested = true;
43238
43239        try {
43240          this.parseVTTCues_(segmentInfo);
43241        } catch (e) {
43242          this.error({
43243            message: e.message
43244          });
43245          this.state = 'READY';
43246          this.pause();
43247          return this.trigger('error');
43248        }
43249
43250        this.updateTimeMapping_(segmentInfo, this.syncController_.timelines[segmentInfo.timeline], this.playlist_);
43251
43252        if (segmentInfo.isSyncRequest) {
43253          this.trigger('syncinfoupdate');
43254          this.pendingSegment_ = null;
43255          this.state = 'READY';
43256          return;
43257        }
43258
43259        segmentInfo.byteLength = segmentInfo.bytes.byteLength;
43260
43261        this.mediaSecondsLoaded += segment.duration;
43262
43263        if (segmentInfo.cues.length) {
43264          // remove any overlapping cues to prevent doubling
43265          this.remove(segmentInfo.cues[0].endTime, segmentInfo.cues[segmentInfo.cues.length - 1].endTime);
43266        }
43267
43268        segmentInfo.cues.forEach(function (cue) {
43269          _this3.subtitlesTrack_.addCue(cue);
43270        });
43271
43272        this.handleUpdateEnd_();
43273      }
43274
43275      /**
43276       * Uses the WebVTT parser to parse the segment response
43277       *
43278       * @param {Object} segmentInfo
43279       *        a segment info object that describes the current segment
43280       * @private
43281       */
43282
43283    }, {
43284      key: 'parseVTTCues_',
43285      value: function parseVTTCues_(segmentInfo) {
43286        var decoder = void 0;
43287        var decodeBytesToString = false;
43288
43289        if (typeof window_1.TextDecoder === 'function') {
43290          decoder = new window_1.TextDecoder('utf8');
43291        } else {
43292          decoder = window_1.WebVTT.StringDecoder();
43293          decodeBytesToString = true;
43294        }
43295
43296        var parser = new window_1.WebVTT.Parser(window_1, window_1.vttjs, decoder);
43297
43298        segmentInfo.cues = [];
43299        segmentInfo.timestampmap = { MPEGTS: 0, LOCAL: 0 };
43300
43301        parser.oncue = segmentInfo.cues.push.bind(segmentInfo.cues);
43302        parser.ontimestampmap = function (map) {
43303          return segmentInfo.timestampmap = map;
43304        };
43305        parser.onparsingerror = function (error) {
43306          videojs$1.log.warn('Error encountered when parsing cues: ' + error.message);
43307        };
43308
43309        if (segmentInfo.segment.map) {
43310          var mapData = segmentInfo.segment.map.bytes;
43311
43312          if (decodeBytesToString) {
43313            mapData = uintToString(mapData);
43314          }
43315
43316          parser.parse(mapData);
43317        }
43318
43319        var segmentData = segmentInfo.bytes;
43320
43321        if (decodeBytesToString) {
43322          segmentData = uintToString(segmentData);
43323        }
43324
43325        parser.parse(segmentData);
43326        parser.flush();
43327      }
43328
43329      /**
43330       * Updates the start and end times of any cues parsed by the WebVTT parser using
43331       * the information parsed from the X-TIMESTAMP-MAP header and a TS to media time mapping
43332       * from the SyncController
43333       *
43334       * @param {Object} segmentInfo
43335       *        a segment info object that describes the current segment
43336       * @param {Object} mappingObj
43337       *        object containing a mapping from TS to media time
43338       * @param {Object} playlist
43339       *        the playlist object containing the segment
43340       * @private
43341       */
43342
43343    }, {
43344      key: 'updateTimeMapping_',
43345      value: function updateTimeMapping_(segmentInfo, mappingObj, playlist) {
43346        var segment = segmentInfo.segment;
43347
43348        if (!mappingObj) {
43349          // If the sync controller does not have a mapping of TS to Media Time for the
43350          // timeline, then we don't have enough information to update the cue
43351          // start/end times
43352          return;
43353        }
43354
43355        if (!segmentInfo.cues.length) {
43356          // If there are no cues, we also do not have enough information to figure out
43357          // segment timing. Mark that the segment contains no cues so we don't re-request
43358          // an empty segment.
43359          segment.empty = true;
43360          return;
43361        }
43362
43363        var timestampmap = segmentInfo.timestampmap;
43364        var diff = timestampmap.MPEGTS / 90000 - timestampmap.LOCAL + mappingObj.mapping;
43365
43366        segmentInfo.cues.forEach(function (cue) {
43367          // First convert cue time to TS time using the timestamp-map provided within the vtt
43368          cue.startTime += diff;
43369          cue.endTime += diff;
43370        });
43371
43372        if (!playlist.syncInfo) {
43373          var firstStart = segmentInfo.cues[0].startTime;
43374          var lastStart = segmentInfo.cues[segmentInfo.cues.length - 1].startTime;
43375
43376          playlist.syncInfo = {
43377            mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex,
43378            time: Math.min(firstStart, lastStart - segment.duration)
43379          };
43380        }
43381      }
43382    }]);
43383    return VTTSegmentLoader;
43384  }(SegmentLoader);
43385
43386  /**
43387   * @file ad-cue-tags.js
43388   */
43389
43390  /**
43391   * Searches for an ad cue that overlaps with the given mediaTime
43392   */
43393  var findAdCue = function findAdCue(track, mediaTime) {
43394    var cues = track.cues;
43395
43396    for (var i = 0; i < cues.length; i++) {
43397      var cue = cues[i];
43398
43399      if (mediaTime >= cue.adStartTime && mediaTime <= cue.adEndTime) {
43400        return cue;
43401      }
43402    }
43403    return null;
43404  };
43405
43406  var updateAdCues = function updateAdCues(media, track) {
43407    var offset = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
43408
43409    if (!media.segments) {
43410      return;
43411    }
43412
43413    var mediaTime = offset;
43414    var cue = void 0;
43415
43416    for (var i = 0; i < media.segments.length; i++) {
43417      var segment = media.segments[i];
43418
43419      if (!cue) {
43420        // Since the cues will span for at least the segment duration, adding a fudge
43421        // factor of half segment duration will prevent duplicate cues from being
43422        // created when timing info is not exact (e.g. cue start time initialized
43423        // at 10.006677, but next call mediaTime is 10.003332 )
43424        cue = findAdCue(track, mediaTime + segment.duration / 2);
43425      }
43426
43427      if (cue) {
43428        if ('cueIn' in segment) {
43429          // Found a CUE-IN so end the cue
43430          cue.endTime = mediaTime;
43431          cue.adEndTime = mediaTime;
43432          mediaTime += segment.duration;
43433          cue = null;
43434          continue;
43435        }
43436
43437        if (mediaTime < cue.endTime) {
43438          // Already processed this mediaTime for this cue
43439          mediaTime += segment.duration;
43440          continue;
43441        }
43442
43443        // otherwise extend cue until a CUE-IN is found
43444        cue.endTime += segment.duration;
43445      } else {
43446        if ('cueOut' in segment) {
43447          cue = new window_1.VTTCue(mediaTime, mediaTime + segment.duration, segment.cueOut);
43448          cue.adStartTime = mediaTime;
43449          // Assumes tag format to be
43450          // #EXT-X-CUE-OUT:30
43451          cue.adEndTime = mediaTime + parseFloat(segment.cueOut);
43452          track.addCue(cue);
43453        }
43454
43455        if ('cueOutCont' in segment) {
43456          // Entered into the middle of an ad cue
43457          var adOffset = void 0;
43458          var adTotal = void 0;
43459
43460          // Assumes tag formate to be
43461          // #EXT-X-CUE-OUT-CONT:10/30
43462
43463          var _segment$cueOutCont$s = segment.cueOutCont.split('/').map(parseFloat);
43464
43465          var _segment$cueOutCont$s2 = slicedToArray$1(_segment$cueOutCont$s, 2);
43466
43467          adOffset = _segment$cueOutCont$s2[0];
43468          adTotal = _segment$cueOutCont$s2[1];
43469
43470          cue = new window_1.VTTCue(mediaTime, mediaTime + segment.duration, '');
43471          cue.adStartTime = mediaTime - adOffset;
43472          cue.adEndTime = cue.adStartTime + adTotal;
43473          track.addCue(cue);
43474        }
43475      }
43476      mediaTime += segment.duration;
43477    }
43478  };
43479
43480  /**
43481   * @file sync-controller.js
43482   */
43483
43484  var tsprobe = tsInspector.inspect;
43485
43486  var syncPointStrategies = [
43487  // Stategy "VOD": Handle the VOD-case where the sync-point is *always*
43488  //                the equivalence display-time 0 === segment-index 0
43489  {
43490    name: 'VOD',
43491    run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
43492      if (duration$$1 !== Infinity) {
43493        var syncPoint = {
43494          time: 0,
43495          segmentIndex: 0
43496        };
43497
43498        return syncPoint;
43499      }
43500      return null;
43501    }
43502  },
43503  // Stategy "ProgramDateTime": We have a program-date-time tag in this playlist
43504  {
43505    name: 'ProgramDateTime',
43506    run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
43507      if (!syncController.datetimeToDisplayTime) {
43508        return null;
43509      }
43510
43511      var segments = playlist.segments || [];
43512      var syncPoint = null;
43513      var lastDistance = null;
43514
43515      currentTime = currentTime || 0;
43516
43517      for (var i = 0; i < segments.length; i++) {
43518        var segment = segments[i];
43519
43520        if (segment.dateTimeObject) {
43521          var segmentTime = segment.dateTimeObject.getTime() / 1000;
43522          var segmentStart = segmentTime + syncController.datetimeToDisplayTime;
43523          var distance = Math.abs(currentTime - segmentStart);
43524
43525          // Once the distance begins to increase, we have passed
43526          // currentTime and can stop looking for better candidates
43527          if (lastDistance !== null && lastDistance < distance) {
43528            break;
43529          }
43530
43531          lastDistance = distance;
43532          syncPoint = {
43533            time: segmentStart,
43534            segmentIndex: i
43535          };
43536        }
43537      }
43538      return syncPoint;
43539    }
43540  },
43541  // Stategy "Segment": We have a known time mapping for a timeline and a
43542  //                    segment in the current timeline with timing data
43543  {
43544    name: 'Segment',
43545    run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
43546      var segments = playlist.segments || [];
43547      var syncPoint = null;
43548      var lastDistance = null;
43549
43550      currentTime = currentTime || 0;
43551
43552      for (var i = 0; i < segments.length; i++) {
43553        var segment = segments[i];
43554
43555        if (segment.timeline === currentTimeline && typeof segment.start !== 'undefined') {
43556          var distance = Math.abs(currentTime - segment.start);
43557
43558          // Once the distance begins to increase, we have passed
43559          // currentTime and can stop looking for better candidates
43560          if (lastDistance !== null && lastDistance < distance) {
43561            break;
43562          }
43563
43564          if (!syncPoint || lastDistance === null || lastDistance >= distance) {
43565            lastDistance = distance;
43566            syncPoint = {
43567              time: segment.start,
43568              segmentIndex: i
43569            };
43570          }
43571        }
43572      }
43573      return syncPoint;
43574    }
43575  },
43576  // Stategy "Discontinuity": We have a discontinuity with a known
43577  //                          display-time
43578  {
43579    name: 'Discontinuity',
43580    run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
43581      var syncPoint = null;
43582
43583      currentTime = currentTime || 0;
43584
43585      if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
43586        var lastDistance = null;
43587
43588        for (var i = 0; i < playlist.discontinuityStarts.length; i++) {
43589          var segmentIndex = playlist.discontinuityStarts[i];
43590          var discontinuity = playlist.discontinuitySequence + i + 1;
43591          var discontinuitySync = syncController.discontinuities[discontinuity];
43592
43593          if (discontinuitySync) {
43594            var distance = Math.abs(currentTime - discontinuitySync.time);
43595
43596            // Once the distance begins to increase, we have passed
43597            // currentTime and can stop looking for better candidates
43598            if (lastDistance !== null && lastDistance < distance) {
43599              break;
43600            }
43601
43602            if (!syncPoint || lastDistance === null || lastDistance >= distance) {
43603              lastDistance = distance;
43604              syncPoint = {
43605                time: discontinuitySync.time,
43606                segmentIndex: segmentIndex
43607              };
43608            }
43609          }
43610        }
43611      }
43612      return syncPoint;
43613    }
43614  },
43615  // Stategy "Playlist": We have a playlist with a known mapping of
43616  //                     segment index to display time
43617  {
43618    name: 'Playlist',
43619    run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) {
43620      if (playlist.syncInfo) {
43621        var syncPoint = {
43622          time: playlist.syncInfo.time,
43623          segmentIndex: playlist.syncInfo.mediaSequence - playlist.mediaSequence
43624        };
43625
43626        return syncPoint;
43627      }
43628      return null;
43629    }
43630  }];
43631
43632  var SyncController = function (_videojs$EventTarget) {
43633    inherits$3(SyncController, _videojs$EventTarget);
43634
43635    function SyncController() {
43636      classCallCheck$3(this, SyncController);
43637
43638      // Segment Loader state variables...
43639      // ...for synching across variants
43640      var _this = possibleConstructorReturn$3(this, (SyncController.__proto__ || Object.getPrototypeOf(SyncController)).call(this));
43641
43642      _this.inspectCache_ = undefined;
43643
43644      // ...for synching across variants
43645      _this.timelines = [];
43646      _this.discontinuities = [];
43647      _this.datetimeToDisplayTime = null;
43648
43649      _this.logger_ = logger('SyncController');
43650      return _this;
43651    }
43652
43653    /**
43654     * Find a sync-point for the playlist specified
43655     *
43656     * A sync-point is defined as a known mapping from display-time to
43657     * a segment-index in the current playlist.
43658     *
43659     * @param {Playlist} playlist
43660     *        The playlist that needs a sync-point
43661     * @param {Number} duration
43662     *        Duration of the MediaSource (Infinite if playing a live source)
43663     * @param {Number} currentTimeline
43664     *        The last timeline from which a segment was loaded
43665     * @returns {Object}
43666     *          A sync-point object
43667     */
43668
43669    createClass$2(SyncController, [{
43670      key: 'getSyncPoint',
43671      value: function getSyncPoint(playlist, duration$$1, currentTimeline, currentTime) {
43672        var syncPoints = this.runStrategies_(playlist, duration$$1, currentTimeline, currentTime);
43673
43674        if (!syncPoints.length) {
43675          // Signal that we need to attempt to get a sync-point manually
43676          // by fetching a segment in the playlist and constructing
43677          // a sync-point from that information
43678          return null;
43679        }
43680
43681        // Now find the sync-point that is closest to the currentTime because
43682        // that should result in the most accurate guess about which segment
43683        // to fetch
43684        return this.selectSyncPoint_(syncPoints, { key: 'time', value: currentTime });
43685      }
43686
43687      /**
43688       * Calculate the amount of time that has expired off the playlist during playback
43689       *
43690       * @param {Playlist} playlist
43691       *        Playlist object to calculate expired from
43692       * @param {Number} duration
43693       *        Duration of the MediaSource (Infinity if playling a live source)
43694       * @returns {Number|null}
43695       *          The amount of time that has expired off the playlist during playback. Null
43696       *          if no sync-points for the playlist can be found.
43697       */
43698
43699    }, {
43700      key: 'getExpiredTime',
43701      value: function getExpiredTime(playlist, duration$$1) {
43702        if (!playlist || !playlist.segments) {
43703          return null;
43704        }
43705
43706        var syncPoints = this.runStrategies_(playlist, duration$$1, playlist.discontinuitySequence, 0);
43707
43708        // Without sync-points, there is not enough information to determine the expired time
43709        if (!syncPoints.length) {
43710          return null;
43711        }
43712
43713        var syncPoint = this.selectSyncPoint_(syncPoints, {
43714          key: 'segmentIndex',
43715          value: 0
43716        });
43717
43718        // If the sync-point is beyond the start of the playlist, we want to subtract the
43719        // duration from index 0 to syncPoint.segmentIndex instead of adding.
43720        if (syncPoint.segmentIndex > 0) {
43721          syncPoint.time *= -1;
43722        }
43723
43724        return Math.abs(syncPoint.time + sumDurations(playlist, syncPoint.segmentIndex, 0));
43725      }
43726
43727      /**
43728       * Runs each sync-point strategy and returns a list of sync-points returned by the
43729       * strategies
43730       *
43731       * @private
43732       * @param {Playlist} playlist
43733       *        The playlist that needs a sync-point
43734       * @param {Number} duration
43735       *        Duration of the MediaSource (Infinity if playing a live source)
43736       * @param {Number} currentTimeline
43737       *        The last timeline from which a segment was loaded
43738       * @returns {Array}
43739       *          A list of sync-point objects
43740       */
43741
43742    }, {
43743      key: 'runStrategies_',
43744      value: function runStrategies_(playlist, duration$$1, currentTimeline, currentTime) {
43745        var syncPoints = [];
43746
43747        // Try to find a sync-point in by utilizing various strategies...
43748        for (var i = 0; i < syncPointStrategies.length; i++) {
43749          var strategy = syncPointStrategies[i];
43750          var syncPoint = strategy.run(this, playlist, duration$$1, currentTimeline, currentTime);
43751
43752          if (syncPoint) {
43753            syncPoint.strategy = strategy.name;
43754            syncPoints.push({
43755              strategy: strategy.name,
43756              syncPoint: syncPoint
43757            });
43758          }
43759        }
43760
43761        return syncPoints;
43762      }
43763
43764      /**
43765       * Selects the sync-point nearest the specified target
43766       *
43767       * @private
43768       * @param {Array} syncPoints
43769       *        List of sync-points to select from
43770       * @param {Object} target
43771       *        Object specifying the property and value we are targeting
43772       * @param {String} target.key
43773       *        Specifies the property to target. Must be either 'time' or 'segmentIndex'
43774       * @param {Number} target.value
43775       *        The value to target for the specified key.
43776       * @returns {Object}
43777       *          The sync-point nearest the target
43778       */
43779
43780    }, {
43781      key: 'selectSyncPoint_',
43782      value: function selectSyncPoint_(syncPoints, target) {
43783        var bestSyncPoint = syncPoints[0].syncPoint;
43784        var bestDistance = Math.abs(syncPoints[0].syncPoint[target.key] - target.value);
43785        var bestStrategy = syncPoints[0].strategy;
43786
43787        for (var i = 1; i < syncPoints.length; i++) {
43788          var newDistance = Math.abs(syncPoints[i].syncPoint[target.key] - target.value);
43789
43790          if (newDistance < bestDistance) {
43791            bestDistance = newDistance;
43792            bestSyncPoint = syncPoints[i].syncPoint;
43793            bestStrategy = syncPoints[i].strategy;
43794          }
43795        }
43796
43797        this.logger_('syncPoint for [' + target.key + ': ' + target.value + '] chosen with strategy' + (' [' + bestStrategy + ']: [time:' + bestSyncPoint.time + ',') + (' segmentIndex:' + bestSyncPoint.segmentIndex + ']'));
43798
43799        return bestSyncPoint;
43800      }
43801
43802      /**
43803       * Save any meta-data present on the segments when segments leave
43804       * the live window to the playlist to allow for synchronization at the
43805       * playlist level later.
43806       *
43807       * @param {Playlist} oldPlaylist - The previous active playlist
43808       * @param {Playlist} newPlaylist - The updated and most current playlist
43809       */
43810
43811    }, {
43812      key: 'saveExpiredSegmentInfo',
43813      value: function saveExpiredSegmentInfo(oldPlaylist, newPlaylist) {
43814        var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence;
43815
43816        // When a segment expires from the playlist and it has a start time
43817        // save that information as a possible sync-point reference in future
43818        for (var i = mediaSequenceDiff - 1; i >= 0; i--) {
43819          var lastRemovedSegment = oldPlaylist.segments[i];
43820
43821          if (lastRemovedSegment && typeof lastRemovedSegment.start !== 'undefined') {
43822            newPlaylist.syncInfo = {
43823              mediaSequence: oldPlaylist.mediaSequence + i,
43824              time: lastRemovedSegment.start
43825            };
43826            this.logger_('playlist refresh sync: [time:' + newPlaylist.syncInfo.time + ',' + (' mediaSequence: ' + newPlaylist.syncInfo.mediaSequence + ']'));
43827            this.trigger('syncinfoupdate');
43828            break;
43829          }
43830        }
43831      }
43832
43833      /**
43834       * Save the mapping from playlist's ProgramDateTime to display. This should
43835       * only ever happen once at the start of playback.
43836       *
43837       * @param {Playlist} playlist - The currently active playlist
43838       */
43839
43840    }, {
43841      key: 'setDateTimeMapping',
43842      value: function setDateTimeMapping(playlist) {
43843        if (!this.datetimeToDisplayTime && playlist.segments && playlist.segments.length && playlist.segments[0].dateTimeObject) {
43844          var playlistTimestamp = playlist.segments[0].dateTimeObject.getTime() / 1000;
43845
43846          this.datetimeToDisplayTime = -playlistTimestamp;
43847        }
43848      }
43849
43850      /**
43851       * Reset the state of the inspection cache when we do a rendition
43852       * switch
43853       */
43854
43855    }, {
43856      key: 'reset',
43857      value: function reset() {
43858        this.inspectCache_ = undefined;
43859      }
43860
43861      /**
43862       * Probe or inspect a fmp4 or an mpeg2-ts segment to determine the start
43863       * and end of the segment in it's internal "media time". Used to generate
43864       * mappings from that internal "media time" to the display time that is
43865       * shown on the player.
43866       *
43867       * @param {SegmentInfo} segmentInfo - The current active request information
43868       */
43869
43870    }, {
43871      key: 'probeSegmentInfo',
43872      value: function probeSegmentInfo(segmentInfo) {
43873        var segment = segmentInfo.segment;
43874        var playlist = segmentInfo.playlist;
43875        var timingInfo = void 0;
43876
43877        if (segment.map) {
43878          timingInfo = this.probeMp4Segment_(segmentInfo);
43879        } else {
43880          timingInfo = this.probeTsSegment_(segmentInfo);
43881        }
43882
43883        if (timingInfo) {
43884          if (this.calculateSegmentTimeMapping_(segmentInfo, timingInfo)) {
43885            this.saveDiscontinuitySyncInfo_(segmentInfo);
43886
43887            // If the playlist does not have sync information yet, record that information
43888            // now with segment timing information
43889            if (!playlist.syncInfo) {
43890              playlist.syncInfo = {
43891                mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex,
43892                time: segment.start
43893              };
43894            }
43895          }
43896        }
43897
43898        return timingInfo;
43899      }
43900
43901      /**
43902       * Probe an fmp4 or an mpeg2-ts segment to determine the start of the segment
43903       * in it's internal "media time".
43904       *
43905       * @private
43906       * @param {SegmentInfo} segmentInfo - The current active request information
43907       * @return {object} The start and end time of the current segment in "media time"
43908       */
43909
43910    }, {
43911      key: 'probeMp4Segment_',
43912      value: function probeMp4Segment_(segmentInfo) {
43913        var segment = segmentInfo.segment;
43914        var timescales = probe.timescale(segment.map.bytes);
43915        var startTime = probe.startTime(timescales, segmentInfo.bytes);
43916
43917        if (segmentInfo.timestampOffset !== null) {
43918          segmentInfo.timestampOffset -= startTime;
43919        }
43920
43921        return {
43922          start: startTime,
43923          end: startTime + segment.duration
43924        };
43925      }
43926
43927      /**
43928       * Probe an mpeg2-ts segment to determine the start and end of the segment
43929       * in it's internal "media time".
43930       *
43931       * @private
43932       * @param {SegmentInfo} segmentInfo - The current active request information
43933       * @return {object} The start and end time of the current segment in "media time"
43934       */
43935
43936    }, {
43937      key: 'probeTsSegment_',
43938      value: function probeTsSegment_(segmentInfo) {
43939        var timeInfo = tsprobe(segmentInfo.bytes, this.inspectCache_);
43940        var segmentStartTime = void 0;
43941        var segmentEndTime = void 0;
43942
43943        if (!timeInfo) {
43944          return null;
43945        }
43946
43947        if (timeInfo.video && timeInfo.video.length === 2) {
43948          this.inspectCache_ = timeInfo.video[1].dts;
43949          segmentStartTime = timeInfo.video[0].dtsTime;
43950          segmentEndTime = timeInfo.video[1].dtsTime;
43951        } else if (timeInfo.audio && timeInfo.audio.length === 2) {
43952          this.inspectCache_ = timeInfo.audio[1].dts;
43953          segmentStartTime = timeInfo.audio[0].dtsTime;
43954          segmentEndTime = timeInfo.audio[1].dtsTime;
43955        }
43956
43957        return {
43958          start: segmentStartTime,
43959          end: segmentEndTime,
43960          containsVideo: timeInfo.video && timeInfo.video.length === 2,
43961          containsAudio: timeInfo.audio && timeInfo.audio.length === 2
43962        };
43963      }
43964    }, {
43965      key: 'timestampOffsetForTimeline',
43966      value: function timestampOffsetForTimeline(timeline) {
43967        if (typeof this.timelines[timeline] === 'undefined') {
43968          return null;
43969        }
43970        return this.timelines[timeline].time;
43971      }
43972    }, {
43973      key: 'mappingForTimeline',
43974      value: function mappingForTimeline(timeline) {
43975        if (typeof this.timelines[timeline] === 'undefined') {
43976          return null;
43977        }
43978        return this.timelines[timeline].mapping;
43979      }
43980
43981      /**
43982       * Use the "media time" for a segment to generate a mapping to "display time" and
43983       * save that display time to the segment.
43984       *
43985       * @private
43986       * @param {SegmentInfo} segmentInfo
43987       *        The current active request information
43988       * @param {object} timingInfo
43989       *        The start and end time of the current segment in "media time"
43990       * @returns {Boolean}
43991       *          Returns false if segment time mapping could not be calculated
43992       */
43993
43994    }, {
43995      key: 'calculateSegmentTimeMapping_',
43996      value: function calculateSegmentTimeMapping_(segmentInfo, timingInfo) {
43997        var segment = segmentInfo.segment;
43998        var mappingObj = this.timelines[segmentInfo.timeline];
43999
44000        if (segmentInfo.timestampOffset !== null) {
44001          mappingObj = {
44002            time: segmentInfo.startOfSegment,
44003            mapping: segmentInfo.startOfSegment - timingInfo.start
44004          };
44005          this.timelines[segmentInfo.timeline] = mappingObj;
44006          this.trigger('timestampoffset');
44007
44008          this.logger_('time mapping for timeline ' + segmentInfo.timeline + ': ' + ('[time: ' + mappingObj.time + '] [mapping: ' + mappingObj.mapping + ']'));
44009
44010          segment.start = segmentInfo.startOfSegment;
44011          segment.end = timingInfo.end + mappingObj.mapping;
44012        } else if (mappingObj) {
44013          segment.start = timingInfo.start + mappingObj.mapping;
44014          segment.end = timingInfo.end + mappingObj.mapping;
44015        } else {
44016          return false;
44017        }
44018
44019        return true;
44020      }
44021
44022      /**
44023       * Each time we have discontinuity in the playlist, attempt to calculate the location
44024       * in display of the start of the discontinuity and save that. We also save an accuracy
44025       * value so that we save values with the most accuracy (closest to 0.)
44026       *
44027       * @private
44028       * @param {SegmentInfo} segmentInfo - The current active request information
44029       */
44030
44031    }, {
44032      key: 'saveDiscontinuitySyncInfo_',
44033      value: function saveDiscontinuitySyncInfo_(segmentInfo) {
44034        var playlist = segmentInfo.playlist;
44035        var segment = segmentInfo.segment;
44036
44037        // If the current segment is a discontinuity then we know exactly where
44038        // the start of the range and it's accuracy is 0 (greater accuracy values
44039        // mean more approximation)
44040        if (segment.discontinuity) {
44041          this.discontinuities[segment.timeline] = {
44042            time: segment.start,
44043            accuracy: 0
44044          };
44045        } else if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) {
44046          // Search for future discontinuities that we can provide better timing
44047          // information for and save that information for sync purposes
44048          for (var i = 0; i < playlist.discontinuityStarts.length; i++) {
44049            var segmentIndex = playlist.discontinuityStarts[i];
44050            var discontinuity = playlist.discontinuitySequence + i + 1;
44051            var mediaIndexDiff = segmentIndex - segmentInfo.mediaIndex;
44052            var accuracy = Math.abs(mediaIndexDiff);
44053
44054            if (!this.discontinuities[discontinuity] || this.discontinuities[discontinuity].accuracy > accuracy) {
44055              var time = void 0;
44056
44057              if (mediaIndexDiff < 0) {
44058                time = segment.start - sumDurations(playlist, segmentInfo.mediaIndex, segmentIndex);
44059              } else {
44060                time = segment.end + sumDurations(playlist, segmentInfo.mediaIndex + 1, segmentIndex);
44061              }
44062
44063              this.discontinuities[discontinuity] = {
44064                time: time,
44065                accuracy: accuracy
44066              };
44067            }
44068          }
44069        }
44070      }
44071    }]);
44072    return SyncController;
44073  }(videojs$1.EventTarget);
44074
44075  var Decrypter$1 = new shimWorker("./decrypter-worker.worker.js", function (window, document$$1) {
44076    var self = this;
44077    var decrypterWorker = function () {
44078
44079      var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
44080
44081      var win;
44082
44083      if (typeof window !== "undefined") {
44084        win = window;
44085      } else if (typeof commonjsGlobal !== "undefined") {
44086        win = commonjsGlobal;
44087      } else if (typeof self !== "undefined") {
44088        win = self;
44089      } else {
44090        win = {};
44091      }
44092
44093      var window_1$$1 = win;
44094
44095      /*
44096       * pkcs7.pad
44097       * https://github.com/brightcove/pkcs7
44098       *
44099       * Copyright (c) 2014 Brightcove
44100       * Licensed under the apache2 license.
44101       */
44102
44103      /**
44104       * Returns the subarray of a Uint8Array without PKCS#7 padding.
44105       * @param padded {Uint8Array} unencrypted bytes that have been padded
44106       * @return {Uint8Array} the unpadded bytes
44107       * @see http://tools.ietf.org/html/rfc5652
44108       */
44109      function unpad(padded) {
44110        return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]);
44111      }
44112
44113      var classCallCheck$$1 = function classCallCheck$$1(instance, Constructor) {
44114        if (!(instance instanceof Constructor)) {
44115          throw new TypeError("Cannot call a class as a function");
44116        }
44117      };
44118
44119      var createClass$$1 = function () {
44120        function defineProperties(target, props) {
44121          for (var i = 0; i < props.length; i++) {
44122            var descriptor = props[i];
44123            descriptor.enumerable = descriptor.enumerable || false;
44124            descriptor.configurable = true;
44125            if ("value" in descriptor) descriptor.writable = true;
44126            Object.defineProperty(target, descriptor.key, descriptor);
44127          }
44128        }
44129
44130        return function (Constructor, protoProps, staticProps) {
44131          if (protoProps) defineProperties(Constructor.prototype, protoProps);
44132          if (staticProps) defineProperties(Constructor, staticProps);
44133          return Constructor;
44134        };
44135      }();
44136
44137      var inherits$$1 = function inherits$$1(subClass, superClass) {
44138        if (typeof superClass !== "function" && superClass !== null) {
44139          throw new TypeError("Super expression must either be null or a function, not " + (typeof superClass === 'undefined' ? 'undefined' : _typeof(superClass)));
44140        }
44141
44142        subClass.prototype = Object.create(superClass && superClass.prototype, {
44143          constructor: {
44144            value: subClass,
44145            enumerable: false,
44146            writable: true,
44147            configurable: true
44148          }
44149        });
44150        if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
44151      };
44152
44153      var possibleConstructorReturn$$1 = function possibleConstructorReturn$$1(self, call) {
44154        if (!self) {
44155          throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
44156        }
44157
44158        return call && ((typeof call === 'undefined' ? 'undefined' : _typeof(call)) === "object" || typeof call === "function") ? call : self;
44159      };
44160
44161      /**
44162       * @file aes.js
44163       *
44164       * This file contains an adaptation of the AES decryption algorithm
44165       * from the Standford Javascript Cryptography Library. That work is
44166       * covered by the following copyright and permissions notice:
44167       *
44168       * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
44169       * All rights reserved.
44170       *
44171       * Redistribution and use in source and binary forms, with or without
44172       * modification, are permitted provided that the following conditions are
44173       * met:
44174       *
44175       * 1. Redistributions of source code must retain the above copyright
44176       *    notice, this list of conditions and the following disclaimer.
44177       *
44178       * 2. Redistributions in binary form must reproduce the above
44179       *    copyright notice, this list of conditions and the following
44180       *    disclaimer in the documentation and/or other materials provided
44181       *    with the distribution.
44182       *
44183       * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
44184       * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
44185       * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
44186       * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
44187       * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
44188       * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
44189       * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
44190       * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
44191       * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
44192       * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
44193       * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
44194       *
44195       * The views and conclusions contained in the software and documentation
44196       * are those of the authors and should not be interpreted as representing
44197       * official policies, either expressed or implied, of the authors.
44198       */
44199
44200      /**
44201       * Expand the S-box tables.
44202       *
44203       * @private
44204       */
44205      var precompute = function precompute() {
44206        var tables = [[[], [], [], [], []], [[], [], [], [], []]];
44207        var encTable = tables[0];
44208        var decTable = tables[1];
44209        var sbox = encTable[4];
44210        var sboxInv = decTable[4];
44211        var i = void 0;
44212        var x = void 0;
44213        var xInv = void 0;
44214        var d = [];
44215        var th = [];
44216        var x2 = void 0;
44217        var x4 = void 0;
44218        var x8 = void 0;
44219        var s = void 0;
44220        var tEnc = void 0;
44221        var tDec = void 0;
44222
44223        // Compute double and third tables
44224        for (i = 0; i < 256; i++) {
44225          th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i;
44226        }
44227
44228        for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
44229          // Compute sbox
44230          s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4;
44231          s = s >> 8 ^ s & 255 ^ 99;
44232          sbox[x] = s;
44233          sboxInv[s] = x;
44234
44235          // Compute MixColumns
44236          x8 = d[x4 = d[x2 = d[x]]];
44237          tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
44238          tEnc = d[s] * 0x101 ^ s * 0x1010100;
44239
44240          for (i = 0; i < 4; i++) {
44241            encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8;
44242            decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8;
44243          }
44244        }
44245
44246        // Compactify. Considerable speedup on Firefox.
44247        for (i = 0; i < 5; i++) {
44248          encTable[i] = encTable[i].slice(0);
44249          decTable[i] = decTable[i].slice(0);
44250        }
44251        return tables;
44252      };
44253      var aesTables = null;
44254
44255      /**
44256       * Schedule out an AES key for both encryption and decryption. This
44257       * is a low-level class. Use a cipher mode to do bulk encryption.
44258       *
44259       * @class AES
44260       * @param key {Array} The key as an array of 4, 6 or 8 words.
44261       */
44262
44263      var AES = function () {
44264        function AES(key) {
44265          classCallCheck$$1(this, AES);
44266
44267          /**
44268           * The expanded S-box and inverse S-box tables. These will be computed
44269           * on the client so that we don't have to send them down the wire.
44270           *
44271           * There are two tables, _tables[0] is for encryption and
44272           * _tables[1] is for decryption.
44273           *
44274           * The first 4 sub-tables are the expanded S-box with MixColumns. The
44275           * last (_tables[01][4]) is the S-box itself.
44276           *
44277           * @private
44278           */
44279          // if we have yet to precompute the S-box tables
44280          // do so now
44281          if (!aesTables) {
44282            aesTables = precompute();
44283          }
44284          // then make a copy of that object for use
44285          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()]];
44286          var i = void 0;
44287          var j = void 0;
44288          var tmp = void 0;
44289          var encKey = void 0;
44290          var decKey = void 0;
44291          var sbox = this._tables[0][4];
44292          var decTable = this._tables[1];
44293          var keyLen = key.length;
44294          var rcon = 1;
44295
44296          if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
44297            throw new Error('Invalid aes key size');
44298          }
44299
44300          encKey = key.slice(0);
44301          decKey = [];
44302          this._key = [encKey, decKey];
44303
44304          // schedule encryption keys
44305          for (i = keyLen; i < 4 * keyLen + 28; i++) {
44306            tmp = encKey[i - 1];
44307
44308            // apply sbox
44309            if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) {
44310              tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255];
44311
44312              // shift rows and add rcon
44313              if (i % keyLen === 0) {
44314                tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24;
44315                rcon = rcon << 1 ^ (rcon >> 7) * 283;
44316              }
44317            }
44318
44319            encKey[i] = encKey[i - keyLen] ^ tmp;
44320          }
44321
44322          // schedule decryption keys
44323          for (j = 0; i; j++, i--) {
44324            tmp = encKey[j & 3 ? i : i - 4];
44325            if (i <= 4 || j < 4) {
44326              decKey[j] = tmp;
44327            } else {
44328              decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]];
44329            }
44330          }
44331        }
44332
44333        /**
44334         * Decrypt 16 bytes, specified as four 32-bit words.
44335         *
44336         * @param {Number} encrypted0 the first word to decrypt
44337         * @param {Number} encrypted1 the second word to decrypt
44338         * @param {Number} encrypted2 the third word to decrypt
44339         * @param {Number} encrypted3 the fourth word to decrypt
44340         * @param {Int32Array} out the array to write the decrypted words
44341         * into
44342         * @param {Number} offset the offset into the output array to start
44343         * writing results
44344         * @return {Array} The plaintext.
44345         */
44346
44347        AES.prototype.decrypt = function decrypt$$1(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
44348          var key = this._key[1];
44349          // state variables a,b,c,d are loaded with pre-whitened data
44350          var a = encrypted0 ^ key[0];
44351          var b = encrypted3 ^ key[1];
44352          var c = encrypted2 ^ key[2];
44353          var d = encrypted1 ^ key[3];
44354          var a2 = void 0;
44355          var b2 = void 0;
44356          var c2 = void 0;
44357
44358          // key.length === 2 ?
44359          var nInnerRounds = key.length / 4 - 2;
44360          var i = void 0;
44361          var kIndex = 4;
44362          var table = this._tables[1];
44363
44364          // load up the tables
44365          var table0 = table[0];
44366          var table1 = table[1];
44367          var table2 = table[2];
44368          var table3 = table[3];
44369          var sbox = table[4];
44370
44371          // Inner rounds. Cribbed from OpenSSL.
44372          for (i = 0; i < nInnerRounds; i++) {
44373            a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex];
44374            b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1];
44375            c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2];
44376            d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3];
44377            kIndex += 4;
44378            a = a2;b = b2;c = c2;
44379          }
44380
44381          // Last round.
44382          for (i = 0; i < 4; i++) {
44383            out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++];
44384            a2 = a;a = b;b = c;c = d;d = a2;
44385          }
44386        };
44387
44388        return AES;
44389      }();
44390
44391      /**
44392       * @file stream.js
44393       */
44394      /**
44395       * A lightweight readable stream implemention that handles event dispatching.
44396       *
44397       * @class Stream
44398       */
44399      var Stream = function () {
44400        function Stream() {
44401          classCallCheck$$1(this, Stream);
44402
44403          this.listeners = {};
44404        }
44405
44406        /**
44407         * Add a listener for a specified event type.
44408         *
44409         * @param {String} type the event name
44410         * @param {Function} listener the callback to be invoked when an event of
44411         * the specified type occurs
44412         */
44413
44414        Stream.prototype.on = function on(type, listener) {
44415          if (!this.listeners[type]) {
44416            this.listeners[type] = [];
44417          }
44418          this.listeners[type].push(listener);
44419        };
44420
44421        /**
44422         * Remove a listener for a specified event type.
44423         *
44424         * @param {String} type the event name
44425         * @param {Function} listener  a function previously registered for this
44426         * type of event through `on`
44427         * @return {Boolean} if we could turn it off or not
44428         */
44429
44430        Stream.prototype.off = function off(type, listener) {
44431          if (!this.listeners[type]) {
44432            return false;
44433          }
44434
44435          var index = this.listeners[type].indexOf(listener);
44436
44437          this.listeners[type].splice(index, 1);
44438          return index > -1;
44439        };
44440
44441        /**
44442         * Trigger an event of the specified type on this stream. Any additional
44443         * arguments to this function are passed as parameters to event listeners.
44444         *
44445         * @param {String} type the event name
44446         */
44447
44448        Stream.prototype.trigger = function trigger(type) {
44449          var callbacks = this.listeners[type];
44450
44451          if (!callbacks) {
44452            return;
44453          }
44454
44455          // Slicing the arguments on every invocation of this method
44456          // can add a significant amount of overhead. Avoid the
44457          // intermediate object creation for the common case of a
44458          // single callback argument
44459          if (arguments.length === 2) {
44460            var length = callbacks.length;
44461
44462            for (var i = 0; i < length; ++i) {
44463              callbacks[i].call(this, arguments[1]);
44464            }
44465          } else {
44466            var args = Array.prototype.slice.call(arguments, 1);
44467            var _length = callbacks.length;
44468
44469            for (var _i = 0; _i < _length; ++_i) {
44470              callbacks[_i].apply(this, args);
44471            }
44472          }
44473        };
44474
44475        /**
44476         * Destroys the stream and cleans up.
44477         */
44478
44479        Stream.prototype.dispose = function dispose() {
44480          this.listeners = {};
44481        };
44482        /**
44483         * Forwards all `data` events on this stream to the destination stream. The
44484         * destination stream should provide a method `push` to receive the data
44485         * events as they arrive.
44486         *
44487         * @param {Stream} destination the stream that will receive all `data` events
44488         * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
44489         */
44490
44491        Stream.prototype.pipe = function pipe(destination) {
44492          this.on('data', function (data) {
44493            destination.push(data);
44494          });
44495        };
44496
44497        return Stream;
44498      }();
44499
44500      /**
44501       * @file async-stream.js
44502       */
44503      /**
44504       * A wrapper around the Stream class to use setTiemout
44505       * and run stream "jobs" Asynchronously
44506       *
44507       * @class AsyncStream
44508       * @extends Stream
44509       */
44510
44511      var AsyncStream$$1 = function (_Stream) {
44512        inherits$$1(AsyncStream$$1, _Stream);
44513
44514        function AsyncStream$$1() {
44515          classCallCheck$$1(this, AsyncStream$$1);
44516
44517          var _this = possibleConstructorReturn$$1(this, _Stream.call(this, Stream));
44518
44519          _this.jobs = [];
44520          _this.delay = 1;
44521          _this.timeout_ = null;
44522          return _this;
44523        }
44524
44525        /**
44526         * process an async job
44527         *
44528         * @private
44529         */
44530
44531        AsyncStream$$1.prototype.processJob_ = function processJob_() {
44532          this.jobs.shift()();
44533          if (this.jobs.length) {
44534            this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
44535          } else {
44536            this.timeout_ = null;
44537          }
44538        };
44539
44540        /**
44541         * push a job into the stream
44542         *
44543         * @param {Function} job the job to push into the stream
44544         */
44545
44546        AsyncStream$$1.prototype.push = function push(job) {
44547          this.jobs.push(job);
44548          if (!this.timeout_) {
44549            this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
44550          }
44551        };
44552
44553        return AsyncStream$$1;
44554      }(Stream);
44555
44556      /**
44557       * @file decrypter.js
44558       *
44559       * An asynchronous implementation of AES-128 CBC decryption with
44560       * PKCS#7 padding.
44561       */
44562
44563      /**
44564       * Convert network-order (big-endian) bytes into their little-endian
44565       * representation.
44566       */
44567      var ntoh = function ntoh(word) {
44568        return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24;
44569      };
44570
44571      /**
44572       * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
44573       *
44574       * @param {Uint8Array} encrypted the encrypted bytes
44575       * @param {Uint32Array} key the bytes of the decryption key
44576       * @param {Uint32Array} initVector the initialization vector (IV) to
44577       * use for the first round of CBC.
44578       * @return {Uint8Array} the decrypted bytes
44579       *
44580       * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
44581       * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
44582       * @see https://tools.ietf.org/html/rfc2315
44583       */
44584      var decrypt$$1 = function decrypt$$1(encrypted, key, initVector) {
44585        // word-level access to the encrypted bytes
44586        var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2);
44587
44588        var decipher = new AES(Array.prototype.slice.call(key));
44589
44590        // byte and word-level access for the decrypted output
44591        var decrypted = new Uint8Array(encrypted.byteLength);
44592        var decrypted32 = new Int32Array(decrypted.buffer);
44593
44594        // temporary variables for working with the IV, encrypted, and
44595        // decrypted data
44596        var init0 = void 0;
44597        var init1 = void 0;
44598        var init2 = void 0;
44599        var init3 = void 0;
44600        var encrypted0 = void 0;
44601        var encrypted1 = void 0;
44602        var encrypted2 = void 0;
44603        var encrypted3 = void 0;
44604
44605        // iteration variable
44606        var wordIx = void 0;
44607
44608        // pull out the words of the IV to ensure we don't modify the
44609        // passed-in reference and easier access
44610        init0 = initVector[0];
44611        init1 = initVector[1];
44612        init2 = initVector[2];
44613        init3 = initVector[3];
44614
44615        // decrypt four word sequences, applying cipher-block chaining (CBC)
44616        // to each decrypted block
44617        for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
44618          // convert big-endian (network order) words into little-endian
44619          // (javascript order)
44620          encrypted0 = ntoh(encrypted32[wordIx]);
44621          encrypted1 = ntoh(encrypted32[wordIx + 1]);
44622          encrypted2 = ntoh(encrypted32[wordIx + 2]);
44623          encrypted3 = ntoh(encrypted32[wordIx + 3]);
44624
44625          // decrypt the block
44626          decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx);
44627
44628          // XOR with the IV, and restore network byte-order to obtain the
44629          // plaintext
44630          decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
44631          decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
44632          decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
44633          decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3);
44634
44635          // setup the IV for the next round
44636          init0 = encrypted0;
44637          init1 = encrypted1;
44638          init2 = encrypted2;
44639          init3 = encrypted3;
44640        }
44641
44642        return decrypted;
44643      };
44644
44645      /**
44646       * The `Decrypter` class that manages decryption of AES
44647       * data through `AsyncStream` objects and the `decrypt`
44648       * function
44649       *
44650       * @param {Uint8Array} encrypted the encrypted bytes
44651       * @param {Uint32Array} key the bytes of the decryption key
44652       * @param {Uint32Array} initVector the initialization vector (IV) to
44653       * @param {Function} done the function to run when done
44654       * @class Decrypter
44655       */
44656
44657      var Decrypter$$1 = function () {
44658        function Decrypter$$1(encrypted, key, initVector, done) {
44659          classCallCheck$$1(this, Decrypter$$1);
44660
44661          var step = Decrypter$$1.STEP;
44662          var encrypted32 = new Int32Array(encrypted.buffer);
44663          var decrypted = new Uint8Array(encrypted.byteLength);
44664          var i = 0;
44665
44666          this.asyncStream_ = new AsyncStream$$1();
44667
44668          // split up the encryption job and do the individual chunks asynchronously
44669          this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
44670          for (i = step; i < encrypted32.length; i += step) {
44671            initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]);
44672            this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
44673          }
44674          // invoke the done() callback when everything is finished
44675          this.asyncStream_.push(function () {
44676            // remove pkcs#7 padding from the decrypted bytes
44677            done(null, unpad(decrypted));
44678          });
44679        }
44680
44681        /**
44682         * a getter for step the maximum number of bytes to process at one time
44683         *
44684         * @return {Number} the value of step 32000
44685         */
44686
44687        /**
44688         * @private
44689         */
44690        Decrypter$$1.prototype.decryptChunk_ = function decryptChunk_(encrypted, key, initVector, decrypted) {
44691          return function () {
44692            var bytes = decrypt$$1(encrypted, key, initVector);
44693
44694            decrypted.set(bytes, encrypted.byteOffset);
44695          };
44696        };
44697
44698        createClass$$1(Decrypter$$1, null, [{
44699          key: 'STEP',
44700          get: function get$$1() {
44701            // 4 * 8000;
44702            return 32000;
44703          }
44704        }]);
44705        return Decrypter$$1;
44706      }();
44707
44708      /**
44709       * @file bin-utils.js
44710       */
44711
44712      /**
44713       * Creates an object for sending to a web worker modifying properties that are TypedArrays
44714       * into a new object with seperated properties for the buffer, byteOffset, and byteLength.
44715       *
44716       * @param {Object} message
44717       *        Object of properties and values to send to the web worker
44718       * @return {Object}
44719       *         Modified message with TypedArray values expanded
44720       * @function createTransferableMessage
44721       */
44722      var createTransferableMessage = function createTransferableMessage(message) {
44723        var transferable = {};
44724
44725        Object.keys(message).forEach(function (key) {
44726          var value = message[key];
44727
44728          if (ArrayBuffer.isView(value)) {
44729            transferable[key] = {
44730              bytes: value.buffer,
44731              byteOffset: value.byteOffset,
44732              byteLength: value.byteLength
44733            };
44734          } else {
44735            transferable[key] = value;
44736          }
44737        });
44738
44739        return transferable;
44740      };
44741
44742      /**
44743       * Our web worker interface so that things can talk to aes-decrypter
44744       * that will be running in a web worker. the scope is passed to this by
44745       * webworkify.
44746       *
44747       * @param {Object} self
44748       *        the scope for the web worker
44749       */
44750      var DecrypterWorker = function DecrypterWorker(self) {
44751        self.onmessage = function (event) {
44752          var data = event.data;
44753          var encrypted = new Uint8Array(data.encrypted.bytes, data.encrypted.byteOffset, data.encrypted.byteLength);
44754          var key = new Uint32Array(data.key.bytes, data.key.byteOffset, data.key.byteLength / 4);
44755          var iv = new Uint32Array(data.iv.bytes, data.iv.byteOffset, data.iv.byteLength / 4);
44756
44757          /* eslint-disable no-new, handle-callback-err */
44758          new Decrypter$$1(encrypted, key, iv, function (err, bytes) {
44759            window_1$$1.postMessage(createTransferableMessage({
44760              source: data.source,
44761              decrypted: bytes
44762            }), [bytes.buffer]);
44763          });
44764          /* eslint-enable */
44765        };
44766      };
44767
44768      var decrypterWorker = new DecrypterWorker(self);
44769
44770      return decrypterWorker;
44771    }();
44772  });
44773
44774  /**
44775   * Convert the properties of an HLS track into an audioTrackKind.
44776   *
44777   * @private
44778   */
44779  var audioTrackKind_ = function audioTrackKind_(properties) {
44780    var kind = properties.default ? 'main' : 'alternative';
44781
44782    if (properties.characteristics && properties.characteristics.indexOf('public.accessibility.describes-video') >= 0) {
44783      kind = 'main-desc';
44784    }
44785
44786    return kind;
44787  };
44788
44789  /**
44790   * Pause provided segment loader and playlist loader if active
44791   *
44792   * @param {SegmentLoader} segmentLoader
44793   *        SegmentLoader to pause
44794   * @param {Object} mediaType
44795   *        Active media type
44796   * @function stopLoaders
44797   */
44798  var stopLoaders = function stopLoaders(segmentLoader, mediaType) {
44799    segmentLoader.abort();
44800    segmentLoader.pause();
44801
44802    if (mediaType && mediaType.activePlaylistLoader) {
44803      mediaType.activePlaylistLoader.pause();
44804      mediaType.activePlaylistLoader = null;
44805    }
44806  };
44807
44808  /**
44809   * Start loading provided segment loader and playlist loader
44810   *
44811   * @param {PlaylistLoader} playlistLoader
44812   *        PlaylistLoader to start loading
44813   * @param {Object} mediaType
44814   *        Active media type
44815   * @function startLoaders
44816   */
44817  var startLoaders = function startLoaders(playlistLoader, mediaType) {
44818    // Segment loader will be started after `loadedmetadata` or `loadedplaylist` from the
44819    // playlist loader
44820    mediaType.activePlaylistLoader = playlistLoader;
44821    playlistLoader.load();
44822  };
44823
44824  /**
44825   * Returns a function to be called when the media group changes. It performs a
44826   * non-destructive (preserve the buffer) resync of the SegmentLoader. This is because a
44827   * change of group is merely a rendition switch of the same content at another encoding,
44828   * rather than a change of content, such as switching audio from English to Spanish.
44829   *
44830   * @param {String} type
44831   *        MediaGroup type
44832   * @param {Object} settings
44833   *        Object containing required information for media groups
44834   * @return {Function}
44835   *         Handler for a non-destructive resync of SegmentLoader when the active media
44836   *         group changes.
44837   * @function onGroupChanged
44838   */
44839  var onGroupChanged = function onGroupChanged(type, settings) {
44840    return function () {
44841      var _settings$segmentLoad = settings.segmentLoaders,
44842          segmentLoader = _settings$segmentLoad[type],
44843          mainSegmentLoader = _settings$segmentLoad.main,
44844          mediaType = settings.mediaTypes[type];
44845
44846      var activeTrack = mediaType.activeTrack();
44847      var activeGroup = mediaType.activeGroup(activeTrack);
44848      var previousActiveLoader = mediaType.activePlaylistLoader;
44849
44850      stopLoaders(segmentLoader, mediaType);
44851
44852      if (!activeGroup) {
44853        // there is no group active
44854        return;
44855      }
44856
44857      if (!activeGroup.playlistLoader) {
44858        if (previousActiveLoader) {
44859          // The previous group had a playlist loader but the new active group does not
44860          // this means we are switching from demuxed to muxed audio. In this case we want to
44861          // do a destructive reset of the main segment loader and not restart the audio
44862          // loaders.
44863          mainSegmentLoader.resetEverything();
44864        }
44865        return;
44866      }
44867
44868      // Non-destructive resync
44869      segmentLoader.resyncLoader();
44870
44871      startLoaders(activeGroup.playlistLoader, mediaType);
44872    };
44873  };
44874
44875  /**
44876   * Returns a function to be called when the media track changes. It performs a
44877   * destructive reset of the SegmentLoader to ensure we start loading as close to
44878   * currentTime as possible.
44879   *
44880   * @param {String} type
44881   *        MediaGroup type
44882   * @param {Object} settings
44883   *        Object containing required information for media groups
44884   * @return {Function}
44885   *         Handler for a destructive reset of SegmentLoader when the active media
44886   *         track changes.
44887   * @function onTrackChanged
44888   */
44889  var onTrackChanged = function onTrackChanged(type, settings) {
44890    return function () {
44891      var _settings$segmentLoad2 = settings.segmentLoaders,
44892          segmentLoader = _settings$segmentLoad2[type],
44893          mainSegmentLoader = _settings$segmentLoad2.main,
44894          mediaType = settings.mediaTypes[type];
44895
44896      var activeTrack = mediaType.activeTrack();
44897      var activeGroup = mediaType.activeGroup(activeTrack);
44898      var previousActiveLoader = mediaType.activePlaylistLoader;
44899
44900      stopLoaders(segmentLoader, mediaType);
44901
44902      if (!activeGroup) {
44903        // there is no group active so we do not want to restart loaders
44904        return;
44905      }
44906
44907      if (!activeGroup.playlistLoader) {
44908        // when switching from demuxed audio/video to muxed audio/video (noted by no playlist
44909        // loader for the audio group), we want to do a destructive reset of the main segment
44910        // loader and not restart the audio loaders
44911        mainSegmentLoader.resetEverything();
44912        return;
44913      }
44914
44915      if (previousActiveLoader === activeGroup.playlistLoader) {
44916        // Nothing has actually changed. This can happen because track change events can fire
44917        // multiple times for a "single" change. One for enabling the new active track, and
44918        // one for disabling the track that was active
44919        startLoaders(activeGroup.playlistLoader, mediaType);
44920        return;
44921      }
44922
44923      if (segmentLoader.track) {
44924        // For WebVTT, set the new text track in the segmentloader
44925        segmentLoader.track(activeTrack);
44926      }
44927
44928      // destructive reset
44929      segmentLoader.resetEverything();
44930
44931      startLoaders(activeGroup.playlistLoader, mediaType);
44932    };
44933  };
44934
44935  var onError = {
44936    /**
44937     * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
44938     * an error.
44939     *
44940     * @param {String} type
44941     *        MediaGroup type
44942     * @param {Object} settings
44943     *        Object containing required information for media groups
44944     * @return {Function}
44945     *         Error handler. Logs warning (or error if the playlist is blacklisted) to
44946     *         console and switches back to default audio track.
44947     * @function onError.AUDIO
44948     */
44949    AUDIO: function AUDIO(type, settings) {
44950      return function () {
44951        var segmentLoader = settings.segmentLoaders[type],
44952            mediaType = settings.mediaTypes[type],
44953            blacklistCurrentPlaylist = settings.blacklistCurrentPlaylist;
44954
44955        stopLoaders(segmentLoader, mediaType);
44956
44957        // switch back to default audio track
44958        var activeTrack = mediaType.activeTrack();
44959        var activeGroup = mediaType.activeGroup();
44960        var id = (activeGroup.filter(function (group) {
44961          return group.default;
44962        })[0] || activeGroup[0]).id;
44963        var defaultTrack = mediaType.tracks[id];
44964
44965        if (activeTrack === defaultTrack) {
44966          // Default track encountered an error. All we can do now is blacklist the current
44967          // rendition and hope another will switch audio groups
44968          blacklistCurrentPlaylist({
44969            message: 'Problem encountered loading the default audio track.'
44970          });
44971          return;
44972        }
44973
44974        videojs$1.log.warn('Problem encountered loading the alternate audio track.' + 'Switching back to default.');
44975
44976        for (var trackId in mediaType.tracks) {
44977          mediaType.tracks[trackId].enabled = mediaType.tracks[trackId] === defaultTrack;
44978        }
44979
44980        mediaType.onTrackChanged();
44981      };
44982    },
44983    /**
44984     * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
44985     * an error.
44986     *
44987     * @param {String} type
44988     *        MediaGroup type
44989     * @param {Object} settings
44990     *        Object containing required information for media groups
44991     * @return {Function}
44992     *         Error handler. Logs warning to console and disables the active subtitle track
44993     * @function onError.SUBTITLES
44994     */
44995    SUBTITLES: function SUBTITLES(type, settings) {
44996      return function () {
44997        var segmentLoader = settings.segmentLoaders[type],
44998            mediaType = settings.mediaTypes[type];
44999
45000        videojs$1.log.warn('Problem encountered loading the subtitle track.' + 'Disabling subtitle track.');
45001
45002        stopLoaders(segmentLoader, mediaType);
45003
45004        var track = mediaType.activeTrack();
45005
45006        if (track) {
45007          track.mode = 'disabled';
45008        }
45009
45010        mediaType.onTrackChanged();
45011      };
45012    }
45013  };
45014
45015  var setupListeners = {
45016    /**
45017     * Setup event listeners for audio playlist loader
45018     *
45019     * @param {String} type
45020     *        MediaGroup type
45021     * @param {PlaylistLoader|null} playlistLoader
45022     *        PlaylistLoader to register listeners on
45023     * @param {Object} settings
45024     *        Object containing required information for media groups
45025     * @function setupListeners.AUDIO
45026     */
45027    AUDIO: function AUDIO(type, playlistLoader, settings) {
45028      if (!playlistLoader) {
45029        // no playlist loader means audio will be muxed with the video
45030        return;
45031      }
45032
45033      var tech = settings.tech,
45034          requestOptions = settings.requestOptions,
45035          segmentLoader = settings.segmentLoaders[type];
45036
45037      playlistLoader.on('loadedmetadata', function () {
45038        var media = playlistLoader.media();
45039
45040        segmentLoader.playlist(media, requestOptions);
45041
45042        // if the video is already playing, or if this isn't a live video and preload
45043        // permits, start downloading segments
45044        if (!tech.paused() || media.endList && tech.preload() !== 'none') {
45045          segmentLoader.load();
45046        }
45047      });
45048
45049      playlistLoader.on('loadedplaylist', function () {
45050        segmentLoader.playlist(playlistLoader.media(), requestOptions);
45051
45052        // If the player isn't paused, ensure that the segment loader is running
45053        if (!tech.paused()) {
45054          segmentLoader.load();
45055        }
45056      });
45057
45058      playlistLoader.on('error', onError[type](type, settings));
45059    },
45060    /**
45061     * Setup event listeners for subtitle playlist loader
45062     *
45063     * @param {String} type
45064     *        MediaGroup type
45065     * @param {PlaylistLoader|null} playlistLoader
45066     *        PlaylistLoader to register listeners on
45067     * @param {Object} settings
45068     *        Object containing required information for media groups
45069     * @function setupListeners.SUBTITLES
45070     */
45071    SUBTITLES: function SUBTITLES(type, playlistLoader, settings) {
45072      var tech = settings.tech,
45073          requestOptions = settings.requestOptions,
45074          segmentLoader = settings.segmentLoaders[type],
45075          mediaType = settings.mediaTypes[type];
45076
45077      playlistLoader.on('loadedmetadata', function () {
45078        var media = playlistLoader.media();
45079
45080        segmentLoader.playlist(media, requestOptions);
45081        segmentLoader.track(mediaType.activeTrack());
45082
45083        // if the video is already playing, or if this isn't a live video and preload
45084        // permits, start downloading segments
45085        if (!tech.paused() || media.endList && tech.preload() !== 'none') {
45086          segmentLoader.load();
45087        }
45088      });
45089
45090      playlistLoader.on('loadedplaylist', function () {
45091        segmentLoader.playlist(playlistLoader.media(), requestOptions);
45092
45093        // If the player isn't paused, ensure that the segment loader is running
45094        if (!tech.paused()) {
45095          segmentLoader.load();
45096        }
45097      });
45098
45099      playlistLoader.on('error', onError[type](type, settings));
45100    }
45101  };
45102
45103  var byGroupId = function byGroupId(type, groupId) {
45104    return function (playlist) {
45105      return playlist.attributes[type] === groupId;
45106    };
45107  };
45108
45109  var byResolvedUri = function byResolvedUri(resolvedUri) {
45110    return function (playlist) {
45111      return playlist.resolvedUri === resolvedUri;
45112    };
45113  };
45114
45115  var initialize = {
45116    /**
45117     * Setup PlaylistLoaders and AudioTracks for the audio groups
45118     *
45119     * @param {String} type
45120     *        MediaGroup type
45121     * @param {Object} settings
45122     *        Object containing required information for media groups
45123     * @function initialize.AUDIO
45124     */
45125    'AUDIO': function AUDIO(type, settings) {
45126      var hls = settings.hls,
45127          sourceType = settings.sourceType,
45128          segmentLoader = settings.segmentLoaders[type],
45129          withCredentials = settings.requestOptions.withCredentials,
45130          _settings$master = settings.master,
45131          mediaGroups = _settings$master.mediaGroups,
45132          playlists = _settings$master.playlists,
45133          _settings$mediaTypes$ = settings.mediaTypes[type],
45134          groups = _settings$mediaTypes$.groups,
45135          tracks = _settings$mediaTypes$.tracks,
45136          masterPlaylistLoader = settings.masterPlaylistLoader;
45137
45138      // force a default if we have none
45139
45140      if (!mediaGroups[type] || Object.keys(mediaGroups[type]).length === 0) {
45141        mediaGroups[type] = { main: { default: { default: true } } };
45142      }
45143
45144      for (var groupId in mediaGroups[type]) {
45145        if (!groups[groupId]) {
45146          groups[groupId] = [];
45147        }
45148
45149        // List of playlists that have an AUDIO attribute value matching the current
45150        // group ID
45151        var groupPlaylists = playlists.filter(byGroupId(type, groupId));
45152
45153        for (var variantLabel in mediaGroups[type][groupId]) {
45154          var properties = mediaGroups[type][groupId][variantLabel];
45155
45156          // List of playlists for the current group ID that have a matching uri with
45157          // this alternate audio variant
45158          var matchingPlaylists = groupPlaylists.filter(byResolvedUri(properties.resolvedUri));
45159
45160          if (matchingPlaylists.length) {
45161            // If there is a playlist that has the same uri as this audio variant, assume
45162            // that the playlist is audio only. We delete the resolvedUri property here
45163            // to prevent a playlist loader from being created so that we don't have
45164            // both the main and audio segment loaders loading the same audio segments
45165            // from the same playlist.
45166            delete properties.resolvedUri;
45167          }
45168
45169          var playlistLoader = void 0;
45170
45171          if (properties.resolvedUri) {
45172            playlistLoader = new PlaylistLoader(properties.resolvedUri, hls, withCredentials);
45173          } else if (properties.playlists && sourceType === 'dash') {
45174            playlistLoader = new DashPlaylistLoader(properties.playlists[0], hls, withCredentials, masterPlaylistLoader);
45175          } else {
45176            // no resolvedUri means the audio is muxed with the video when using this
45177            // audio track
45178            playlistLoader = null;
45179          }
45180
45181          properties = videojs$1.mergeOptions({ id: variantLabel, playlistLoader: playlistLoader }, properties);
45182
45183          setupListeners[type](type, properties.playlistLoader, settings);
45184
45185          groups[groupId].push(properties);
45186
45187          if (typeof tracks[variantLabel] === 'undefined') {
45188            var track = new videojs$1.AudioTrack({
45189              id: variantLabel,
45190              kind: audioTrackKind_(properties),
45191              enabled: false,
45192              language: properties.language,
45193              default: properties.default,
45194              label: variantLabel
45195            });
45196
45197            tracks[variantLabel] = track;
45198          }
45199        }
45200      }
45201
45202      // setup single error event handler for the segment loader
45203      segmentLoader.on('error', onError[type](type, settings));
45204    },
45205    /**
45206     * Setup PlaylistLoaders and TextTracks for the subtitle groups
45207     *
45208     * @param {String} type
45209     *        MediaGroup type
45210     * @param {Object} settings
45211     *        Object containing required information for media groups
45212     * @function initialize.SUBTITLES
45213     */
45214    'SUBTITLES': function SUBTITLES(type, settings) {
45215      var tech = settings.tech,
45216          hls = settings.hls,
45217          sourceType = settings.sourceType,
45218          segmentLoader = settings.segmentLoaders[type],
45219          withCredentials = settings.requestOptions.withCredentials,
45220          mediaGroups = settings.master.mediaGroups,
45221          _settings$mediaTypes$2 = settings.mediaTypes[type],
45222          groups = _settings$mediaTypes$2.groups,
45223          tracks = _settings$mediaTypes$2.tracks,
45224          masterPlaylistLoader = settings.masterPlaylistLoader;
45225
45226      for (var groupId in mediaGroups[type]) {
45227        if (!groups[groupId]) {
45228          groups[groupId] = [];
45229        }
45230
45231        for (var variantLabel in mediaGroups[type][groupId]) {
45232          if (mediaGroups[type][groupId][variantLabel].forced) {
45233            // Subtitle playlists with the forced attribute are not selectable in Safari.
45234            // According to Apple's HLS Authoring Specification:
45235            //   If content has forced subtitles and regular subtitles in a given language,
45236            //   the regular subtitles track in that language MUST contain both the forced
45237            //   subtitles and the regular subtitles for that language.
45238            // Because of this requirement and that Safari does not add forced subtitles,
45239            // forced subtitles are skipped here to maintain consistent experience across
45240            // all platforms
45241            continue;
45242          }
45243
45244          var properties = mediaGroups[type][groupId][variantLabel];
45245
45246          var playlistLoader = void 0;
45247
45248          if (sourceType === 'hls') {
45249            playlistLoader = new PlaylistLoader(properties.resolvedUri, hls, withCredentials);
45250          } else if (sourceType === 'dash') {
45251            playlistLoader = new DashPlaylistLoader(properties.playlists[0], hls, withCredentials, masterPlaylistLoader);
45252          }
45253
45254          properties = videojs$1.mergeOptions({
45255            id: variantLabel,
45256            playlistLoader: playlistLoader
45257          }, properties);
45258
45259          setupListeners[type](type, properties.playlistLoader, settings);
45260
45261          groups[groupId].push(properties);
45262
45263          if (typeof tracks[variantLabel] === 'undefined') {
45264            var track = tech.addRemoteTextTrack({
45265              id: variantLabel,
45266              kind: 'subtitles',
45267              enabled: false,
45268              language: properties.language,
45269              label: variantLabel
45270            }, false).track;
45271
45272            tracks[variantLabel] = track;
45273          }
45274        }
45275      }
45276
45277      // setup single error event handler for the segment loader
45278      segmentLoader.on('error', onError[type](type, settings));
45279    },
45280    /**
45281     * Setup TextTracks for the closed-caption groups
45282     *
45283     * @param {String} type
45284     *        MediaGroup type
45285     * @param {Object} settings
45286     *        Object containing required information for media groups
45287     * @function initialize['CLOSED-CAPTIONS']
45288     */
45289    'CLOSED-CAPTIONS': function CLOSEDCAPTIONS(type, settings) {
45290      var tech = settings.tech,
45291          mediaGroups = settings.master.mediaGroups,
45292          _settings$mediaTypes$3 = settings.mediaTypes[type],
45293          groups = _settings$mediaTypes$3.groups,
45294          tracks = _settings$mediaTypes$3.tracks;
45295
45296      for (var groupId in mediaGroups[type]) {
45297        if (!groups[groupId]) {
45298          groups[groupId] = [];
45299        }
45300
45301        for (var variantLabel in mediaGroups[type][groupId]) {
45302          var properties = mediaGroups[type][groupId][variantLabel];
45303
45304          // We only support CEA608 captions for now, so ignore anything that
45305          // doesn't use a CCx INSTREAM-ID
45306          if (!properties.instreamId.match(/CC\d/)) {
45307            continue;
45308          }
45309
45310          // No PlaylistLoader is required for Closed-Captions because the captions are
45311          // embedded within the video stream
45312          groups[groupId].push(videojs$1.mergeOptions({ id: variantLabel }, properties));
45313
45314          if (typeof tracks[variantLabel] === 'undefined') {
45315            var track = tech.addRemoteTextTrack({
45316              id: properties.instreamId,
45317              kind: 'captions',
45318              enabled: false,
45319              language: properties.language,
45320              label: variantLabel
45321            }, false).track;
45322
45323            tracks[variantLabel] = track;
45324          }
45325        }
45326      }
45327    }
45328  };
45329
45330  /**
45331   * Returns a function used to get the active group of the provided type
45332   *
45333   * @param {String} type
45334   *        MediaGroup type
45335   * @param {Object} settings
45336   *        Object containing required information for media groups
45337   * @return {Function}
45338   *         Function that returns the active media group for the provided type. Takes an
45339   *         optional parameter {TextTrack} track. If no track is provided, a list of all
45340   *         variants in the group, otherwise the variant corresponding to the provided
45341   *         track is returned.
45342   * @function activeGroup
45343   */
45344  var activeGroup = function activeGroup(type, settings) {
45345    return function (track) {
45346      var masterPlaylistLoader = settings.masterPlaylistLoader,
45347          groups = settings.mediaTypes[type].groups;
45348
45349      var media = masterPlaylistLoader.media();
45350
45351      if (!media) {
45352        return null;
45353      }
45354
45355      var variants = null;
45356
45357      if (media.attributes[type]) {
45358        variants = groups[media.attributes[type]];
45359      }
45360
45361      variants = variants || groups.main;
45362
45363      if (typeof track === 'undefined') {
45364        return variants;
45365      }
45366
45367      if (track === null) {
45368        // An active track was specified so a corresponding group is expected. track === null
45369        // means no track is currently active so there is no corresponding group
45370        return null;
45371      }
45372
45373      return variants.filter(function (props) {
45374        return props.id === track.id;
45375      })[0] || null;
45376    };
45377  };
45378
45379  var activeTrack = {
45380    /**
45381     * Returns a function used to get the active track of type provided
45382     *
45383     * @param {String} type
45384     *        MediaGroup type
45385     * @param {Object} settings
45386     *        Object containing required information for media groups
45387     * @return {Function}
45388     *         Function that returns the active media track for the provided type. Returns
45389     *         null if no track is active
45390     * @function activeTrack.AUDIO
45391     */
45392    AUDIO: function AUDIO(type, settings) {
45393      return function () {
45394        var tracks = settings.mediaTypes[type].tracks;
45395
45396        for (var id in tracks) {
45397          if (tracks[id].enabled) {
45398            return tracks[id];
45399          }
45400        }
45401
45402        return null;
45403      };
45404    },
45405    /**
45406     * Returns a function used to get the active track of type provided
45407     *
45408     * @param {String} type
45409     *        MediaGroup type
45410     * @param {Object} settings
45411     *        Object containing required information for media groups
45412     * @return {Function}
45413     *         Function that returns the active media track for the provided type. Returns
45414     *         null if no track is active
45415     * @function activeTrack.SUBTITLES
45416     */
45417    SUBTITLES: function SUBTITLES(type, settings) {
45418      return function () {
45419        var tracks = settings.mediaTypes[type].tracks;
45420
45421        for (var id in tracks) {
45422          if (tracks[id].mode === 'showing') {
45423            return tracks[id];
45424          }
45425        }
45426
45427        return null;
45428      };
45429    }
45430  };
45431
45432  /**
45433   * Setup PlaylistLoaders and Tracks for media groups (Audio, Subtitles,
45434   * Closed-Captions) specified in the master manifest.
45435   *
45436   * @param {Object} settings
45437   *        Object containing required information for setting up the media groups
45438   * @param {SegmentLoader} settings.segmentLoaders.AUDIO
45439   *        Audio segment loader
45440   * @param {SegmentLoader} settings.segmentLoaders.SUBTITLES
45441   *        Subtitle segment loader
45442   * @param {SegmentLoader} settings.segmentLoaders.main
45443   *        Main segment loader
45444   * @param {Tech} settings.tech
45445   *        The tech of the player
45446   * @param {Object} settings.requestOptions
45447   *        XHR request options used by the segment loaders
45448   * @param {PlaylistLoader} settings.masterPlaylistLoader
45449   *        PlaylistLoader for the master source
45450   * @param {HlsHandler} settings.hls
45451   *        HLS SourceHandler
45452   * @param {Object} settings.master
45453   *        The parsed master manifest
45454   * @param {Object} settings.mediaTypes
45455   *        Object to store the loaders, tracks, and utility methods for each media type
45456   * @param {Function} settings.blacklistCurrentPlaylist
45457   *        Blacklists the current rendition and forces a rendition switch.
45458   * @function setupMediaGroups
45459   */
45460  var setupMediaGroups = function setupMediaGroups(settings) {
45461    ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) {
45462      initialize[type](type, settings);
45463    });
45464
45465    var mediaTypes = settings.mediaTypes,
45466        masterPlaylistLoader = settings.masterPlaylistLoader,
45467        tech = settings.tech,
45468        hls = settings.hls;
45469
45470    // setup active group and track getters and change event handlers
45471
45472    ['AUDIO', 'SUBTITLES'].forEach(function (type) {
45473      mediaTypes[type].activeGroup = activeGroup(type, settings);
45474      mediaTypes[type].activeTrack = activeTrack[type](type, settings);
45475      mediaTypes[type].onGroupChanged = onGroupChanged(type, settings);
45476      mediaTypes[type].onTrackChanged = onTrackChanged(type, settings);
45477    });
45478
45479    // DO NOT enable the default subtitle or caption track.
45480    // DO enable the default audio track
45481    var audioGroup = mediaTypes.AUDIO.activeGroup();
45482    var groupId = (audioGroup.filter(function (group) {
45483      return group.default;
45484    })[0] || audioGroup[0]).id;
45485
45486    mediaTypes.AUDIO.tracks[groupId].enabled = true;
45487    mediaTypes.AUDIO.onTrackChanged();
45488
45489    masterPlaylistLoader.on('mediachange', function () {
45490      ['AUDIO', 'SUBTITLES'].forEach(function (type) {
45491        return mediaTypes[type].onGroupChanged();
45492      });
45493    });
45494
45495    // custom audio track change event handler for usage event
45496    var onAudioTrackChanged = function onAudioTrackChanged() {
45497      mediaTypes.AUDIO.onTrackChanged();
45498      tech.trigger({ type: 'usage', name: 'hls-audio-change' });
45499    };
45500
45501    tech.audioTracks().addEventListener('change', onAudioTrackChanged);
45502    tech.remoteTextTracks().addEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
45503
45504    hls.on('dispose', function () {
45505      tech.audioTracks().removeEventListener('change', onAudioTrackChanged);
45506      tech.remoteTextTracks().removeEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
45507    });
45508
45509    // clear existing audio tracks and add the ones we just created
45510    tech.clearTracks('audio');
45511
45512    for (var id in mediaTypes.AUDIO.tracks) {
45513      tech.audioTracks().addTrack(mediaTypes.AUDIO.tracks[id]);
45514    }
45515  };
45516
45517  /**
45518   * Creates skeleton object used to store the loaders, tracks, and utility methods for each
45519   * media type
45520   *
45521   * @return {Object}
45522   *         Object to store the loaders, tracks, and utility methods for each media type
45523   * @function createMediaTypes
45524   */
45525  var createMediaTypes = function createMediaTypes() {
45526    var mediaTypes = {};
45527
45528    ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) {
45529      mediaTypes[type] = {
45530        groups: {},
45531        tracks: {},
45532        activePlaylistLoader: null,
45533        activeGroup: noop$1,
45534        activeTrack: noop$1,
45535        onGroupChanged: noop$1,
45536        onTrackChanged: noop$1
45537      };
45538    });
45539
45540    return mediaTypes;
45541  };
45542
45543  /**
45544   * @file master-playlist-controller.js
45545   */
45546
45547  var ABORT_EARLY_BLACKLIST_SECONDS = 60 * 2;
45548
45549  var Hls = void 0;
45550
45551  // SegmentLoader stats that need to have each loader's
45552  // values summed to calculate the final value
45553  var loaderStats = ['mediaRequests', 'mediaRequestsAborted', 'mediaRequestsTimedout', 'mediaRequestsErrored', 'mediaTransferDuration', 'mediaBytesTransferred'];
45554  var sumLoaderStat = function sumLoaderStat(stat) {
45555    return this.audioSegmentLoader_[stat] + this.mainSegmentLoader_[stat];
45556  };
45557
45558  /**
45559   * the master playlist controller controller all interactons
45560   * between playlists and segmentloaders. At this time this mainly
45561   * involves a master playlist and a series of audio playlists
45562   * if they are available
45563   *
45564   * @class MasterPlaylistController
45565   * @extends videojs.EventTarget
45566   */
45567  var MasterPlaylistController = function (_videojs$EventTarget) {
45568    inherits$3(MasterPlaylistController, _videojs$EventTarget);
45569
45570    function MasterPlaylistController(options) {
45571      classCallCheck$3(this, MasterPlaylistController);
45572
45573      var _this = possibleConstructorReturn$3(this, (MasterPlaylistController.__proto__ || Object.getPrototypeOf(MasterPlaylistController)).call(this));
45574
45575      var url = options.url,
45576          withCredentials = options.withCredentials,
45577          tech = options.tech,
45578          bandwidth = options.bandwidth,
45579          externHls = options.externHls,
45580          useCueTags = options.useCueTags,
45581          blacklistDuration = options.blacklistDuration,
45582          enableLowInitialPlaylist = options.enableLowInitialPlaylist,
45583          sourceType = options.sourceType;
45584
45585      if (!url) {
45586        throw new Error('A non-empty playlist URL is required');
45587      }
45588
45589      Hls = externHls;
45590
45591      _this.withCredentials = withCredentials;
45592      _this.tech_ = tech;
45593      _this.hls_ = tech.hls;
45594      _this.sourceType_ = sourceType;
45595      _this.useCueTags_ = useCueTags;
45596      _this.blacklistDuration = blacklistDuration;
45597      _this.enableLowInitialPlaylist = enableLowInitialPlaylist;
45598      if (_this.useCueTags_) {
45599        _this.cueTagsTrack_ = _this.tech_.addTextTrack('metadata', 'ad-cues');
45600        _this.cueTagsTrack_.inBandMetadataTrackDispatchType = '';
45601      }
45602
45603      _this.requestOptions_ = {
45604        withCredentials: _this.withCredentials,
45605        timeout: null
45606      };
45607
45608      _this.mediaTypes_ = createMediaTypes();
45609
45610      _this.mediaSource = new videojs$1.MediaSource();
45611
45612      // load the media source into the player
45613      _this.mediaSource.addEventListener('sourceopen', _this.handleSourceOpen_.bind(_this));
45614
45615      _this.seekable_ = videojs$1.createTimeRanges();
45616      _this.hasPlayed_ = function () {
45617        return false;
45618      };
45619
45620      _this.syncController_ = new SyncController(options);
45621      _this.segmentMetadataTrack_ = tech.addRemoteTextTrack({
45622        kind: 'metadata',
45623        label: 'segment-metadata'
45624      }, false).track;
45625
45626      _this.decrypter_ = new Decrypter$1();
45627
45628      var segmentLoaderSettings = {
45629        hls: _this.hls_,
45630        mediaSource: _this.mediaSource,
45631        currentTime: _this.tech_.currentTime.bind(_this.tech_),
45632        seekable: function seekable$$1() {
45633          return _this.seekable();
45634        },
45635        seeking: function seeking() {
45636          return _this.tech_.seeking();
45637        },
45638        duration: function duration$$1() {
45639          return _this.mediaSource.duration;
45640        },
45641        hasPlayed: function hasPlayed() {
45642          return _this.hasPlayed_();
45643        },
45644        goalBufferLength: function goalBufferLength() {
45645          return _this.goalBufferLength();
45646        },
45647        bandwidth: bandwidth,
45648        syncController: _this.syncController_,
45649        decrypter: _this.decrypter_,
45650        sourceType: _this.sourceType_
45651      };
45652
45653      _this.masterPlaylistLoader_ = _this.sourceType_ === 'dash' ? new DashPlaylistLoader(url, _this.hls_, _this.withCredentials) : new PlaylistLoader(url, _this.hls_, _this.withCredentials);
45654      _this.setupMasterPlaylistLoaderListeners_();
45655
45656      // setup segment loaders
45657      // combined audio/video or just video when alternate audio track is selected
45658      _this.mainSegmentLoader_ = new SegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, {
45659        segmentMetadataTrack: _this.segmentMetadataTrack_,
45660        loaderType: 'main'
45661      }), options);
45662
45663      // alternate audio track
45664      _this.audioSegmentLoader_ = new SegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, {
45665        loaderType: 'audio'
45666      }), options);
45667
45668      _this.subtitleSegmentLoader_ = new VTTSegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, {
45669        loaderType: 'vtt'
45670      }), options);
45671
45672      _this.setupSegmentLoaderListeners_();
45673
45674      // Create SegmentLoader stat-getters
45675      loaderStats.forEach(function (stat) {
45676        _this[stat + '_'] = sumLoaderStat.bind(_this, stat);
45677      });
45678
45679      _this.logger_ = logger('MPC');
45680
45681      _this.masterPlaylistLoader_.load();
45682      return _this;
45683    }
45684
45685    /**
45686     * Register event handlers on the master playlist loader. A helper
45687     * function for construction time.
45688     *
45689     * @private
45690     */
45691
45692    createClass$2(MasterPlaylistController, [{
45693      key: 'setupMasterPlaylistLoaderListeners_',
45694      value: function setupMasterPlaylistLoaderListeners_() {
45695        var _this2 = this;
45696
45697        this.masterPlaylistLoader_.on('loadedmetadata', function () {
45698          var media = _this2.masterPlaylistLoader_.media();
45699          var requestTimeout = _this2.masterPlaylistLoader_.targetDuration * 1.5 * 1000;
45700
45701          // If we don't have any more available playlists, we don't want to
45702          // timeout the request.
45703          if (isLowestEnabledRendition(_this2.masterPlaylistLoader_.master, _this2.masterPlaylistLoader_.media())) {
45704            _this2.requestOptions_.timeout = 0;
45705          } else {
45706            _this2.requestOptions_.timeout = requestTimeout;
45707          }
45708
45709          // if this isn't a live video and preload permits, start
45710          // downloading segments
45711          if (media.endList && _this2.tech_.preload() !== 'none') {
45712            _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_);
45713            _this2.mainSegmentLoader_.load();
45714          }
45715
45716          setupMediaGroups({
45717            sourceType: _this2.sourceType_,
45718            segmentLoaders: {
45719              AUDIO: _this2.audioSegmentLoader_,
45720              SUBTITLES: _this2.subtitleSegmentLoader_,
45721              main: _this2.mainSegmentLoader_
45722            },
45723            tech: _this2.tech_,
45724            requestOptions: _this2.requestOptions_,
45725            masterPlaylistLoader: _this2.masterPlaylistLoader_,
45726            hls: _this2.hls_,
45727            master: _this2.master(),
45728            mediaTypes: _this2.mediaTypes_,
45729            blacklistCurrentPlaylist: _this2.blacklistCurrentPlaylist.bind(_this2)
45730          });
45731
45732          _this2.triggerPresenceUsage_(_this2.master(), media);
45733
45734          try {
45735            _this2.setupSourceBuffers_();
45736          } catch (e) {
45737            videojs$1.log.warn('Failed to create SourceBuffers', e);
45738            return _this2.mediaSource.endOfStream('decode');
45739          }
45740          _this2.setupFirstPlay();
45741
45742          _this2.trigger('selectedinitialmedia');
45743        });
45744
45745        this.masterPlaylistLoader_.on('loadedplaylist', function () {
45746          var updatedPlaylist = _this2.masterPlaylistLoader_.media();
45747
45748          if (!updatedPlaylist) {
45749            // blacklist any variants that are not supported by the browser before selecting
45750            // an initial media as the playlist selectors do not consider browser support
45751            _this2.excludeUnsupportedVariants_();
45752
45753            var selectedMedia = void 0;
45754
45755            if (_this2.enableLowInitialPlaylist) {
45756              selectedMedia = _this2.selectInitialPlaylist();
45757            }
45758
45759            if (!selectedMedia) {
45760              selectedMedia = _this2.selectPlaylist();
45761            }
45762
45763            _this2.initialMedia_ = selectedMedia;
45764            _this2.masterPlaylistLoader_.media(_this2.initialMedia_);
45765            return;
45766          }
45767
45768          if (_this2.useCueTags_) {
45769            _this2.updateAdCues_(updatedPlaylist);
45770          }
45771
45772          // TODO: Create a new event on the PlaylistLoader that signals
45773          // that the segments have changed in some way and use that to
45774          // update the SegmentLoader instead of doing it twice here and
45775          // on `mediachange`
45776          _this2.mainSegmentLoader_.playlist(updatedPlaylist, _this2.requestOptions_);
45777          _this2.updateDuration();
45778
45779          // If the player isn't paused, ensure that the segment loader is running,
45780          // as it is possible that it was temporarily stopped while waiting for
45781          // a playlist (e.g., in case the playlist errored and we re-requested it).
45782          if (!_this2.tech_.paused()) {
45783            _this2.mainSegmentLoader_.load();
45784          }
45785
45786          if (!updatedPlaylist.endList) {
45787            var addSeekableRange = function addSeekableRange() {
45788              var seekable$$1 = _this2.seekable();
45789
45790              if (seekable$$1.length !== 0) {
45791                _this2.mediaSource.addSeekableRange_(seekable$$1.start(0), seekable$$1.end(0));
45792              }
45793            };
45794
45795            if (_this2.duration() !== Infinity) {
45796              var onDurationchange = function onDurationchange() {
45797                if (_this2.duration() === Infinity) {
45798                  addSeekableRange();
45799                } else {
45800                  _this2.tech_.one('durationchange', onDurationchange);
45801                }
45802              };
45803
45804              _this2.tech_.one('durationchange', onDurationchange);
45805            } else {
45806              addSeekableRange();
45807            }
45808          }
45809        });
45810
45811        this.masterPlaylistLoader_.on('error', function () {
45812          _this2.blacklistCurrentPlaylist(_this2.masterPlaylistLoader_.error);
45813        });
45814
45815        this.masterPlaylistLoader_.on('mediachanging', function () {
45816          _this2.mainSegmentLoader_.abort();
45817          _this2.mainSegmentLoader_.pause();
45818        });
45819
45820        this.masterPlaylistLoader_.on('mediachange', function () {
45821          var media = _this2.masterPlaylistLoader_.media();
45822          var requestTimeout = _this2.masterPlaylistLoader_.targetDuration * 1.5 * 1000;
45823
45824          // If we don't have any more available playlists, we don't want to
45825          // timeout the request.
45826          if (isLowestEnabledRendition(_this2.masterPlaylistLoader_.master, _this2.masterPlaylistLoader_.media())) {
45827            _this2.requestOptions_.timeout = 0;
45828          } else {
45829            _this2.requestOptions_.timeout = requestTimeout;
45830          }
45831
45832          // TODO: Create a new event on the PlaylistLoader that signals
45833          // that the segments have changed in some way and use that to
45834          // update the SegmentLoader instead of doing it twice here and
45835          // on `loadedplaylist`
45836          _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_);
45837          _this2.mainSegmentLoader_.load();
45838
45839          _this2.tech_.trigger({
45840            type: 'mediachange',
45841            bubbles: true
45842          });
45843        });
45844
45845        this.masterPlaylistLoader_.on('playlistunchanged', function () {
45846          var updatedPlaylist = _this2.masterPlaylistLoader_.media();
45847          var playlistOutdated = _this2.stuckAtPlaylistEnd_(updatedPlaylist);
45848
45849          if (playlistOutdated) {
45850            // Playlist has stopped updating and we're stuck at its end. Try to
45851            // blacklist it and switch to another playlist in the hope that that
45852            // one is updating (and give the player a chance to re-adjust to the
45853            // safe live point).
45854            _this2.blacklistCurrentPlaylist({
45855              message: 'Playlist no longer updating.'
45856            });
45857            // useful for monitoring QoS
45858            _this2.tech_.trigger('playliststuck');
45859          }
45860        });
45861
45862        this.masterPlaylistLoader_.on('renditiondisabled', function () {
45863          _this2.tech_.trigger({ type: 'usage', name: 'hls-rendition-disabled' });
45864        });
45865        this.masterPlaylistLoader_.on('renditionenabled', function () {
45866          _this2.tech_.trigger({ type: 'usage', name: 'hls-rendition-enabled' });
45867        });
45868      }
45869
45870      /**
45871       * A helper function for triggerring presence usage events once per source
45872       *
45873       * @private
45874       */
45875
45876    }, {
45877      key: 'triggerPresenceUsage_',
45878      value: function triggerPresenceUsage_(master, media) {
45879        var mediaGroups = master.mediaGroups || {};
45880        var defaultDemuxed = true;
45881        var audioGroupKeys = Object.keys(mediaGroups.AUDIO);
45882
45883        for (var mediaGroup in mediaGroups.AUDIO) {
45884          for (var label in mediaGroups.AUDIO[mediaGroup]) {
45885            var properties = mediaGroups.AUDIO[mediaGroup][label];
45886
45887            if (!properties.uri) {
45888              defaultDemuxed = false;
45889            }
45890          }
45891        }
45892
45893        if (defaultDemuxed) {
45894          this.tech_.trigger({ type: 'usage', name: 'hls-demuxed' });
45895        }
45896
45897        if (Object.keys(mediaGroups.SUBTITLES).length) {
45898          this.tech_.trigger({ type: 'usage', name: 'hls-webvtt' });
45899        }
45900
45901        if (Hls.Playlist.isAes(media)) {
45902          this.tech_.trigger({ type: 'usage', name: 'hls-aes' });
45903        }
45904
45905        if (Hls.Playlist.isFmp4(media)) {
45906          this.tech_.trigger({ type: 'usage', name: 'hls-fmp4' });
45907        }
45908
45909        if (audioGroupKeys.length && Object.keys(mediaGroups.AUDIO[audioGroupKeys[0]]).length > 1) {
45910          this.tech_.trigger({ type: 'usage', name: 'hls-alternate-audio' });
45911        }
45912
45913        if (this.useCueTags_) {
45914          this.tech_.trigger({ type: 'usage', name: 'hls-playlist-cue-tags' });
45915        }
45916      }
45917      /**
45918       * Register event handlers on the segment loaders. A helper function
45919       * for construction time.
45920       *
45921       * @private
45922       */
45923
45924    }, {
45925      key: 'setupSegmentLoaderListeners_',
45926      value: function setupSegmentLoaderListeners_() {
45927        var _this3 = this;
45928
45929        this.mainSegmentLoader_.on('bandwidthupdate', function () {
45930          var nextPlaylist = _this3.selectPlaylist();
45931          var currentPlaylist = _this3.masterPlaylistLoader_.media();
45932          var buffered = _this3.tech_.buffered();
45933          var forwardBuffer = buffered.length ? buffered.end(buffered.length - 1) - _this3.tech_.currentTime() : 0;
45934
45935          var bufferLowWaterLine = _this3.bufferLowWaterLine();
45936
45937          // If the playlist is live, then we want to not take low water line into account.
45938          // This is because in LIVE, the player plays 3 segments from the end of the
45939          // playlist, and if `BUFFER_LOW_WATER_LINE` is greater than the duration availble
45940          // in those segments, a viewer will never experience a rendition upswitch.
45941          if (!currentPlaylist.endList ||
45942          // For the same reason as LIVE, we ignore the low water line when the VOD
45943          // duration is below the max potential low water line
45944          _this3.duration() < Config.MAX_BUFFER_LOW_WATER_LINE ||
45945          // we want to switch down to lower resolutions quickly to continue playback, but
45946          nextPlaylist.attributes.BANDWIDTH < currentPlaylist.attributes.BANDWIDTH ||
45947          // ensure we have some buffer before we switch up to prevent us running out of
45948          // buffer while loading a higher rendition.
45949          forwardBuffer >= bufferLowWaterLine) {
45950            _this3.masterPlaylistLoader_.media(nextPlaylist);
45951          }
45952
45953          _this3.tech_.trigger('bandwidthupdate');
45954        });
45955        this.mainSegmentLoader_.on('progress', function () {
45956          _this3.trigger('progress');
45957        });
45958
45959        this.mainSegmentLoader_.on('error', function () {
45960          _this3.blacklistCurrentPlaylist(_this3.mainSegmentLoader_.error());
45961        });
45962
45963        this.mainSegmentLoader_.on('syncinfoupdate', function () {
45964          _this3.onSyncInfoUpdate_();
45965        });
45966
45967        this.mainSegmentLoader_.on('timestampoffset', function () {
45968          _this3.tech_.trigger({ type: 'usage', name: 'hls-timestamp-offset' });
45969        });
45970        this.audioSegmentLoader_.on('syncinfoupdate', function () {
45971          _this3.onSyncInfoUpdate_();
45972        });
45973
45974        this.mainSegmentLoader_.on('ended', function () {
45975          _this3.onEndOfStream();
45976        });
45977
45978        this.mainSegmentLoader_.on('earlyabort', function () {
45979          _this3.blacklistCurrentPlaylist({
45980            message: 'Aborted early because there isn\'t enough bandwidth to complete the ' + 'request without rebuffering.'
45981          }, ABORT_EARLY_BLACKLIST_SECONDS);
45982        });
45983
45984        this.mainSegmentLoader_.on('reseteverything', function () {
45985          // If playing an MTS stream, a videojs.MediaSource is listening for
45986          // hls-reset to reset caption parsing state in the transmuxer
45987          _this3.tech_.trigger('hls-reset');
45988        });
45989
45990        this.mainSegmentLoader_.on('segmenttimemapping', function (event) {
45991          // If playing an MTS stream in html, a videojs.MediaSource is listening for
45992          // hls-segment-time-mapping update its internal mapping of stream to display time
45993          _this3.tech_.trigger({
45994            type: 'hls-segment-time-mapping',
45995            mapping: event.mapping
45996          });
45997        });
45998
45999        this.audioSegmentLoader_.on('ended', function () {
46000          _this3.onEndOfStream();
46001        });
46002      }
46003    }, {
46004      key: 'mediaSecondsLoaded_',
46005      value: function mediaSecondsLoaded_() {
46006        return Math.max(this.audioSegmentLoader_.mediaSecondsLoaded + this.mainSegmentLoader_.mediaSecondsLoaded);
46007      }
46008
46009      /**
46010       * Call load on our SegmentLoaders
46011       */
46012
46013    }, {
46014      key: 'load',
46015      value: function load() {
46016        this.mainSegmentLoader_.load();
46017        if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
46018          this.audioSegmentLoader_.load();
46019        }
46020        if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
46021          this.subtitleSegmentLoader_.load();
46022        }
46023      }
46024
46025      /**
46026       * Re-tune playback quality level for the current player
46027       * conditions. This method may perform destructive actions, like
46028       * removing already buffered content, to readjust the currently
46029       * active playlist quickly.
46030       *
46031       * @private
46032       */
46033
46034    }, {
46035      key: 'fastQualityChange_',
46036      value: function fastQualityChange_() {
46037        var media = this.selectPlaylist();
46038
46039        if (media !== this.masterPlaylistLoader_.media()) {
46040          this.masterPlaylistLoader_.media(media);
46041
46042          this.mainSegmentLoader_.resetLoader();
46043          // don't need to reset audio as it is reset when media changes
46044        }
46045      }
46046
46047      /**
46048       * Begin playback.
46049       */
46050
46051    }, {
46052      key: 'play',
46053      value: function play() {
46054        if (this.setupFirstPlay()) {
46055          return;
46056        }
46057
46058        if (this.tech_.ended()) {
46059          this.tech_.setCurrentTime(0);
46060        }
46061
46062        if (this.hasPlayed_()) {
46063          this.load();
46064        }
46065
46066        var seekable$$1 = this.tech_.seekable();
46067
46068        // if the viewer has paused and we fell out of the live window,
46069        // seek forward to the live point
46070        if (this.tech_.duration() === Infinity) {
46071          if (this.tech_.currentTime() < seekable$$1.start(0)) {
46072            return this.tech_.setCurrentTime(seekable$$1.end(seekable$$1.length - 1));
46073          }
46074        }
46075      }
46076
46077      /**
46078       * Seek to the latest media position if this is a live video and the
46079       * player and video are loaded and initialized.
46080       */
46081
46082    }, {
46083      key: 'setupFirstPlay',
46084      value: function setupFirstPlay() {
46085        var _this4 = this;
46086
46087        var media = this.masterPlaylistLoader_.media();
46088
46089        // Check that everything is ready to begin buffering for the first call to play
46090        //  If 1) there is no active media
46091        //     2) the player is paused
46092        //     3) the first play has already been setup
46093        // then exit early
46094        if (!media || this.tech_.paused() || this.hasPlayed_()) {
46095          return false;
46096        }
46097
46098        // when the video is a live stream
46099        if (!media.endList) {
46100          var seekable$$1 = this.seekable();
46101
46102          if (!seekable$$1.length) {
46103            // without a seekable range, the player cannot seek to begin buffering at the live
46104            // point
46105            return false;
46106          }
46107
46108          if (videojs$1.browser.IE_VERSION && this.tech_.readyState() === 0) {
46109            // IE11 throws an InvalidStateError if you try to set currentTime while the
46110            // readyState is 0, so it must be delayed until the tech fires loadedmetadata.
46111            this.tech_.one('loadedmetadata', function () {
46112              _this4.trigger('firstplay');
46113              _this4.tech_.setCurrentTime(seekable$$1.end(0));
46114              _this4.hasPlayed_ = function () {
46115                return true;
46116              };
46117            });
46118
46119            return false;
46120          }
46121
46122          // trigger firstplay to inform the source handler to ignore the next seek event
46123          this.trigger('firstplay');
46124          // seek to the live point
46125          this.tech_.setCurrentTime(seekable$$1.end(0));
46126        }
46127
46128        this.hasPlayed_ = function () {
46129          return true;
46130        };
46131        // we can begin loading now that everything is ready
46132        this.load();
46133        return true;
46134      }
46135
46136      /**
46137       * handle the sourceopen event on the MediaSource
46138       *
46139       * @private
46140       */
46141
46142    }, {
46143      key: 'handleSourceOpen_',
46144      value: function handleSourceOpen_() {
46145        // Only attempt to create the source buffer if none already exist.
46146        // handleSourceOpen is also called when we are "re-opening" a source buffer
46147        // after `endOfStream` has been called (in response to a seek for instance)
46148        try {
46149          this.setupSourceBuffers_();
46150        } catch (e) {
46151          videojs$1.log.warn('Failed to create Source Buffers', e);
46152          return this.mediaSource.endOfStream('decode');
46153        }
46154
46155        // if autoplay is enabled, begin playback. This is duplicative of
46156        // code in video.js but is required because play() must be invoked
46157        // *after* the media source has opened.
46158        if (this.tech_.autoplay()) {
46159          var playPromise = this.tech_.play();
46160
46161          // Catch/silence error when a pause interrupts a play request
46162          // on browsers which return a promise
46163          if (typeof playPromise !== 'undefined' && typeof playPromise.then === 'function') {
46164            playPromise.then(null, function (e) {});
46165          }
46166        }
46167
46168        this.trigger('sourceopen');
46169      }
46170
46171      /**
46172       * Calls endOfStream on the media source when all active stream types have called
46173       * endOfStream
46174       *
46175       * @param {string} streamType
46176       *        Stream type of the segment loader that called endOfStream
46177       * @private
46178       */
46179
46180    }, {
46181      key: 'onEndOfStream',
46182      value: function onEndOfStream() {
46183        var isEndOfStream = this.mainSegmentLoader_.ended_;
46184
46185        if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
46186          // if the audio playlist loader exists, then alternate audio is active
46187          if (!this.mainSegmentLoader_.startingMedia_ || this.mainSegmentLoader_.startingMedia_.containsVideo) {
46188            // if we do not know if the main segment loader contains video yet or if we
46189            // definitively know the main segment loader contains video, then we need to wait
46190            // for both main and audio segment loaders to call endOfStream
46191            isEndOfStream = isEndOfStream && this.audioSegmentLoader_.ended_;
46192          } else {
46193            // otherwise just rely on the audio loader
46194            isEndOfStream = this.audioSegmentLoader_.ended_;
46195          }
46196        }
46197
46198        if (isEndOfStream) {
46199          this.mediaSource.endOfStream();
46200        }
46201      }
46202
46203      /**
46204       * Check if a playlist has stopped being updated
46205       * @param {Object} playlist the media playlist object
46206       * @return {boolean} whether the playlist has stopped being updated or not
46207       */
46208
46209    }, {
46210      key: 'stuckAtPlaylistEnd_',
46211      value: function stuckAtPlaylistEnd_(playlist) {
46212        var seekable$$1 = this.seekable();
46213
46214        if (!seekable$$1.length) {
46215          // playlist doesn't have enough information to determine whether we are stuck
46216          return false;
46217        }
46218
46219        var expired = this.syncController_.getExpiredTime(playlist, this.mediaSource.duration);
46220
46221        if (expired === null) {
46222          return false;
46223        }
46224
46225        // does not use the safe live end to calculate playlist end, since we
46226        // don't want to say we are stuck while there is still content
46227        var absolutePlaylistEnd = Hls.Playlist.playlistEnd(playlist, expired);
46228        var currentTime = this.tech_.currentTime();
46229        var buffered = this.tech_.buffered();
46230
46231        if (!buffered.length) {
46232          // return true if the playhead reached the absolute end of the playlist
46233          return absolutePlaylistEnd - currentTime <= SAFE_TIME_DELTA;
46234        }
46235        var bufferedEnd = buffered.end(buffered.length - 1);
46236
46237        // return true if there is too little buffer left and buffer has reached absolute
46238        // end of playlist
46239        return bufferedEnd - currentTime <= SAFE_TIME_DELTA && absolutePlaylistEnd - bufferedEnd <= SAFE_TIME_DELTA;
46240      }
46241
46242      /**
46243       * Blacklists a playlist when an error occurs for a set amount of time
46244       * making it unavailable for selection by the rendition selection algorithm
46245       * and then forces a new playlist (rendition) selection.
46246       *
46247       * @param {Object=} error an optional error that may include the playlist
46248       * to blacklist
46249       * @param {Number=} blacklistDuration an optional number of seconds to blacklist the
46250       * playlist
46251       */
46252
46253    }, {
46254      key: 'blacklistCurrentPlaylist',
46255      value: function blacklistCurrentPlaylist() {
46256        var error = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
46257        var blacklistDuration = arguments[1];
46258
46259        var currentPlaylist = void 0;
46260        var nextPlaylist = void 0;
46261
46262        // If the `error` was generated by the playlist loader, it will contain
46263        // the playlist we were trying to load (but failed) and that should be
46264        // blacklisted instead of the currently selected playlist which is likely
46265        // out-of-date in this scenario
46266        currentPlaylist = error.playlist || this.masterPlaylistLoader_.media();
46267
46268        blacklistDuration = blacklistDuration || error.blacklistDuration || this.blacklistDuration;
46269
46270        // If there is no current playlist, then an error occurred while we were
46271        // trying to load the master OR while we were disposing of the tech
46272        if (!currentPlaylist) {
46273          this.error = error;
46274
46275          try {
46276            return this.mediaSource.endOfStream('network');
46277          } catch (e) {
46278            return this.trigger('error');
46279          }
46280        }
46281
46282        var isFinalRendition = this.masterPlaylistLoader_.master.playlists.filter(isEnabled).length === 1;
46283
46284        if (isFinalRendition) {
46285          // Never blacklisting this playlist because it's final rendition
46286          videojs$1.log.warn('Problem encountered with the current ' + 'HLS playlist. Trying again since it is the final playlist.');
46287
46288          this.tech_.trigger('retryplaylist');
46289          return this.masterPlaylistLoader_.load(isFinalRendition);
46290        }
46291        // Blacklist this playlist
46292        currentPlaylist.excludeUntil = Date.now() + blacklistDuration * 1000;
46293        this.tech_.trigger('blacklistplaylist');
46294        this.tech_.trigger({ type: 'usage', name: 'hls-rendition-blacklisted' });
46295
46296        // Select a new playlist
46297        nextPlaylist = this.selectPlaylist();
46298        videojs$1.log.warn('Problem encountered with the current HLS playlist.' + (error.message ? ' ' + error.message : '') + ' Switching to another playlist.');
46299
46300        return this.masterPlaylistLoader_.media(nextPlaylist);
46301      }
46302
46303      /**
46304       * Pause all segment loaders
46305       */
46306
46307    }, {
46308      key: 'pauseLoading',
46309      value: function pauseLoading() {
46310        this.mainSegmentLoader_.pause();
46311        if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
46312          this.audioSegmentLoader_.pause();
46313        }
46314        if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
46315          this.subtitleSegmentLoader_.pause();
46316        }
46317      }
46318
46319      /**
46320       * set the current time on all segment loaders
46321       *
46322       * @param {TimeRange} currentTime the current time to set
46323       * @return {TimeRange} the current time
46324       */
46325
46326    }, {
46327      key: 'setCurrentTime',
46328      value: function setCurrentTime(currentTime) {
46329        var buffered = findRange(this.tech_.buffered(), currentTime);
46330
46331        if (!(this.masterPlaylistLoader_ && this.masterPlaylistLoader_.media())) {
46332          // return immediately if the metadata is not ready yet
46333          return 0;
46334        }
46335
46336        // it's clearly an edge-case but don't thrown an error if asked to
46337        // seek within an empty playlist
46338        if (!this.masterPlaylistLoader_.media().segments) {
46339          return 0;
46340        }
46341
46342        // In flash playback, the segment loaders should be reset on every seek, even
46343        // in buffer seeks. If the seek location is already buffered, continue buffering as
46344        // usual
46345        // TODO: redo this comment
46346        if (buffered && buffered.length) {
46347          return currentTime;
46348        }
46349
46350        // cancel outstanding requests so we begin buffering at the new
46351        // location
46352        this.mainSegmentLoader_.resetEverything();
46353        this.mainSegmentLoader_.abort();
46354        if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
46355          this.audioSegmentLoader_.resetEverything();
46356          this.audioSegmentLoader_.abort();
46357        }
46358        if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
46359          this.subtitleSegmentLoader_.resetEverything();
46360          this.subtitleSegmentLoader_.abort();
46361        }
46362
46363        // start segment loader loading in case they are paused
46364        this.load();
46365      }
46366
46367      /**
46368       * get the current duration
46369       *
46370       * @return {TimeRange} the duration
46371       */
46372
46373    }, {
46374      key: 'duration',
46375      value: function duration$$1() {
46376        if (!this.masterPlaylistLoader_) {
46377          return 0;
46378        }
46379
46380        if (this.mediaSource) {
46381          return this.mediaSource.duration;
46382        }
46383
46384        return Hls.Playlist.duration(this.masterPlaylistLoader_.media());
46385      }
46386
46387      /**
46388       * check the seekable range
46389       *
46390       * @return {TimeRange} the seekable range
46391       */
46392
46393    }, {
46394      key: 'seekable',
46395      value: function seekable$$1() {
46396        return this.seekable_;
46397      }
46398    }, {
46399      key: 'onSyncInfoUpdate_',
46400      value: function onSyncInfoUpdate_() {
46401        var mainSeekable = void 0;
46402        var audioSeekable = void 0;
46403
46404        if (!this.masterPlaylistLoader_) {
46405          return;
46406        }
46407
46408        var media = this.masterPlaylistLoader_.media();
46409
46410        if (!media) {
46411          return;
46412        }
46413
46414        var expired = this.syncController_.getExpiredTime(media, this.mediaSource.duration);
46415
46416        if (expired === null) {
46417          // not enough information to update seekable
46418          return;
46419        }
46420
46421        mainSeekable = Hls.Playlist.seekable(media, expired);
46422
46423        if (mainSeekable.length === 0) {
46424          return;
46425        }
46426
46427        if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
46428          media = this.mediaTypes_.AUDIO.activePlaylistLoader.media();
46429          expired = this.syncController_.getExpiredTime(media, this.mediaSource.duration);
46430
46431          if (expired === null) {
46432            return;
46433          }
46434
46435          audioSeekable = Hls.Playlist.seekable(media, expired);
46436
46437          if (audioSeekable.length === 0) {
46438            return;
46439          }
46440        }
46441
46442        if (!audioSeekable) {
46443          // seekable has been calculated based on buffering video data so it
46444          // can be returned directly
46445          this.seekable_ = mainSeekable;
46446        } else if (audioSeekable.start(0) > mainSeekable.end(0) || mainSeekable.start(0) > audioSeekable.end(0)) {
46447          // seekables are pretty far off, rely on main
46448          this.seekable_ = mainSeekable;
46449        } else {
46450          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)]]);
46451        }
46452
46453        this.logger_('seekable updated [' + printableRange(this.seekable_) + ']');
46454
46455        this.tech_.trigger('seekablechanged');
46456      }
46457
46458      /**
46459       * Update the player duration
46460       */
46461
46462    }, {
46463      key: 'updateDuration',
46464      value: function updateDuration() {
46465        var _this5 = this;
46466
46467        var oldDuration = this.mediaSource.duration;
46468        var newDuration = Hls.Playlist.duration(this.masterPlaylistLoader_.media());
46469        var buffered = this.tech_.buffered();
46470        var setDuration = function setDuration() {
46471          _this5.mediaSource.duration = newDuration;
46472          _this5.tech_.trigger('durationchange');
46473
46474          _this5.mediaSource.removeEventListener('sourceopen', setDuration);
46475        };
46476
46477        if (buffered.length > 0) {
46478          newDuration = Math.max(newDuration, buffered.end(buffered.length - 1));
46479        }
46480
46481        // if the duration has changed, invalidate the cached value
46482        if (oldDuration !== newDuration) {
46483          // update the duration
46484          if (this.mediaSource.readyState !== 'open') {
46485            this.mediaSource.addEventListener('sourceopen', setDuration);
46486          } else {
46487            setDuration();
46488          }
46489        }
46490      }
46491
46492      /**
46493       * dispose of the MasterPlaylistController and everything
46494       * that it controls
46495       */
46496
46497    }, {
46498      key: 'dispose',
46499      value: function dispose() {
46500        var _this6 = this;
46501
46502        this.decrypter_.terminate();
46503        this.masterPlaylistLoader_.dispose();
46504        this.mainSegmentLoader_.dispose();
46505
46506        ['AUDIO', 'SUBTITLES'].forEach(function (type) {
46507          var groups = _this6.mediaTypes_[type].groups;
46508
46509          for (var id in groups) {
46510            groups[id].forEach(function (group) {
46511              if (group.playlistLoader) {
46512                group.playlistLoader.dispose();
46513              }
46514            });
46515          }
46516        });
46517
46518        this.audioSegmentLoader_.dispose();
46519        this.subtitleSegmentLoader_.dispose();
46520      }
46521
46522      /**
46523       * return the master playlist object if we have one
46524       *
46525       * @return {Object} the master playlist object that we parsed
46526       */
46527
46528    }, {
46529      key: 'master',
46530      value: function master() {
46531        return this.masterPlaylistLoader_.master;
46532      }
46533
46534      /**
46535       * return the currently selected playlist
46536       *
46537       * @return {Object} the currently selected playlist object that we parsed
46538       */
46539
46540    }, {
46541      key: 'media',
46542      value: function media() {
46543        // playlist loader will not return media if it has not been fully loaded
46544        return this.masterPlaylistLoader_.media() || this.initialMedia_;
46545      }
46546
46547      /**
46548       * setup our internal source buffers on our segment Loaders
46549       *
46550       * @private
46551       */
46552
46553    }, {
46554      key: 'setupSourceBuffers_',
46555      value: function setupSourceBuffers_() {
46556        var media = this.masterPlaylistLoader_.media();
46557        var mimeTypes = void 0;
46558
46559        // wait until a media playlist is available and the Media Source is
46560        // attached
46561        if (!media || this.mediaSource.readyState !== 'open') {
46562          return;
46563        }
46564
46565        mimeTypes = mimeTypesForPlaylist(this.masterPlaylistLoader_.master, media);
46566        if (mimeTypes.length < 1) {
46567          this.error = 'No compatible SourceBuffer configuration for the variant stream:' + media.resolvedUri;
46568          return this.mediaSource.endOfStream('decode');
46569        }
46570
46571        this.configureLoaderMimeTypes_(mimeTypes);
46572        // exclude any incompatible variant streams from future playlist
46573        // selection
46574        this.excludeIncompatibleVariants_(media);
46575      }
46576    }, {
46577      key: 'configureLoaderMimeTypes_',
46578      value: function configureLoaderMimeTypes_(mimeTypes) {
46579        // If the content is demuxed, we can't start appending segments to a source buffer
46580        // until both source buffers are set up, or else the browser may not let us add the
46581        // second source buffer (it will assume we are playing either audio only or video
46582        // only).
46583        var sourceBufferEmitter =
46584        // If there is more than one mime type
46585        mimeTypes.length > 1 &&
46586        // and the first mime type does not have muxed video and audio
46587        mimeTypes[0].indexOf(',') === -1 &&
46588        // and the two mime types are different (they can be the same in the case of audio
46589        // only with alternate audio)
46590        mimeTypes[0] !== mimeTypes[1] ?
46591        // then we want to wait on the second source buffer
46592        new videojs$1.EventTarget() :
46593        // otherwise there is no need to wait as the content is either audio only,
46594        // video only, or muxed content.
46595        null;
46596
46597        this.mainSegmentLoader_.mimeType(mimeTypes[0], sourceBufferEmitter);
46598        if (mimeTypes[1]) {
46599          this.audioSegmentLoader_.mimeType(mimeTypes[1], sourceBufferEmitter);
46600        }
46601      }
46602
46603      /**
46604       * Blacklists playlists with codecs that are unsupported by the browser.
46605       */
46606
46607    }, {
46608      key: 'excludeUnsupportedVariants_',
46609      value: function excludeUnsupportedVariants_() {
46610        this.master().playlists.forEach(function (variant) {
46611          if (variant.attributes.CODECS && window_1.MediaSource && window_1.MediaSource.isTypeSupported && !window_1.MediaSource.isTypeSupported('video/mp4; codecs="' + mapLegacyAvcCodecs(variant.attributes.CODECS) + '"')) {
46612            variant.excludeUntil = Infinity;
46613          }
46614        });
46615      }
46616
46617      /**
46618       * Blacklist playlists that are known to be codec or
46619       * stream-incompatible with the SourceBuffer configuration. For
46620       * instance, Media Source Extensions would cause the video element to
46621       * stall waiting for video data if you switched from a variant with
46622       * video and audio to an audio-only one.
46623       *
46624       * @param {Object} media a media playlist compatible with the current
46625       * set of SourceBuffers. Variants in the current master playlist that
46626       * do not appear to have compatible codec or stream configurations
46627       * will be excluded from the default playlist selection algorithm
46628       * indefinitely.
46629       * @private
46630       */
46631
46632    }, {
46633      key: 'excludeIncompatibleVariants_',
46634      value: function excludeIncompatibleVariants_(media) {
46635        var codecCount = 2;
46636        var videoCodec = null;
46637        var codecs = void 0;
46638
46639        if (media.attributes.CODECS) {
46640          codecs = parseCodecs(media.attributes.CODECS);
46641          videoCodec = codecs.videoCodec;
46642          codecCount = codecs.codecCount;
46643        }
46644
vendor: 45,268 bytes, lines 46645-47941
46645        this.master().playlists.forEach(function (variant) {
46646          var variantCodecs = {
46647            codecCount: 2,
46648            videoCodec: null
46649          };
46650
46651          if (variant.attributes.CODECS) {
46652            variantCodecs = parseCodecs(variant.attributes.CODECS);
46653          }
46654
46655          // if the streams differ in the presence or absence of audio or
46656          // video, they are incompatible
46657          if (variantCodecs.codecCount !== codecCount) {
46658            variant.excludeUntil = Infinity;
46659          }
46660
46661          // if h.264 is specified on the current playlist, some flavor of
46662          // it must be specified on all compatible variants
46663          if (variantCodecs.videoCodec !== videoCodec) {
46664            variant.excludeUntil = Infinity;
46665          }
46666        });
46667      }
46668    }, {
46669      key: 'updateAdCues_',
46670      value: function updateAdCues_(media) {
46671        var offset = 0;
46672        var seekable$$1 = this.seekable();
46673
46674        if (seekable$$1.length) {
46675          offset = seekable$$1.start(0);
46676        }
46677
46678        updateAdCues(media, this.cueTagsTrack_, offset);
46679      }
46680
46681      /**
46682       * Calculates the desired forward buffer length based on current time
46683       *
46684       * @return {Number} Desired forward buffer length in seconds
46685       */
46686
46687    }, {
46688      key: 'goalBufferLength',
46689      value: function goalBufferLength() {
46690        var currentTime = this.tech_.currentTime();
46691        var initial = Config.GOAL_BUFFER_LENGTH;
46692        var rate = Config.GOAL_BUFFER_LENGTH_RATE;
46693        var max = Math.max(initial, Config.MAX_GOAL_BUFFER_LENGTH);
46694
46695        return Math.min(initial + currentTime * rate, max);
46696      }
46697
46698      /**
46699       * Calculates the desired buffer low water line based on current time
46700       *
46701       * @return {Number} Desired buffer low water line in seconds
46702       */
46703
46704    }, {
46705      key: 'bufferLowWaterLine',
46706      value: function bufferLowWaterLine() {
46707        var currentTime = this.tech_.currentTime();
46708        var initial = Config.BUFFER_LOW_WATER_LINE;
46709        var rate = Config.BUFFER_LOW_WATER_LINE_RATE;
46710        var max = Math.max(initial, Config.MAX_BUFFER_LOW_WATER_LINE);
46711
46712        return Math.min(initial + currentTime * rate, max);
46713      }
46714    }]);
46715    return MasterPlaylistController;
46716  }(videojs$1.EventTarget);
46717
46718  /**
46719   * Returns a function that acts as the Enable/disable playlist function.
46720   *
46721   * @param {PlaylistLoader} loader - The master playlist loader
46722   * @param {String} playlistUri - uri of the playlist
46723   * @param {Function} changePlaylistFn - A function to be called after a
46724   * playlist's enabled-state has been changed. Will NOT be called if a
46725   * playlist's enabled-state is unchanged
46726   * @param {Boolean=} enable - Value to set the playlist enabled-state to
46727   * or if undefined returns the current enabled-state for the playlist
46728   * @return {Function} Function for setting/getting enabled
46729   */
46730  var enableFunction = function enableFunction(loader, playlistUri, changePlaylistFn) {
46731    return function (enable) {
46732      var playlist = loader.master.playlists[playlistUri];
46733      var incompatible = isIncompatible(playlist);
46734      var currentlyEnabled = isEnabled(playlist);
46735
46736      if (typeof enable === 'undefined') {
46737        return currentlyEnabled;
46738      }
46739
46740      if (enable) {
46741        delete playlist.disabled;
46742      } else {
46743        playlist.disabled = true;
46744      }
46745
46746      if (enable !== currentlyEnabled && !incompatible) {
46747        // Ensure the outside world knows about our changes
46748        changePlaylistFn();
46749        if (enable) {
46750          loader.trigger('renditionenabled');
46751        } else {
46752          loader.trigger('renditiondisabled');
46753        }
46754      }
46755      return enable;
46756    };
46757  };
46758
46759  /**
46760   * The representation object encapsulates the publicly visible information
46761   * in a media playlist along with a setter/getter-type function (enabled)
46762   * for changing the enabled-state of a particular playlist entry
46763   *
46764   * @class Representation
46765   */
46766
46767  var Representation = function Representation(hlsHandler, playlist, id) {
46768    classCallCheck$3(this, Representation);
46769
46770    // Get a reference to a bound version of fastQualityChange_
46771    var fastChangeFunction = hlsHandler.masterPlaylistController_.fastQualityChange_.bind(hlsHandler.masterPlaylistController_);
46772
46773    // some playlist attributes are optional
46774    if (playlist.attributes.RESOLUTION) {
46775      var resolution = playlist.attributes.RESOLUTION;
46776
46777      this.width = resolution.width;
46778      this.height = resolution.height;
46779    }
46780
46781    this.bandwidth = playlist.attributes.BANDWIDTH;
46782
46783    // The id is simply the ordinality of the media playlist
46784    // within the master playlist
46785    this.id = id;
46786
46787    // Partially-apply the enableFunction to create a playlist-
46788    // specific variant
46789    this.enabled = enableFunction(hlsHandler.playlists, playlist.uri, fastChangeFunction);
46790  };
46791
46792  /**
46793   * A mixin function that adds the `representations` api to an instance
46794   * of the HlsHandler class
46795   * @param {HlsHandler} hlsHandler - An instance of HlsHandler to add the
46796   * representation API into
46797   */
46798
46799  var renditionSelectionMixin = function renditionSelectionMixin(hlsHandler) {
46800    var playlists = hlsHandler.playlists;
46801
46802    // Add a single API-specific function to the HlsHandler instance
46803    hlsHandler.representations = function () {
46804      return playlists.master.playlists.filter(function (media) {
46805        return !isIncompatible(media);
46806      }).map(function (e, i) {
46807        return new Representation(hlsHandler, e, e.uri);
46808      });
46809    };
46810  };
46811
46812  /**
46813   * @file playback-watcher.js
46814   *
46815   * Playback starts, and now my watch begins. It shall not end until my death. I shall
46816   * take no wait, hold no uncleared timeouts, father no bad seeks. I shall wear no crowns
46817   * and win no glory. I shall live and die at my post. I am the corrector of the underflow.
46818   * I am the watcher of gaps. I am the shield that guards the realms of seekable. I pledge
46819   * my life and honor to the Playback Watch, for this Player and all the Players to come.
46820   */
46821
46822  // Set of events that reset the playback-watcher time check logic and clear the timeout
46823  var timerCancelEvents = ['seeking', 'seeked', 'pause', 'playing', 'error'];
46824
46825  /**
46826   * @class PlaybackWatcher
46827   */
46828
46829  var PlaybackWatcher = function () {
46830    /**
46831     * Represents an PlaybackWatcher object.
46832     * @constructor
46833     * @param {object} options an object that includes the tech and settings
46834     */
46835    function PlaybackWatcher(options) {
46836      var _this = this;
46837
46838      classCallCheck$3(this, PlaybackWatcher);
46839
46840      this.tech_ = options.tech;
46841      this.seekable = options.seekable;
46842
46843      this.consecutiveUpdates = 0;
46844      this.lastRecordedTime = null;
46845      this.timer_ = null;
46846      this.checkCurrentTimeTimeout_ = null;
46847      this.logger_ = logger('PlaybackWatcher');
46848
46849      this.logger_('initialize');
46850
46851      var canPlayHandler = function canPlayHandler() {
46852        return _this.monitorCurrentTime_();
46853      };
46854      var waitingHandler = function waitingHandler() {
46855        return _this.techWaiting_();
46856      };
46857      var cancelTimerHandler = function cancelTimerHandler() {
46858        return _this.cancelTimer_();
46859      };
46860      var fixesBadSeeksHandler = function fixesBadSeeksHandler() {
46861        return _this.fixesBadSeeks_();
46862      };
46863
46864      this.tech_.on('seekablechanged', fixesBadSeeksHandler);
46865      this.tech_.on('waiting', waitingHandler);
46866      this.tech_.on(timerCancelEvents, cancelTimerHandler);
46867      this.tech_.on('canplay', canPlayHandler);
46868
46869      // Define the dispose function to clean up our events
46870      this.dispose = function () {
46871        _this.logger_('dispose');
46872        _this.tech_.off('seekablechanged', fixesBadSeeksHandler);
46873        _this.tech_.off('waiting', waitingHandler);
46874        _this.tech_.off(timerCancelEvents, cancelTimerHandler);
46875        _this.tech_.off('canplay', canPlayHandler);
46876        if (_this.checkCurrentTimeTimeout_) {
46877          window_1.clearTimeout(_this.checkCurrentTimeTimeout_);
46878        }
46879        _this.cancelTimer_();
46880      };
46881    }
46882
46883    /**
46884     * Periodically check current time to see if playback stopped
46885     *
46886     * @private
46887     */
46888
46889    createClass$2(PlaybackWatcher, [{
46890      key: 'monitorCurrentTime_',
46891      value: function monitorCurrentTime_() {
46892        this.checkCurrentTime_();
46893
46894        if (this.checkCurrentTimeTimeout_) {
46895          window_1.clearTimeout(this.checkCurrentTimeTimeout_);
46896        }
46897
46898        // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
46899        this.checkCurrentTimeTimeout_ = window_1.setTimeout(this.monitorCurrentTime_.bind(this), 250);
46900      }
46901
46902      /**
46903       * The purpose of this function is to emulate the "waiting" event on
46904       * browsers that do not emit it when they are waiting for more
46905       * data to continue playback
46906       *
46907       * @private
46908       */
46909
46910    }, {
46911      key: 'checkCurrentTime_',
46912      value: function checkCurrentTime_() {
46913        if (this.tech_.seeking() && this.fixesBadSeeks_()) {
46914          this.consecutiveUpdates = 0;
46915          this.lastRecordedTime = this.tech_.currentTime();
46916          return;
46917        }
46918
46919        if (this.tech_.paused() || this.tech_.seeking()) {
46920          return;
46921        }
46922
46923        var currentTime = this.tech_.currentTime();
46924        var buffered = this.tech_.buffered();
46925
46926        if (this.lastRecordedTime === currentTime && (!buffered.length || currentTime + SAFE_TIME_DELTA >= buffered.end(buffered.length - 1))) {
46927          // If current time is at the end of the final buffered region, then any playback
46928          // stall is most likely caused by buffering in a low bandwidth environment. The tech
46929          // should fire a `waiting` event in this scenario, but due to browser and tech
46930          // inconsistencies. Calling `techWaiting_` here allows us to simulate
46931          // responding to a native `waiting` event when the tech fails to emit one.
46932          return this.techWaiting_();
46933        }
46934
46935        if (this.consecutiveUpdates >= 5 && currentTime === this.lastRecordedTime) {
46936          this.consecutiveUpdates++;
46937          this.waiting_();
46938        } else if (currentTime === this.lastRecordedTime) {
46939          this.consecutiveUpdates++;
46940        } else {
46941          this.consecutiveUpdates = 0;
46942          this.lastRecordedTime = currentTime;
46943        }
46944      }
46945
46946      /**
46947       * Cancels any pending timers and resets the 'timeupdate' mechanism
46948       * designed to detect that we are stalled
46949       *
46950       * @private
46951       */
46952
46953    }, {
46954      key: 'cancelTimer_',
46955      value: function cancelTimer_() {
46956        this.consecutiveUpdates = 0;
46957
46958        if (this.timer_) {
46959          this.logger_('cancelTimer_');
46960          clearTimeout(this.timer_);
46961        }
46962
46963        this.timer_ = null;
46964      }
46965
46966      /**
46967       * Fixes situations where there's a bad seek
46968       *
46969       * @return {Boolean} whether an action was taken to fix the seek
46970       * @private
46971       */
46972
46973    }, {
46974      key: 'fixesBadSeeks_',
46975      value: function fixesBadSeeks_() {
46976        var seeking = this.tech_.seeking();
46977        var seekable = this.seekable();
46978        var currentTime = this.tech_.currentTime();
46979        var seekTo = void 0;
46980
46981        if (seeking && this.afterSeekableWindow_(seekable, currentTime)) {
46982          var seekableEnd = seekable.end(seekable.length - 1);
46983
46984          // sync to live point (if VOD, our seekable was updated and we're simply adjusting)
46985          seekTo = seekableEnd;
46986        }
46987
46988        if (seeking && this.beforeSeekableWindow_(seekable, currentTime)) {
46989          var seekableStart = seekable.start(0);
46990
46991          // sync to the beginning of the live window
46992          // provide a buffer of .1 seconds to handle rounding/imprecise numbers
46993          seekTo = seekableStart + SAFE_TIME_DELTA;
46994        }
46995
46996        if (typeof seekTo !== 'undefined') {
46997          this.logger_('Trying to seek outside of seekable at time ' + currentTime + ' with ' + ('seekable range ' + printableRange(seekable) + '. Seeking to ') + (seekTo + '.'));
46998
46999          this.tech_.setCurrentTime(seekTo);
47000          return true;
47001        }
47002
47003        return false;
47004      }
47005
47006      /**
47007       * Handler for situations when we determine the player is waiting.
47008       *
47009       * @private
47010       */
47011
47012    }, {
47013      key: 'waiting_',
47014      value: function waiting_() {
47015        if (this.techWaiting_()) {
47016          return;
47017        }
47018
47019        // All tech waiting checks failed. Use last resort correction
47020        var currentTime = this.tech_.currentTime();
47021        var buffered = this.tech_.buffered();
47022        var currentRange = findRange(buffered, currentTime);
47023
47024        // Sometimes the player can stall for unknown reasons within a contiguous buffered
47025        // region with no indication that anything is amiss (seen in Firefox). Seeking to
47026        // currentTime is usually enough to kickstart the player. This checks that the player
47027        // is currently within a buffered region before attempting a corrective seek.
47028        // Chrome does not appear to continue `timeupdate` events after a `waiting` event
47029        // until there is ~ 3 seconds of forward buffer available. PlaybackWatcher should also
47030        // make sure there is ~3 seconds of forward buffer before taking any corrective action
47031        // to avoid triggering an `unknownwaiting` event when the network is slow.
47032        if (currentRange.length && currentTime + 3 <= currentRange.end(0)) {
47033          this.cancelTimer_();
47034          this.tech_.setCurrentTime(currentTime);
47035
47036          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.');
47037
47038          // unknown waiting corrections may be useful for monitoring QoS
47039          this.tech_.trigger({ type: 'usage', name: 'hls-unknown-waiting' });
47040          return;
47041        }
47042      }
47043
47044      /**
47045       * Handler for situations when the tech fires a `waiting` event
47046       *
47047       * @return {Boolean}
47048       *         True if an action (or none) was needed to correct the waiting. False if no
47049       *         checks passed
47050       * @private
47051       */
47052
47053    }, {
47054      key: 'techWaiting_',
47055      value: function techWaiting_() {
47056        var seekable = this.seekable();
47057        var currentTime = this.tech_.currentTime();
47058
47059        if (this.tech_.seeking() && this.fixesBadSeeks_()) {
47060          // Tech is seeking or bad seek fixed, no action needed
47061          return true;
47062        }
47063
47064        if (this.tech_.seeking() || this.timer_ !== null) {
47065          // Tech is seeking or already waiting on another action, no action needed
47066          return true;
47067        }
47068
47069        if (this.beforeSeekableWindow_(seekable, currentTime)) {
47070          var livePoint = seekable.end(seekable.length - 1);
47071
47072          this.logger_('Fell out of live window at time ' + currentTime + '. Seeking to ' + ('live point (seekable end) ' + livePoint));
47073          this.cancelTimer_();
47074          this.tech_.setCurrentTime(livePoint);
47075
47076          // live window resyncs may be useful for monitoring QoS
47077          this.tech_.trigger({ type: 'usage', name: 'hls-live-resync' });
47078          return true;
47079        }
47080
47081        var buffered = this.tech_.buffered();
47082        var nextRange = findNextRange(buffered, currentTime);
47083
47084        if (this.videoUnderflow_(nextRange, buffered, currentTime)) {
47085          // Even though the video underflowed and was stuck in a gap, the audio overplayed
47086          // the gap, leading currentTime into a buffered range. Seeking to currentTime
47087          // allows the video to catch up to the audio position without losing any audio
47088          // (only suffering ~3 seconds of frozen video and a pause in audio playback).
47089          this.cancelTimer_();
47090          this.tech_.setCurrentTime(currentTime);
47091
47092          // video underflow may be useful for monitoring QoS
47093          this.tech_.trigger({ type: 'usage', name: 'hls-video-underflow' });
47094          return true;
47095        }
47096
47097        // check for gap
47098        if (nextRange.length > 0) {
47099          var difference = nextRange.start(0) - currentTime;
47100
47101          this.logger_('Stopped at ' + currentTime + ', setting timer for ' + difference + ', seeking ' + ('to ' + nextRange.start(0)));
47102
47103          this.timer_ = setTimeout(this.skipTheGap_.bind(this), difference * 1000, currentTime);
47104          return true;
47105        }
47106
47107        // All checks failed. Returning false to indicate failure to correct waiting
47108        return false;
47109      }
47110    }, {
47111      key: 'afterSeekableWindow_',
47112      value: function afterSeekableWindow_(seekable, currentTime) {
47113        if (!seekable.length) {
47114          // we can't make a solid case if there's no seekable, default to false
47115          return false;
47116        }
47117
47118        if (currentTime > seekable.end(seekable.length - 1) + SAFE_TIME_DELTA) {
47119          return true;
47120        }
47121
47122        return false;
47123      }
47124    }, {
47125      key: 'beforeSeekableWindow_',
47126      value: function beforeSeekableWindow_(seekable, currentTime) {
47127        if (seekable.length &&
47128        // can't fall before 0 and 0 seekable start identifies VOD stream
47129        seekable.start(0) > 0 && currentTime < seekable.start(0) - SAFE_TIME_DELTA) {
47130          return true;
47131        }
47132
47133        return false;
47134      }
47135    }, {
47136      key: 'videoUnderflow_',
47137      value: function videoUnderflow_(nextRange, buffered, currentTime) {
47138        if (nextRange.length === 0) {
47139          // Even if there is no available next range, there is still a possibility we are
47140          // stuck in a gap due to video underflow.
47141          var gap = this.gapFromVideoUnderflow_(buffered, currentTime);
47142
47143          if (gap) {
47144            this.logger_('Encountered a gap in video from ' + gap.start + ' to ' + gap.end + '. ' + ('Seeking to current time ' + currentTime));
47145
47146            return true;
47147          }
47148        }
47149
47150        return false;
47151      }
47152
47153      /**
47154       * Timer callback. If playback still has not proceeded, then we seek
47155       * to the start of the next buffered region.
47156       *
47157       * @private
47158       */
47159
47160    }, {
47161      key: 'skipTheGap_',
47162      value: function skipTheGap_(scheduledCurrentTime) {
47163        var buffered = this.tech_.buffered();
47164        var currentTime = this.tech_.currentTime();
47165        var nextRange = findNextRange(buffered, currentTime);
47166
47167        this.cancelTimer_();
47168
47169        if (nextRange.length === 0 || currentTime !== scheduledCurrentTime) {
47170          return;
47171        }
47172
47173        this.logger_('skipTheGap_:', 'currentTime:', currentTime, 'scheduled currentTime:', scheduledCurrentTime, 'nextRange start:', nextRange.start(0));
47174
47175        // only seek if we still have not played
47176        this.tech_.setCurrentTime(nextRange.start(0) + TIME_FUDGE_FACTOR);
47177
47178        this.tech_.trigger({ type: 'usage', name: 'hls-gap-skip' });
47179      }
47180    }, {
47181      key: 'gapFromVideoUnderflow_',
47182      value: function gapFromVideoUnderflow_(buffered, currentTime) {
47183        // At least in Chrome, if there is a gap in the video buffer, the audio will continue
47184        // playing for ~3 seconds after the video gap starts. This is done to account for
47185        // video buffer underflow/underrun (note that this is not done when there is audio
47186        // buffer underflow/underrun -- in that case the video will stop as soon as it
47187        // encounters the gap, as audio stalls are more noticeable/jarring to a user than
47188        // video stalls). The player's time will reflect the playthrough of audio, so the
47189        // time will appear as if we are in a buffered region, even if we are stuck in a
47190        // "gap."
47191        //
47192        // Example:
47193        // video buffer:   0 => 10.1, 10.2 => 20
47194        // audio buffer:   0 => 20
47195        // overall buffer: 0 => 10.1, 10.2 => 20
47196        // current time: 13
47197        //
47198        // Chrome's video froze at 10 seconds, where the video buffer encountered the gap,
47199        // however, the audio continued playing until it reached ~3 seconds past the gap
47200        // (13 seconds), at which point it stops as well. Since current time is past the
47201        // gap, findNextRange will return no ranges.
47202        //
47203        // To check for this issue, we see if there is a gap that starts somewhere within
47204        // a 3 second range (3 seconds +/- 1 second) back from our current time.
47205        var gaps = findGaps(buffered);
47206
47207        for (var i = 0; i < gaps.length; i++) {
47208          var start = gaps.start(i);
47209          var end = gaps.end(i);
47210
47211          // gap is starts no more than 4 seconds back
47212          if (currentTime - start < 4 && currentTime - start > 2) {
47213            return {
47214              start: start,
47215              end: end
47216            };
47217          }
47218        }
47219
47220        return null;
47221      }
47222    }]);
47223    return PlaybackWatcher;
47224  }();
47225
47226  var defaultOptions = {
47227    errorInterval: 30,
47228    getSource: function getSource(next) {
47229      var tech = this.tech({ IWillNotUseThisInPlugins: true });
47230      var sourceObj = tech.currentSource_;
47231
47232      return next(sourceObj);
47233    }
47234  };
47235
47236  /**
47237   * Main entry point for the plugin
47238   *
47239   * @param {Player} player a reference to a videojs Player instance
47240   * @param {Object} [options] an object with plugin options
47241   * @private
47242   */
47243  var initPlugin = function initPlugin(player, options) {
47244    var lastCalled = 0;
47245    var seekTo = 0;
47246    var localOptions = videojs$1.mergeOptions(defaultOptions, options);
47247
47248    player.ready(function () {
47249      player.trigger({ type: 'usage', name: 'hls-error-reload-initialized' });
47250    });
47251
47252    /**
47253     * Player modifications to perform that must wait until `loadedmetadata`
47254     * has been triggered
47255     *
47256     * @private
47257     */
47258    var loadedMetadataHandler = function loadedMetadataHandler() {
47259      if (seekTo) {
47260        player.currentTime(seekTo);
47261      }
47262    };
47263
47264    /**
47265     * Set the source on the player element, play, and seek if necessary
47266     *
47267     * @param {Object} sourceObj An object specifying the source url and mime-type to play
47268     * @private
47269     */
47270    var setSource = function setSource(sourceObj) {
47271      if (sourceObj === null || sourceObj === undefined) {
47272        return;
47273      }
47274      seekTo = player.duration() !== Infinity && player.currentTime() || 0;
47275
47276      player.one('loadedmetadata', loadedMetadataHandler);
47277
47278      player.src(sourceObj);
47279      player.trigger({ type: 'usage', name: 'hls-error-reload' });
47280      player.play();
47281    };
47282
47283    /**
47284     * Attempt to get a source from either the built-in getSource function
47285     * or a custom function provided via the options
47286     *
47287     * @private
47288     */
47289    var errorHandler = function errorHandler() {
47290      // Do not attempt to reload the source if a source-reload occurred before
47291      // 'errorInterval' time has elapsed since the last source-reload
47292      if (Date.now() - lastCalled < localOptions.errorInterval * 1000) {
47293        player.trigger({ type: 'usage', name: 'hls-error-reload-canceled' });
47294        return;
47295      }
47296
47297      if (!localOptions.getSource || typeof localOptions.getSource !== 'function') {
47298        videojs$1.log.error('ERROR: reloadSourceOnError - The option getSource must be a function!');
47299        return;
47300      }
47301      lastCalled = Date.now();
47302
47303      return localOptions.getSource.call(player, setSource);
47304    };
47305
47306    /**
47307     * Unbind any event handlers that were bound by the plugin
47308     *
47309     * @private
47310     */
47311    var cleanupEvents = function cleanupEvents() {
47312      player.off('loadedmetadata', loadedMetadataHandler);
47313      player.off('error', errorHandler);
47314      player.off('dispose', cleanupEvents);
47315    };
47316
47317    /**
47318     * Cleanup before re-initializing the plugin
47319     *
47320     * @param {Object} [newOptions] an object with plugin options
47321     * @private
47322     */
47323    var reinitPlugin = function reinitPlugin(newOptions) {
47324      cleanupEvents();
47325      initPlugin(player, newOptions);
47326    };
47327
47328    player.on('error', errorHandler);
47329    player.on('dispose', cleanupEvents);
47330
47331    // Overwrite the plugin function so that we can correctly cleanup before
47332    // initializing the plugin
47333    player.reloadSourceOnError = reinitPlugin;
47334  };
47335
47336  /**
47337   * Reload the source when an error is detected as long as there
47338   * wasn't an error previously within the last 30 seconds
47339   *
47340   * @param {Object} [options] an object with plugin options
47341   */
47342  var reloadSourceOnError = function reloadSourceOnError(options) {
47343    initPlugin(this, options);
47344  };
47345
47346  var version$3 = "1.1.0";
47347
47348  /**
47349   * @file videojs-http-streaming.js
47350   *
47351   * The main file for the HLS project.
47352   * License: https://github.com/videojs/videojs-http-streaming/blob/master/LICENSE
47353   */
47354
47355  var Hls$1 = {
47356    PlaylistLoader: PlaylistLoader,
47357    Playlist: Playlist,
47358    Decrypter: Decrypter,
47359    AsyncStream: AsyncStream,
47360    decrypt: decrypt,
47361    utils: utils,
47362
47363    STANDARD_PLAYLIST_SELECTOR: lastBandwidthSelector,
47364    INITIAL_PLAYLIST_SELECTOR: lowestBitrateCompatibleVariantSelector,
47365    comparePlaylistBandwidth: comparePlaylistBandwidth,
47366    comparePlaylistResolution: comparePlaylistResolution,
47367
47368    xhr: xhrFactory()
47369  };
47370
47371  // 0.5 MB/s
47372  var INITIAL_BANDWIDTH = 4194304;
47373
47374  // Define getter/setters for config properites
47375  ['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) {
47376    Object.defineProperty(Hls$1, prop, {
47377      get: function get$$1() {
47378        videojs$1.log.warn('using Hls.' + prop + ' is UNSAFE be sure you know what you are doing');
47379        return Config[prop];
47380      },
47381      set: function set$$1(value) {
47382        videojs$1.log.warn('using Hls.' + prop + ' is UNSAFE be sure you know what you are doing');
47383
47384        if (typeof value !== 'number' || value < 0) {
47385          videojs$1.log.warn('value of Hls.' + prop + ' must be greater than or equal to 0');
47386          return;
47387        }
47388
47389        Config[prop] = value;
47390      }
47391    });
47392  });
47393
47394  var simpleTypeFromSourceType = function simpleTypeFromSourceType(type) {
47395    var mpegurlRE = /^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i;
47396
47397    if (mpegurlRE.test(type)) {
47398      return 'hls';
47399    }
47400
47401    var dashRE = /^application\/dash\+xml/i;
47402
47403    if (dashRE.test(type)) {
47404      return 'dash';
47405    }
47406
47407    return null;
47408  };
47409
47410  /**
47411   * Updates the selectedIndex of the QualityLevelList when a mediachange happens in hls.
47412   *
47413   * @param {QualityLevelList} qualityLevels The QualityLevelList to update.
47414   * @param {PlaylistLoader} playlistLoader PlaylistLoader containing the new media info.
47415   * @function handleHlsMediaChange
47416   */
47417  var handleHlsMediaChange = function handleHlsMediaChange(qualityLevels, playlistLoader) {
47418    var newPlaylist = playlistLoader.media();
47419    var selectedIndex = -1;
47420
47421    for (var i = 0; i < qualityLevels.length; i++) {
47422      if (qualityLevels[i].id === newPlaylist.uri) {
47423        selectedIndex = i;
47424        break;
47425      }
47426    }
47427
47428    qualityLevels.selectedIndex_ = selectedIndex;
47429    qualityLevels.trigger({
47430      selectedIndex: selectedIndex,
47431      type: 'change'
47432    });
47433  };
47434
47435  /**
47436   * Adds quality levels to list once playlist metadata is available
47437   *
47438   * @param {QualityLevelList} qualityLevels The QualityLevelList to attach events to.
47439   * @param {Object} hls Hls object to listen to for media events.
47440   * @function handleHlsLoadedMetadata
47441   */
47442  var handleHlsLoadedMetadata = function handleHlsLoadedMetadata(qualityLevels, hls) {
47443    hls.representations().forEach(function (rep) {
47444      qualityLevels.addQualityLevel(rep);
47445    });
47446    handleHlsMediaChange(qualityLevels, hls.playlists);
47447  };
47448
47449  // HLS is a source handler, not a tech. Make sure attempts to use it
47450  // as one do not cause exceptions.
47451  Hls$1.canPlaySource = function () {
47452    return videojs$1.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
47453  };
47454
47455  var emeKeySystems = function emeKeySystems(keySystemOptions, videoPlaylist, audioPlaylist) {
47456    if (!keySystemOptions) {
47457      return keySystemOptions;
47458    }
47459
47460    // upsert the content types based on the selected playlist
47461    var keySystemContentTypes = {};
47462
47463    for (var keySystem in keySystemOptions) {
47464      keySystemContentTypes[keySystem] = {
47465        audioContentType: 'audio/mp4; codecs="' + audioPlaylist.attributes.CODECS + '"',
47466        videoContentType: 'video/mp4; codecs="' + videoPlaylist.attributes.CODECS + '"'
47467      };
47468
47469      if (videoPlaylist.contentProtection && videoPlaylist.contentProtection[keySystem] && videoPlaylist.contentProtection[keySystem].pssh) {
47470        keySystemContentTypes[keySystem].pssh = videoPlaylist.contentProtection[keySystem].pssh;
47471      }
47472
47473      // videojs-contrib-eme accepts the option of specifying: 'com.some.cdm': 'url'
47474      // so we need to prevent overwriting the URL entirely
47475      if (typeof keySystemOptions[keySystem] === 'string') {
47476        keySystemContentTypes[keySystem].url = keySystemOptions[keySystem];
47477      }
47478    }
47479
47480    return videojs$1.mergeOptions(keySystemOptions, keySystemContentTypes);
47481  };
47482
47483  var setupEmeOptions = function setupEmeOptions(hlsHandler) {
47484    if (hlsHandler.options_.sourceType !== 'dash') {
47485      return;
47486    }
47487    var player = videojs$1.players[hlsHandler.tech_.options_.playerId];
47488
47489    if (player.eme) {
47490      var sourceOptions = emeKeySystems(hlsHandler.source_.keySystems, hlsHandler.playlists.media(), hlsHandler.masterPlaylistController_.mediaTypes_.AUDIO.activePlaylistLoader.media());
47491
47492      if (sourceOptions) {
47493        player.currentSource().keySystems = sourceOptions;
47494      }
47495    }
47496  };
47497
47498  /**
47499   * Whether the browser has built-in HLS support.
47500   */
47501  Hls$1.supportsNativeHls = function () {
47502    var video = document_1.createElement('video');
47503
47504    // native HLS is definitely not supported if HTML5 video isn't
47505    if (!videojs$1.getTech('Html5').isSupported()) {
47506      return false;
47507    }
47508
47509    // HLS manifests can go by many mime-types
47510    var canPlay = [
47511    // Apple santioned
47512    'application/vnd.apple.mpegurl',
47513    // Apple sanctioned for backwards compatibility
47514    'audio/mpegurl',
47515    // Very common
47516    'audio/x-mpegurl',
47517    // Very common
47518    'application/x-mpegurl',
47519    // Included for completeness
47520    'video/x-mpegurl', 'video/mpegurl', 'application/mpegurl'];
47521
47522    return canPlay.some(function (canItPlay) {
47523      return (/maybe|probably/i.test(video.canPlayType(canItPlay))
47524      );
47525    });
47526  }();
47527
47528  Hls$1.supportsNativeDash = function () {
47529    if (!videojs$1.getTech('Html5').isSupported()) {
47530      return false;
47531    }
47532
47533    return (/maybe|probably/i.test(document_1.createElement('video').canPlayType('application/dash+xml'))
47534    );
47535  }();
47536
47537  Hls$1.supportsTypeNatively = function (type) {
47538    if (type === 'hls') {
47539      return Hls$1.supportsNativeHls;
47540    }
47541
47542    if (type === 'dash') {
47543      return Hls$1.supportsNativeDash;
47544    }
47545
47546    return false;
47547  };
47548
47549  /**
47550   * HLS is a source handler, not a tech. Make sure attempts to use it
47551   * as one do not cause exceptions.
47552   */
47553  Hls$1.isSupported = function () {
47554    return videojs$1.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
47555  };
47556
47557  var Component$1 = videojs$1.getComponent('Component');
47558
47559  /**
47560   * The Hls Handler object, where we orchestrate all of the parts
47561   * of HLS to interact with video.js
47562   *
47563   * @class HlsHandler
47564   * @extends videojs.Component
47565   * @param {Object} source the soruce object
47566   * @param {Tech} tech the parent tech object
47567   * @param {Object} options optional and required options
47568   */
47569
47570  var HlsHandler = function (_Component) {
47571    inherits$3(HlsHandler, _Component);
47572
47573    function HlsHandler(source, tech, options) {
47574      classCallCheck$3(this, HlsHandler);
47575
47576      // tech.player() is deprecated but setup a reference to HLS for
47577      // backwards-compatibility
47578      var _this = possibleConstructorReturn$3(this, (HlsHandler.__proto__ || Object.getPrototypeOf(HlsHandler)).call(this, tech, options.hls));
47579
47580      if (tech.options_ && tech.options_.playerId) {
47581        var _player = videojs$1(tech.options_.playerId);
47582
47583        if (!_player.hasOwnProperty('hls')) {
47584          Object.defineProperty(_player, 'hls', {
47585            get: function get$$1() {
47586              videojs$1.log.warn('player.hls is deprecated. Use player.tech_.hls instead.');
47587              tech.trigger({ type: 'usage', name: 'hls-player-access' });
47588              return _this;
47589            }
47590          });
47591        }
47592
47593        // Set up a reference to the HlsHandler from player.vhs. This allows users to start
47594        // migrating from player.tech_.hls... to player.vhs... for API access. Although this
47595        // isn't the most appropriate form of reference for video.js (since all APIs should
47596        // be provided through core video.js), it is a common pattern for plugins, and vhs
47597        // will act accordingly.
47598        _player.vhs = _this;
47599        // deprecated, for backwards compatibility
47600        _player.dash = _this;
47601      }
47602
47603      _this.tech_ = tech;
47604      _this.source_ = source;
47605      _this.stats = {};
47606      _this.ignoreNextSeekingEvent_ = false;
47607      _this.setOptions_();
47608
47609      if (_this.options_.overrideNative && tech.overrideNativeAudioTracks && tech.overrideNativeVideoTracks) {
47610        tech.overrideNativeAudioTracks(true);
47611        tech.overrideNativeVideoTracks(true);
47612      } else if (_this.options_.overrideNative && (tech.featuresNativeVideoTracks || tech.featuresNativeAudioTracks)) {
47613        // overriding native HLS only works if audio tracks have been emulated
47614        // error early if we're misconfigured
47615        throw new Error('Overriding native HLS requires emulated tracks. ' + 'See https://git.io/vMpjB');
47616      }
47617
47618      // listen for fullscreenchange events for this player so that we
47619      // can adjust our quality selection quickly
47620      _this.on(document_1, ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'], function (event) {
47621        var fullscreenElement = document_1.fullscreenElement || document_1.webkitFullscreenElement || document_1.mozFullScreenElement || document_1.msFullscreenElement;
47622
47623        if (fullscreenElement && fullscreenElement.contains(_this.tech_.el())) {
47624          _this.masterPlaylistController_.fastQualityChange_();
47625        }
47626      });
47627
47628      _this.on(_this.tech_, 'seeking', function () {
47629        if (this.ignoreNextSeekingEvent_) {
47630          this.ignoreNextSeekingEvent_ = false;
47631          return;
47632        }
47633
47634        this.setCurrentTime(this.tech_.currentTime());
47635      });
47636      _this.on(_this.tech_, 'error', function () {
47637        if (this.masterPlaylistController_) {
47638          this.masterPlaylistController_.pauseLoading();
47639        }
47640      });
47641
47642      _this.on(_this.tech_, 'play', _this.play);
47643      return _this;
47644    }
47645
47646    createClass$2(HlsHandler, [{
47647      key: 'setOptions_',
47648      value: function setOptions_() {
47649        var _this2 = this;
47650
47651        // defaults
47652        this.options_.withCredentials = this.options_.withCredentials || false;
47653
47654        if (typeof this.options_.blacklistDuration !== 'number') {
47655          this.options_.blacklistDuration = 5 * 60;
47656        }
47657
47658        // start playlist selection at a reasonable bandwidth for
47659        // broadband internet (0.5 MB/s) or mobile (0.0625 MB/s)
47660        if (typeof this.options_.bandwidth !== 'number') {
47661          this.options_.bandwidth = INITIAL_BANDWIDTH;
47662        }
47663
47664        // If the bandwidth number is unchanged from the initial setting
47665        // then this takes precedence over the enableLowInitialPlaylist option
47666        this.options_.enableLowInitialPlaylist = this.options_.enableLowInitialPlaylist && this.options_.bandwidth === INITIAL_BANDWIDTH;
47667
47668        // grab options passed to player.src
47669        ['withCredentials', 'bandwidth'].forEach(function (option) {
47670          if (typeof _this2.source_[option] !== 'undefined') {
47671            _this2.options_[option] = _this2.source_[option];
47672          }
47673        });
47674
47675        this.bandwidth = this.options_.bandwidth;
47676      }
47677      /**
47678       * called when player.src gets called, handle a new source
47679       *
47680       * @param {Object} src the source object to handle
47681       */
47682
47683    }, {
47684      key: 'src',
47685      value: function src(_src, type) {
47686        var _this3 = this;
47687
47688        // do nothing if the src is falsey
47689        if (!_src) {
47690          return;
47691        }
47692        this.setOptions_();
47693        // add master playlist controller options
47694        this.options_.url = this.source_.src;
47695        this.options_.tech = this.tech_;
47696        this.options_.externHls = Hls$1;
47697        this.options_.sourceType = simpleTypeFromSourceType(type);
47698
47699        this.masterPlaylistController_ = new MasterPlaylistController(this.options_);
47700        this.playbackWatcher_ = new PlaybackWatcher(videojs$1.mergeOptions(this.options_, {
47701          seekable: function seekable$$1() {
47702            return _this3.seekable();
47703          }
47704        }));
47705
47706        this.masterPlaylistController_.on('error', function () {
47707          var player = videojs$1.players[_this3.tech_.options_.playerId];
47708
47709          player.error(_this3.masterPlaylistController_.error);
47710        });
47711
47712        // `this` in selectPlaylist should be the HlsHandler for backwards
47713        // compatibility with < v2
47714        this.masterPlaylistController_.selectPlaylist = this.selectPlaylist ? this.selectPlaylist.bind(this) : Hls$1.STANDARD_PLAYLIST_SELECTOR.bind(this);
47715
47716        this.masterPlaylistController_.selectInitialPlaylist = Hls$1.INITIAL_PLAYLIST_SELECTOR.bind(this);
47717
47718        // re-expose some internal objects for backwards compatibility with < v2
47719        this.playlists = this.masterPlaylistController_.masterPlaylistLoader_;
47720        this.mediaSource = this.masterPlaylistController_.mediaSource;
47721
47722        // Proxy assignment of some properties to the master playlist
47723        // controller. Using a custom property for backwards compatibility
47724        // with < v2
47725        Object.defineProperties(this, {
47726          selectPlaylist: {
47727            get: function get$$1() {
47728              return this.masterPlaylistController_.selectPlaylist;
47729            },
47730            set: function set$$1(selectPlaylist) {
47731              this.masterPlaylistController_.selectPlaylist = selectPlaylist.bind(this);
47732            }
47733          },
47734          throughput: {
47735            get: function get$$1() {
47736              return this.masterPlaylistController_.mainSegmentLoader_.throughput.rate;
47737            },
47738            set: function set$$1(throughput) {
47739              this.masterPlaylistController_.mainSegmentLoader_.throughput.rate = throughput;
47740              // By setting `count` to 1 the throughput value becomes the starting value
47741              // for the cumulative average
47742              this.masterPlaylistController_.mainSegmentLoader_.throughput.count = 1;
47743            }
47744          },
47745          bandwidth: {
47746            get: function get$$1() {
47747              return this.masterPlaylistController_.mainSegmentLoader_.bandwidth;
47748            },
47749            set: function set$$1(bandwidth) {
47750              this.masterPlaylistController_.mainSegmentLoader_.bandwidth = bandwidth;
47751              // setting the bandwidth manually resets the throughput counter
47752              // `count` is set to zero that current value of `rate` isn't included
47753              // in the cumulative average
47754              this.masterPlaylistController_.mainSegmentLoader_.throughput = {
47755                rate: 0,
47756                count: 0
47757              };
47758            }
47759          },
47760          /**
47761           * `systemBandwidth` is a combination of two serial processes bit-rates. The first
47762           * is the network bitrate provided by `bandwidth` and the second is the bitrate of
47763           * the entire process after that - decryption, transmuxing, and appending - provided
47764           * by `throughput`.
47765           *
47766           * Since the two process are serial, the overall system bandwidth is given by:
47767           *   sysBandwidth = 1 / (1 / bandwidth + 1 / throughput)
47768           */
47769          systemBandwidth: {
47770            get: function get$$1() {
47771              var invBandwidth = 1 / (this.bandwidth || 1);
47772              var invThroughput = void 0;
47773
47774              if (this.throughput > 0) {
47775                invThroughput = 1 / this.throughput;
47776              } else {
47777                invThroughput = 0;
47778              }
47779
47780              var systemBitrate = Math.floor(1 / (invBandwidth + invThroughput));
47781
47782              return systemBitrate;
47783            },
47784            set: function set$$1() {
47785              videojs$1.log.error('The "systemBandwidth" property is read-only');
47786            }
47787          }
47788        });
47789
47790        Object.defineProperties(this.stats, {
47791          bandwidth: {
47792            get: function get$$1() {
47793              return _this3.bandwidth || 0;
47794            },
47795            enumerable: true
47796          },
47797          mediaRequests: {
47798            get: function get$$1() {
47799              return _this3.masterPlaylistController_.mediaRequests_() || 0;
47800            },
47801            enumerable: true
47802          },
47803          mediaRequestsAborted: {
47804            get: function get$$1() {
47805              return _this3.masterPlaylistController_.mediaRequestsAborted_() || 0;
47806            },
47807            enumerable: true
47808          },
47809          mediaRequestsTimedout: {
47810            get: function get$$1() {
47811              return _this3.masterPlaylistController_.mediaRequestsTimedout_() || 0;
47812            },
47813            enumerable: true
47814          },
47815          mediaRequestsErrored: {
47816            get: function get$$1() {
47817              return _this3.masterPlaylistController_.mediaRequestsErrored_() || 0;
47818            },
47819            enumerable: true
47820          },
47821          mediaTransferDuration: {
47822            get: function get$$1() {
47823              return _this3.masterPlaylistController_.mediaTransferDuration_() || 0;
47824            },
47825            enumerable: true
47826          },
47827          mediaBytesTransferred: {
47828            get: function get$$1() {
47829              return _this3.masterPlaylistController_.mediaBytesTransferred_() || 0;
47830            },
47831            enumerable: true
47832          },
47833          mediaSecondsLoaded: {
47834            get: function get$$1() {
47835              return _this3.masterPlaylistController_.mediaSecondsLoaded_() || 0;
47836            },
47837            enumerable: true
47838          },
47839          buffered: {
47840            get: function get$$1() {
47841              return timeRangesToArray(_this3.tech_.buffered());
47842            },
47843            enumerable: true
47844          },
47845          currentTime: {
47846            get: function get$$1() {
47847              return _this3.tech_.currentTime();
47848            },
47849            enumerable: true
47850          },
47851          currentSource: {
47852            get: function get$$1() {
47853              return _this3.tech_.currentSource_;
47854            },
47855            enumerable: true
47856          },
47857          currentTech: {
47858            get: function get$$1() {
47859              return _this3.tech_.name_;
47860            },
47861            enumerable: true
47862          },
47863          duration: {
47864            get: function get$$1() {
47865              return _this3.tech_.duration();
47866            },
47867            enumerable: true
47868          },
47869          master: {
47870            get: function get$$1() {
47871              return _this3.playlists.master;
47872            },
47873            enumerable: true
47874          },
47875          playerDimensions: {
47876            get: function get$$1() {
47877              return _this3.tech_.currentDimensions();
47878            },
47879            enumerable: true
47880          },
47881          seekable: {
47882            get: function get$$1() {
47883              return timeRangesToArray(_this3.tech_.seekable());
47884            },
47885            enumerable: true
47886          },
47887          timestamp: {
47888            get: function get$$1() {
47889              return Date.now();
47890            },
47891            enumerable: true
47892          },
47893          videoPlaybackQuality: {
47894            get: function get$$1() {
47895              return _this3.tech_.getVideoPlaybackQuality();
47896            },
47897            enumerable: true
47898          }
47899        });
47900
47901        this.tech_.one('canplay', this.masterPlaylistController_.setupFirstPlay.bind(this.masterPlaylistController_));
47902
47903        this.masterPlaylistController_.on('selectedinitialmedia', function () {
47904          // Add the manual rendition mix-in to HlsHandler
47905          renditionSelectionMixin(_this3);
47906          setupEmeOptions(_this3);
47907        });
47908
47909        // the bandwidth of the primary segment loader is our best
47910        // estimate of overall bandwidth
47911        this.on(this.masterPlaylistController_, 'progress', function () {
47912          this.tech_.trigger('progress');
47913        });
47914
47915        // In the live case, we need to ignore the very first `seeking` event since
47916        // that will be the result of the seek-to-live behavior
47917        this.on(this.masterPlaylistController_, 'firstplay', function () {
47918          this.ignoreNextSeekingEvent_ = true;
47919        });
47920
47921        this.tech_.ready(function () {
47922          return _this3.setupQualityLevels_();
47923        });
47924
47925        // do nothing if the tech has been disposed already
47926        // this can occur if someone sets the src in player.ready(), for instance
47927        if (!this.tech_.el()) {
47928          return;
47929        }
47930
47931        this.tech_.src(videojs$1.URL.createObjectURL(this.masterPlaylistController_.mediaSource));
47932      }
47933
47934      /**
47935       * Initializes the quality levels and sets listeners to update them.
47936       *
47937       * @method setupQualityLevels_
47938       * @private
47939       */
47940
47941    }, {
47942      key: 'setupQualityLevels_',
47943      value: function setupQualityLevels_() {
47944        var _this4 = this;
47945
47946        var player = videojs$1.players[this.tech_.options_.playerId];
47947
47948        if (player && player.qualityLevels) {
47949          this.qualityLevels_ = player.qualityLevels();
47950
47951          this.masterPlaylistController_.on('selectedinitialmedia', function () {
47952            handleHlsLoadedMetadata(_this4.qualityLevels_, _this4);
47953          });
47954
47955          this.playlists.on('mediachange', function () {
47956            handleHlsMediaChange(_this4.qualityLevels_, _this4.playlists);
47957          });
47958        }
47959      }
47960
47961      /**
47962       * Begin playing the video.
47963       */
47964
47965    }, {
47966      key: 'play',
47967      value: function play() {
47968        this.masterPlaylistController_.play();
47969      }
47970
47971      /**
47972       * a wrapper around the function in MasterPlaylistController
47973       */
47974
47975    }, {
47976      key: 'setCurrentTime',
47977      value: function setCurrentTime(currentTime) {
47978        this.masterPlaylistController_.setCurrentTime(currentTime);
47979      }
47980
47981      /**
47982       * a wrapper around the function in MasterPlaylistController
47983       */
47984
47985    }, {
47986      key: 'duration',
47987      value: function duration$$1() {
47988        return this.masterPlaylistController_.duration();
47989      }
47990
47991      /**
47992       * a wrapper around the function in MasterPlaylistController
47993       */
47994
47995    }, {
47996      key: 'seekable',
47997      value: function seekable$$1() {
47998        return this.masterPlaylistController_.seekable();
47999      }
48000
48001      /**
48002       * Abort all outstanding work and cleanup.
48003       */
48004
48005    }, {
48006      key: 'dispose',
48007      value: function dispose() {
48008        if (this.playbackWatcher_) {
48009          this.playbackWatcher_.dispose();
48010        }
48011        if (this.masterPlaylistController_) {
48012          this.masterPlaylistController_.dispose();
48013        }
vendor: 2,813 bytes, lines 48014-48094
48014        if (this.qualityLevels_) {
48015          this.qualityLevels_.dispose();
48016        }
48017        get$2(HlsHandler.prototype.__proto__ || Object.getPrototypeOf(HlsHandler.prototype), 'dispose', this).call(this);
48018      }
48019    }]);
48020    return HlsHandler;
48021  }(Component$1);
48022
48023  /**
48024   * The Source Handler object, which informs video.js what additional
48025   * MIME types are supported and sets up playback. It is registered
48026   * automatically to the appropriate tech based on the capabilities of
48027   * the browser it is running in. It is not necessary to use or modify
48028   * this object in normal usage.
48029   */
48030
48031  var HlsSourceHandler = {
48032    name: 'videojs-http-streaming',
48033    VERSION: version$3,
48034    canHandleSource: function canHandleSource(srcObj) {
48035      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
48036
48037      var localOptions = videojs$1.mergeOptions(videojs$1.options, options);
48038
48039      return HlsSourceHandler.canPlayType(srcObj.type, localOptions);
48040    },
48041    handleSource: function handleSource(source, tech) {
48042      var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
48043
48044      var localOptions = videojs$1.mergeOptions(videojs$1.options, options);
48045
48046      tech.hls = new HlsHandler(source, tech, localOptions);
48047      tech.hls.xhr = xhrFactory();
48048
48049      tech.hls.src(source.src, source.type);
48050      return tech.hls;
48051    },
48052    canPlayType: function canPlayType(type) {
48053      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
48054
48055      var _videojs$mergeOptions = videojs$1.mergeOptions(videojs$1.options, options),
48056          overrideNative = _videojs$mergeOptions.hls.overrideNative;
48057
48058      var supportedType = simpleTypeFromSourceType(type);
48059      var canUseMsePlayback = supportedType && (!Hls$1.supportsTypeNatively(supportedType) || overrideNative);
48060
48061      return canUseMsePlayback ? 'maybe' : '';
48062    }
48063  };
48064
48065  if (typeof videojs$1.MediaSource === 'undefined' || typeof videojs$1.URL === 'undefined') {
48066    videojs$1.MediaSource = MediaSource;
48067    videojs$1.URL = URL$1;
48068  }
48069
48070  // register source handlers with the appropriate techs
48071  if (MediaSource.supportsNativeMediaSources()) {
48072    videojs$1.getTech('Html5').registerSourceHandler(HlsSourceHandler, 0);
48073  }
48074
48075  videojs$1.HlsHandler = HlsHandler;
48076  videojs$1.HlsSourceHandler = HlsSourceHandler;
48077  videojs$1.Hls = Hls$1;
48078  if (!videojs$1.use) {
48079    videojs$1.registerComponent('Hls', Hls$1);
48080  }
48081  videojs$1.options.hls = videojs$1.options.hls || {};
48082
48083  if (videojs$1.registerPlugin) {
48084    videojs$1.registerPlugin('reloadSourceOnError', reloadSourceOnError);
48085  } else {
48086    videojs$1.plugin('reloadSourceOnError', reloadSourceOnError);
48087  }
48088
48089  return videojs$1;
48090
48091})));
48092
48093!function(){!function(a){var b=a&&a.videojs;b&&(b.CDN_VERSION="7.1.0")}(window)}();
48094   

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.